Added Identity docstrings. Renamed Identity method.

This commit is contained in:
Mark Qvist 2021-05-16 21:58:50 +02:00
parent 59f83ee1a5
commit 522204d8a5
2 changed files with 108 additions and 3 deletions

View File

@ -14,8 +14,17 @@ from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.asymmetric import padding
class Identity: class Identity:
#KEYSIZE = 1536 """
This class is used to manage identities in Reticulum. It provides methods
for encryption, decryption, signatures and verification, and is the basis
for all encrypted communication over Reticulum networks.
:param public_only: Specifies whether this destination only holds a public key.
"""
KEYSIZE = 1024 KEYSIZE = 1024
"""
RSA key size in bits.
"""
DERKEYSIZE = KEYSIZE+272 DERKEYSIZE = KEYSIZE+272
# Non-configurable constants # Non-configurable constants
@ -27,6 +36,10 @@ class Identity:
DECRYPT_CHUNKSIZE = KEYSIZE//8 DECRYPT_CHUNKSIZE = KEYSIZE//8
TRUNCATED_HASHLENGTH = 80 # In bits TRUNCATED_HASHLENGTH = 80 # In bits
"""
Constant specifying the truncated hash length (in bits) used by Reticulum
for addressable hashes. Non-configurable.
"""
# Storage # Storage
known_destinations = {} known_destinations = {}
@ -38,6 +51,12 @@ class Identity:
@staticmethod @staticmethod
def recall(destination_hash): def recall(destination_hash):
"""
Recall identity for a destination hash.
:param destination_hash: Destination hash as *bytes*.
:returns: An :ref:`RNS.Identity<api-identity>` instance that can be used to create an outgoing :ref:`RNS.Destination<api-destination>`, or *None* if the destination is unknown.
"""
RNS.log("Searching for "+RNS.prettyhexrep(destination_hash)+"...", RNS.LOG_EXTREME) RNS.log("Searching for "+RNS.prettyhexrep(destination_hash)+"...", RNS.LOG_EXTREME)
if destination_hash in Identity.known_destinations: if destination_hash in Identity.known_destinations:
identity_data = Identity.known_destinations[destination_hash] identity_data = Identity.known_destinations[destination_hash]
@ -52,6 +71,12 @@ class Identity:
@staticmethod @staticmethod
def recall_app_data(destination_hash): def recall_app_data(destination_hash):
"""
Recall last heard app_data for a destination hash.
:param destination_hash: Destination hash as *bytes*.
:returns: *Bytes* containing app_data, or *None* if the destination is unknown.
"""
RNS.log("Searching for app_data for "+RNS.prettyhexrep(destination_hash)+"...", RNS.LOG_EXTREME) RNS.log("Searching for app_data for "+RNS.prettyhexrep(destination_hash)+"...", RNS.LOG_EXTREME)
if destination_hash in Identity.known_destinations: if destination_hash in Identity.known_destinations:
app_data = Identity.known_destinations[destination_hash][3] app_data = Identity.known_destinations[destination_hash][3]
@ -84,6 +109,12 @@ class Identity:
@staticmethod @staticmethod
def full_hash(data): def full_hash(data):
"""
Get a SHA-256 hash of passed data.
:param data: Data to be hashed as *bytes*.
:returns: SHA-256 hash as *bytes*
"""
digest = hashes.Hash(hashes.SHA256(), backend=default_backend()) digest = hashes.Hash(hashes.SHA256(), backend=default_backend())
digest.update(data) digest.update(data)
@ -91,10 +122,22 @@ class Identity:
@staticmethod @staticmethod
def truncated_hash(data): def truncated_hash(data):
"""
Get a truncated SHA-256 hash of passed data.
:param data: Data to be hashed as *bytes*.
:returns: Truncated SHA-256 hash as *bytes*
"""
return Identity.full_hash(data)[:(Identity.TRUNCATED_HASHLENGTH//8)] return Identity.full_hash(data)[:(Identity.TRUNCATED_HASHLENGTH//8)]
@staticmethod @staticmethod
def get_random_hash(): def get_random_hash():
"""
Get a random SHA-256 hash.
:param data: Data to be hashed as *bytes*.
:returns: Truncated SHA-256 hash of random data as *bytes*
"""
return Identity.truncated_hash(os.urandom(10)) return Identity.truncated_hash(os.urandom(10))
@staticmethod @staticmethod
@ -134,6 +177,13 @@ class Identity:
@staticmethod @staticmethod
def from_file(path): def from_file(path):
"""
Create a new :ref:`RNS.Identity<api-identity>` instance from a file.
Can be used to load previously created and saved identities into Reticulum.
:param path: The full path to the saved :ref:`RNS.Identity<api-identity>` data
:returns: A :ref:`RNS.Identity<api-identity>` instance, or *None* if the loaded data was invalid.
"""
identity = Identity(public_only=True) identity = Identity(public_only=True)
if identity.load(path): if identity.load(path):
return identity return identity
@ -175,12 +225,24 @@ class Identity:
RNS.log("Identity keys created for "+RNS.prettyhexrep(self.hash), RNS.LOG_VERBOSE) RNS.log("Identity keys created for "+RNS.prettyhexrep(self.hash), RNS.LOG_VERBOSE)
def get_private_key(self): def get_private_key(self):
"""
:returns: The private key as *bytes*
"""
return self.prv_bytes return self.prv_bytes
def get_public_key(self): def get_public_key(self):
"""
:returns: The public key as *bytes*
"""
return self.pub_bytes return self.pub_bytes
def load_private_key(self, prv_bytes): def load_private_key(self, prv_bytes):
"""
Load a private key into the instance.
:param prv_bytes: The private key as *bytes*.
:returns: True if the key was loaded, otherwise False.
"""
try: try:
self.prv_bytes = prv_bytes self.prv_bytes = prv_bytes
self.prv = serialization.load_der_private_key( self.prv = serialization.load_der_private_key(
@ -203,6 +265,12 @@ class Identity:
return False return False
def load_public_key(self, key): def load_public_key(self, key):
"""
Load a public key into the instance.
:param prv_bytes: The public key as *bytes*.
:returns: True if the key was loaded, otherwise False.
"""
try: try:
self.pub_bytes = key self.pub_bytes = key
self.pub = load_der_public_key(self.pub_bytes, backend=default_backend()) self.pub = load_der_public_key(self.pub_bytes, backend=default_backend())
@ -214,7 +282,15 @@ class Identity:
self.hash = Identity.truncated_hash(self.pub_bytes) self.hash = Identity.truncated_hash(self.pub_bytes)
self.hexhash = self.hash.hex() self.hexhash = self.hash.hex()
def save(self, path): def to_file(self, path):
"""
Saves the identity to a file. This will write the private key to disk,
and anyone with access to this file will be able to decrypt all
communication for the identity. Be very careful with this method.
:param path: The full path specifying where to save the identity.
:returns: True if the file was saved, otherwise False.
"""
try: try:
with open(path, "wb") as key_file: with open(path, "wb") as key_file:
key_file.write(self.prv_bytes) key_file.write(self.prv_bytes)
@ -235,6 +311,13 @@ class Identity:
RNS.log("The contained exception was: "+str(e)) RNS.log("The contained exception was: "+str(e))
def encrypt(self, plaintext): def encrypt(self, plaintext):
"""
Encrypts information for the identity.
:param plaintext: The plaintext to be encrypted as *bytes*.
:returns: Ciphertext as *bytes*.
:raises: *KeyError* if the instance does not hold a public key
"""
if self.pub != None: if self.pub != None:
chunksize = Identity.ENCRYPT_CHUNKSIZE chunksize = Identity.ENCRYPT_CHUNKSIZE
chunks = int(math.ceil(len(plaintext)/(float(chunksize)))) chunks = int(math.ceil(len(plaintext)/(float(chunksize))))
@ -260,6 +343,13 @@ class Identity:
def decrypt(self, ciphertext): def decrypt(self, ciphertext):
"""
Decrypts information for the identity.
:param ciphertext: The ciphertext to be decrypted as *bytes*.
:returns: Plaintext as *bytes*, or *None* if decryption fails.
:raises: *KeyError* if the instance does not hold a private key
"""
if self.prv != None: if self.prv != None:
plaintext = None plaintext = None
try: try:
@ -290,6 +380,13 @@ class Identity:
def sign(self, message): def sign(self, message):
"""
Signs information by the identity.
:param message: The message to be signed as *bytes*.
:returns: Signature as *bytes*.
:raises: *KeyError* if the instance does not hold a private key
"""
if self.prv != None: if self.prv != None:
signature = self.prv.sign( signature = self.prv.sign(
message, message,
@ -304,6 +401,14 @@ class Identity:
raise KeyError("Signing failed because identity does not hold a private key") raise KeyError("Signing failed because identity does not hold a private key")
def validate(self, signature, message): def validate(self, signature, message):
"""
Validates the signature of a signed message.
:param signature: The signature to be validated as *bytes*.
:param message: The message to be validated as *bytes*.
:returns: True if the signature is valid, otherwise False.
:raises: *KeyError* if the instance does not hold a public key
"""
if self.pub != None: if self.pub != None:
try: try:
self.pub.verify( self.pub.verify(

View File

@ -93,7 +93,7 @@ class Transport:
if Transport.identity == None: if Transport.identity == None:
RNS.log("No valid Transport Identity in storage, creating...", RNS.LOG_VERBOSE) RNS.log("No valid Transport Identity in storage, creating...", RNS.LOG_VERBOSE)
Transport.identity = RNS.Identity() Transport.identity = RNS.Identity()
Transport.identity.save(transport_identity_path) Transport.identity.to_file(transport_identity_path)
else: else:
RNS.log("Loaded Transport Identity from storage", RNS.LOG_VERBOSE) RNS.log("Loaded Transport Identity from storage", RNS.LOG_VERBOSE)