From 09d9d4b48e7db1caee8e755874c85c32d8315a31 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Fri, 9 Oct 2020 15:54:15 +0200 Subject: [PATCH] Fix docstrings and comments (WIP) Updates docstrings and comments interface and gpg.functions. --- securesystemslib/gpg/functions.py | 54 +-- securesystemslib/interface.py | 634 ++++++++++-------------------- 2 files changed, 230 insertions(+), 458 deletions(-) diff --git a/securesystemslib/gpg/functions.py b/securesystemslib/gpg/functions.py index 8c157c516..f609ddd38 100644 --- a/securesystemslib/gpg/functions.py +++ b/securesystemslib/gpg/functions.py @@ -232,41 +232,23 @@ def verify_signature(signature_object, pubkey_info, content): def export_pubkey(keyid, homedir=None): - """ - - Calls gpg command line utility to export the gpg public key bundle - identified by the passed keyid from the gpg keyring at the passed homedir - in a securesystemslib-style format. - - NOTE: The identified key is exported including the corresponding master - key and all subkeys. - - The executed base export command is defined in - securesystemslib.gpg.constants.GPG_EXPORT_PUBKEY_COMMAND. + """Exports a public key from a GnuPG keyring. - - keyid: - The GPG keyid in format: securesystemslib.formats.KEYID_SCHEMA - - homedir: (optional) - Path to the gpg keyring. If not passed the default keyring is used. - - - ValueError: - if the keyid does not match the required format. - - securesystemslib.exceptions.UnsupportedLibraryError: - If the gpg command is not available, or - the cryptography library is not installed. - - securesystemslib.gpg.execeptions.KeyNotFoundError: - if no key or subkey was found for that keyid. + Arguments: + keyid: An OpenPGP keyid in KEYID_SCHEMA format. + homedir (optional): A path to the GnuPG home directory. If not set the + default GnuPG home directory is used. + Raises: + ValueError: Keyid is not a string. + UnsupportedLibraryError: The gpg command or pyca/cryptography are not + available. + KeyNotFoundError: No key or subkey was found for that keyid. - - None. + Side Effects: + Calls system gpg command in a subprocess. - + Returns: The exported public key object in the format: securesystemslib.formats.GPG_PUBKEY_SCHEMA. @@ -302,7 +284,7 @@ def export_pubkey(keyid, homedir=None): def export_pubkeys(keyids, homedir=None): - """Export multiple public keys from a GnuPG keyring. + """Exports multiple public keys from a GnuPG keyring. Arguments: keyids: A list of OpenPGP keyids in KEYID_SCHEMA format. @@ -311,7 +293,13 @@ def export_pubkeys(keyids, homedir=None): Raises: TypeError: Keyids is not iterable. - See 'export_pubkey' for other exceptions. + ValueError: A Keyid is not a string. + UnsupportedLibraryError: The gpg command or pyca/cryptography are not + available. + KeyNotFoundError: No key or subkey was found for that keyid. + + Side Effects: + Calls system gpg command in a subprocess. Returns: A dict with the OpenPGP keyids passed as the keyids argument for dict keys diff --git a/securesystemslib/interface.py b/securesystemslib/interface.py index 42049b727..8796603e9 100755 --- a/securesystemslib/interface.py +++ b/securesystemslib/interface.py @@ -96,10 +96,8 @@ def get_password(prompt='Password: ', confirm=False): The password entered by the user. - """ - # Are the arguments the expected type? - # If not, raise 'securesystemslib.exceptions.FormatError'. + """ securesystemslib.formats.TEXT_SCHEMA.check_match(prompt) securesystemslib.formats.BOOLEAN_SCHEMA.check_match(confirm) @@ -189,90 +187,70 @@ def _get_key_file_decryption_password(password, prompt, path): + def generate_and_write_rsa_keypair(filepath=None, bits=DEFAULT_RSA_KEY_BITS, password=None, prompt=False): - """ - - Generate an RSA key pair. The public portion of the generated RSA key is - saved to <'filepath'>.pub, whereas the private key portion is saved to - <'filepath'>. If no password is given, the user is prompted for one. If - the 'password' is an empty string, the private key is saved unencrypted to - <'filepath'>. If the filepath is not given, the KEYID is used as the - filename and the keypair saved to the current working directory. + """Generates RSA key pair and writes PEM-encoded keys to disk. - The best available form of encryption, for a given key's backend, is used - with pyca/cryptography. According to their documentation, "it is a curated - encryption choice and the algorithm may change over time." + If a password is passed or entered on the prompt, the private key is + encrypted. According to the documentation of the used pyca/cryptography + library encryption is performed "using the best available encryption for a + given key's backend", which "is a curated encryption choice and the algorithm + may change over time." The private key is written in PKCS#1 and the public + key in X.509 SubjectPublicKeyInfo format. - - filepath: - The public and private key files are saved to .pub and - , respectively. If the filepath is not given, the public and - private keys are saved to the current working directory as .pub - and . KEYID is the generated key's KEYID. - - bits: - The number of bits of the generated RSA key. + Arguments: + filepath (optional): The path to write the private key to. If not passed, + the key is written to CWD using the keyid as filename. The public key + is written to the same path as the private key using the suffix '.pub'. + bits (optional): The number of bits of the generated RSA key. + password (optional): An encryption password. + prompt (optional): A boolean indicating if the user should be prompted + for an encryption password. If the user enters an empty password, the + key is not encrypted. - password: - The password to encrypt 'filepath'. If None, the user is prompted for a - password. If an empty string is given, the private key is written to - disk unencrypted. + Raises: + UnsupportedLibraryError: pyca/cryptography is not available. + FormatError: Arguments are malformed. + ValueError: An empty string is passed as 'password', or both a 'password' + is passed and 'prompt' is true. + StorageError: Key files cannot be written. - - securesystemslib.exceptions.FormatError, if the arguments are improperly - formatted. + Side Effects: + Writes key files to disk. - - Writes key files to '' and '.pub'. + Returns: + The private key filepath. - - The 'filepath' of the written key. """ - - # Does 'bits' have the correct format? - # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. securesystemslib.formats.RSAKEYBITS_SCHEMA.check_match(bits) + password = _get_key_file_encryption_password(password, prompt, filepath) - # Generate the public and private RSA keys. + # Generate private RSA key and extract public and private both in PEM rsa_key = securesystemslib.keys.generate_rsa_key(bits) public = rsa_key['keyval']['public'] private = rsa_key['keyval']['private'] + # Use passed 'filepath' or keyid as file name if not filepath: filepath = os.path.join(os.getcwd(), rsa_key['keyid']) - else: - logger.debug('The filepath has been specified. Not using the key\'s' - ' KEYID as the default filepath.') - - # Does 'filepath' have the correct format? securesystemslib.formats.PATH_SCHEMA.check_match(filepath) - - # Encrypt the private key if 'password' is set. + # Encrypt the private key if a 'password' was passed or entered on the prompt if password is not None: private = securesystemslib.keys.create_rsa_encrypted_pem(private, password) - else: - logger.debug('An empty password was given. Not encrypting the private key.') - - # If the parent directory of filepath does not exist, - # create it (and all its parent directories, if necessary). + # Create intermediate directories as required securesystemslib.util.ensure_parent_dir(filepath) - # Write the public key (i.e., 'public', which is in PEM format) to - # '.pub'. (1) Create a temporary file, (2) write the contents of - # the public key, and (3) move to final destination. + # Write PEM-encoded public key to .pub file_object = tempfile.TemporaryFile() file_object.write(public.encode('utf-8')) - # The temporary file is closed after the final move. securesystemslib.util.persist_temp_file(file_object, filepath + '.pub') - # Write the private key in encrypted PEM format to ''. - # Unlike the public key file, the private key does not have a file - # extension. + # Write PEM-encoded private key to file_object = tempfile.TemporaryFile() file_object.write(private.encode('utf-8')) securesystemslib.util.persist_temp_file(file_object, filepath) @@ -285,89 +263,46 @@ def generate_and_write_rsa_keypair(filepath=None, bits=DEFAULT_RSA_KEY_BITS, def import_rsa_privatekey_from_file(filepath, password=None, scheme='rsassa-pss-sha256', prompt=False, storage_backend=None): - """ - - Import the PEM file in 'filepath' containing the private key. - - If password is passed use passed password for decryption. - If prompt is True use entered password for decryption. - If no password is passed and either prompt is False or if the password - entered at the prompt is an empty string, omit decryption, treating the - key as if it is not encrypted. - If password is passed and prompt is True, an error is raised. (See below.) - - The returned key is an object in the - 'securesystemslib.formats.RSAKEY_SCHEMA' format. + """Imports PEM-encoded RSA private key from file storage. - - filepath: - file, an RSA encrypted PEM file. Unlike the public RSA PEM - key file, 'filepath' does not have an extension. - - password: - The passphrase to decrypt 'filepath'. - - scheme: - The signature scheme used by the imported key. - - prompt: - If True the user is prompted for a passphrase to decrypt 'filepath'. - Default is False. + The expected key format is PKCS#1. If a password is passed or entered on the + prompt, the private key is decrypted, otherwise it is treated as unencrypted. - storage_backend: - An object which implements - securesystemslib.storage.StorageBackendInterface. When no object is - passed a FilesystemBackend will be instantiated and used. - - - ValueError, if 'password' is passed and 'prompt' is True. - - ValueError, if 'password' is passed and it is an empty string. - - securesystemslib.exceptions.FormatError, if the arguments are improperly - formatted. - - securesystemslib.exceptions.FormatError, if the entered password is - improperly formatted. - - IOError, if 'filepath' can't be loaded. - - securesystemslib.exceptions.CryptoError, if a password is available - and 'filepath' is not a valid key file encrypted using that password. - - securesystemslib.exceptions.CryptoError, if no password is available - and 'filepath' is not a valid non-encrypted key file. + Arguments: + filepath: The path to read the file from. + password (optional): A password to decrypt the key. + scheme (optional): The signing scheme assigned to the returned key object. + prompt (optional): A boolean indicating if the user should be prompted + for a decryption password. If the user enters an empty password, the + key is not decrypted. + storage_backend (optional): An object implementing StorageBackendInterface. + If not passed a default FilesystemBackend will be used. - - The contents of 'filepath' are read, optionally decrypted, and returned. + Raises: + UnsupportedLibraryError: pyca/cryptography is not available. + FormatError: Arguments are malformed. + ValueError: Both a 'password' is passed and 'prompt' is true. + StorageError: Key file cannot be read. + CryptoError: Key cannot be parsed. - - An RSA key object, conformant to 'securesystemslib.formats.RSAKEY_SCHEMA'. + Returns: + An RSA private key object conformant with 'RSAKEY_SCHEMA'. """ - - # Does 'filepath' have the correct format? - # Ensure the arguments have the appropriate number of objects and object - # types, and that all dict keys are properly named. - # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. securesystemslib.formats.PATH_SCHEMA.check_match(filepath) - - # Is 'scheme' properly formatted? securesystemslib.formats.RSA_SCHEME_SCHEMA.check_match(scheme) + password = _get_key_file_decryption_password(password, prompt, filepath) if storage_backend is None: storage_backend = securesystemslib.storage.FilesystemBackend() - # Read the contents of 'filepath' that should be a PEM formatted private key. with storage_backend.get(filepath) as file_object: pem_key = file_object.read().decode('utf-8') - # Convert 'pem_key' to 'securesystemslib.formats.RSAKEY_SCHEMA' format. - # Raise 'securesystemslib.exceptions.CryptoError' if 'pem_key' is invalid. - # If 'password' is None decryption will be omitted. - rsa_key = securesystemslib.keys.import_rsakey_from_private_pem(pem_key, - scheme, password) + # Optionally decrypt and convert PEM-encoded key to 'RSAKEY_SCHEMA' format + rsa_key = securesystemslib.keys.import_rsakey_from_private_pem( + pem_key, scheme, password) return rsa_key @@ -377,56 +312,36 @@ def import_rsa_privatekey_from_file(filepath, password=None, def import_rsa_publickey_from_file(filepath, scheme='rsassa-pss-sha256', storage_backend=None): - """ - - Import the RSA key stored in 'filepath'. The key object returned is in the - format 'securesystemslib.formats.RSAKEY_SCHEMA'. If the RSA PEM in - 'filepath' contains a private key, it is discarded. + """Imports PEM-encoded RSA public key from file storage. - - filepath: - .pub file, an RSA PEM file. + The expected key format is X.509 SubjectPublicKeyInfo. - scheme: - The signature scheme used by the imported key. - - storage_backend: - An object which implements - securesystemslib.storage.StorageBackendInterface. When no object is - passed a FilesystemBackend will be instantiated and used. + Arguments: + filepath: The path to read the file from. + scheme (optional): The signing scheme assigned to the returned key object. + storage_backend (optional): An object implementing StorageBackendInterface. + If not passed a default FilesystemBackend will be used. - - securesystemslib.exceptions.FormatError, if 'filepath' is improperly - formatted. + Raises: + UnsupportedLibraryError: pyca/cryptography is not available. + FormatError: Arguments are malformed. + StorageError: Key file cannot be read. + Error: Public key is malformed. - securesystemslib.exceptions.Error, if a valid RSA key object cannot be - generated. This may be caused by an improperly formatted PEM file. + Returns + An RSA public key object conformant with 'RSAKEY_SCHEMA'. - - 'filepath' is read and its contents extracted. - - - An RSA key object conformant to 'securesystemslib.formats.RSAKEY_SCHEMA'. """ - - # Does 'filepath' have the correct format? - # Ensure the arguments have the appropriate number of objects and object - # types, and that all dict keys are properly named. - # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. securesystemslib.formats.PATH_SCHEMA.check_match(filepath) - - # Is 'scheme' properly formatted? securesystemslib.formats.RSA_SCHEME_SCHEMA.check_match(scheme) if storage_backend is None: storage_backend = securesystemslib.storage.FilesystemBackend() - # Read the contents of the key file that should be in PEM format and contains - # the public portion of the RSA key. with storage_backend.get(filepath) as file_object: rsa_pubkey_pem = file_object.read().decode('utf-8') - # Convert 'rsa_pubkey_pem' to 'securesystemslib.formats.RSAKEY_SCHEMA' format. + # Convert PEM-encoded key to 'RSAKEY_SCHEMA' format try: rsakey_dict = securesystemslib.keys.import_rsakey_from_public_pem( rsa_pubkey_pem, scheme) @@ -443,100 +358,68 @@ def import_rsa_publickey_from_file(filepath, scheme='rsassa-pss-sha256', def generate_and_write_ed25519_keypair(filepath=None, password=None, prompt=False): - """ - - Generate an Ed25519 keypair, where the encrypted key (using 'password' as - the passphrase) is saved to <'filepath'>. The public key portion of the - generated Ed25519 key is saved to <'filepath'>.pub. If the filepath is not - given, the KEYID is used as the filename and the keypair saved to the - current working directory. + """Generates ed25519 key pair and writes custom JSON-formatted keys to disk. - The private key is encrypted according to 'cryptography's approach: - "Encrypt using the best available encryption for a given key's backend. - This is a curated encryption choice and the algorithm may change over - time." + If a password is passed or entered on the prompt, the private key is + encrypted using AES-256 in CTR mode, with the password strengthened in + PBKDF2-HMAC-SHA256. - - filepath: - The public and private key files are saved to .pub and - , respectively. If the filepath is not given, the public and - private keys are saved to the current working directory as .pub - and . KEYID is the generated key's KEYID. - - password: - The password, or passphrase, to encrypt the private portion of the - generated Ed25519 key. A symmetric encryption key is derived from - 'password', so it is not directly used. + Arguments: + filepath (optional): The path to write the private key to. If not passed, + the key is written to CWD using the keyid as filename. The public key + is written to the same path as the private key using the suffix '.pub'. + password (optional): An encryption password. + prompt (optional): A boolean indicating if the user should be prompted + for an encryption password. If the user enters an empty password, the + key is not encrypted. - - securesystemslib.exceptions.FormatError, if the arguments are improperly - formatted. + Raises: + UnsupportedLibraryError: pyca/pynacl or pyca/cryptography is not available. + FormatError: Arguments are malformed. + ValueError: An empty string is passed as 'password', or both a 'password' + is passed and 'prompt' is true. + StorageError: Key files cannot be written. - securesystemslib.exceptions.CryptoError, if 'filepath' cannot be encrypted. + Side Effects: + Writes key files to disk. - - Writes key files to '' and '.pub'. + Returns: + The private key filepath. - - The 'filepath' of the written key. """ - password = _get_key_file_encryption_password(password, prompt, filepath) ed25519_key = securesystemslib.keys.generate_ed25519_key() + # Use passed 'filepath' or keyid as file name if not filepath: filepath = os.path.join(os.getcwd(), ed25519_key['keyid']) - else: - logger.debug('The filepath has been specified. Not using the key\'s' - ' KEYID as the default filepath.') - - # Does 'filepath' have the correct format? - # Ensure the arguments have the appropriate number of objects and object - # types, and that all dict keys are properly named. - # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. securesystemslib.formats.PATH_SCHEMA.check_match(filepath) - # If the parent directory of filepath does not exist, - # create it (and all its parent directories, if necessary). + # Create intermediate directories as required securesystemslib.util.ensure_parent_dir(filepath) - # Create a temporary file, write the contents of the public key, and move - # to final destination. - file_object = tempfile.TemporaryFile() - - # Generate the ed25519 public key file contents in metadata format (i.e., - # does not include the keyid portion). + # Use custom JSON format for ed25519 keys on-disk keytype = ed25519_key['keytype'] keyval = ed25519_key['keyval'] scheme = ed25519_key['scheme'] ed25519key_metadata_format = securesystemslib.keys.format_keyval_to_metadata( keytype, scheme, keyval, private=False) + # Write public key to .pub + file_object = tempfile.TemporaryFile() file_object.write(json.dumps(ed25519key_metadata_format).encode('utf-8')) - - # Write the public key (i.e., 'public', which is in PEM format) to - # '.pub'. (1) Create a temporary file, (2) write the contents of - # the public key, and (3) move to final destination. - # The temporary file is closed after the final move. securesystemslib.util.persist_temp_file(file_object, filepath + '.pub') - # Write the encrypted key string, conformant to - # 'securesystemslib.formats.ENCRYPTEDKEY_SCHEMA', to ''. - file_object = tempfile.TemporaryFile() - - # Encrypt the private key if 'password' is set. + # Encrypt private key if we have a password, store as JSON string otherwise if password is not None: ed25519_key = securesystemslib.keys.encrypt_key(ed25519_key, password) - else: - logger.debug('An empty password was given. ' - 'Not encrypting the private key.') ed25519_key = json.dumps(ed25519_key) - # Raise 'securesystemslib.exceptions.CryptoError' if 'ed25519_key' cannot be - # encrypted. + # Write private key to + file_object = tempfile.TemporaryFile() file_object.write(ed25519_key.encode('utf-8')) securesystemslib.util.persist_temp_file(file_object, filepath) @@ -546,46 +429,29 @@ def generate_and_write_ed25519_keypair(filepath=None, password=None, def import_ed25519_publickey_from_file(filepath): - """ - - Load the ED25519 public key object (conformant to - 'securesystemslib.formats.KEY_SCHEMA') stored in 'filepath'. Return - 'filepath' in securesystemslib.formats.ED25519KEY_SCHEMA format. + """Imports custom JSON-formatted ed25519 public key from disk. - If the key object in 'filepath' contains a private key, it is discarded. - - - filepath: - .pub file, a public key file. + Arguments: + filepath: The path to read the file from. - - securesystemslib.exceptions.FormatError, if 'filepath' is improperly - formatted or is an unexpected key type. + Raises: + FormatError: Argument is malformed. + StorageError: Key file cannot be read. + Error: Public key is malformed. - - The contents of 'filepath' is read and saved. + Returns: + An ed25519 public key object conformant with 'ED25519KEY_SCHEMA'. - - An ED25519 key object conformant to - 'securesystemslib.formats.ED25519KEY_SCHEMA'. """ - - # Does 'filepath' have the correct format? - # Ensure the arguments have the appropriate number of objects and object - # types, and that all dict keys are properly named. - # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. securesystemslib.formats.PATH_SCHEMA.check_match(filepath) - # ED25519 key objects are saved in json and metadata format. Return the - # loaded key object in securesystemslib.formats.ED25519KEY_SCHEMA' format that - # also includes the keyid. + # Load custom on-disk JSON formatted key and convert to its custom in-memory + # dict key representation ed25519_key_metadata = securesystemslib.util.load_json_file(filepath) - ed25519_key, junk = \ - securesystemslib.keys.format_metadata_to_key(ed25519_key_metadata) + ed25519_key, junk = securesystemslib.keys.format_metadata_to_key( + ed25519_key_metadata) - # Raise an exception if an unexpected key type is imported. Redundant - # validation of 'keytype'. 'securesystemslib.keys.format_metadata_to_key()' - # should have fully validated 'ed25519_key_metadata'. + # Check that the generic loading functions indeed loaded an ed25519 key if ed25519_key['keytype'] != 'ed25519': message = 'Invalid key type loaded: ' + repr(ed25519_key['keytype']) raise securesystemslib.exceptions.FormatError(message) @@ -596,67 +462,49 @@ def import_ed25519_publickey_from_file(filepath): + def import_ed25519_privatekey_from_file(filepath, password=None, prompt=False, storage_backend=None): - """ - - Import the encrypted ed25519 key file in 'filepath', decrypt it, and return - the key object in 'securesystemslib.formats.ED25519KEY_SCHEMA' format. - - The private key (may also contain the public part) is encrypted with AES - 256 and CTR the mode of operation. The password is strengthened with - PBKDF2-HMAC-SHA256. - - - filepath: - file, an RSA encrypted key file. + """Imports custom JSON-formatted ed25519 private key from file storage. - password: - The password, or passphrase, to import the private key (i.e., the - encrypted key file 'filepath' must be decrypted before the ed25519 key - object can be returned. + If a password is passed or entered on the prompt, the private key is + decrypted, otherwise it is treated as unencrypted. - prompt: - If True the user is prompted for a passphrase to decrypt 'filepath'. - Default is False. + Arguments: + filepath: The path to read the file from. + password (optional): A password to decrypt the key. + prompt (optional): A boolean indicating if the user should be prompted + for a decryption password. If the user enters an empty password, the + key is not decrypted. + storage_backend (optional): An object implementing StorageBackendInterface. + If not passed a default FilesystemBackend will be used. - storage_backend: - An object which implements - securesystemslib.storage.StorageBackendInterface. When no object is - passed a FilesystemBackend will be instantiated and used. - - securesystemslib.exceptions.FormatError, if the arguments are improperly - formatted or the imported key object contains an invalid key type (i.e., - not 'ed25519'). + Raises: + UnsupportedLibraryError: pyca/cryptography is not available. + FormatError: Arguments are malformed. + ValueError: Both a 'password' is passed and 'prompt' is true. + StorageError: Key file cannot be read. + Error, CryptoError: Key cannot be parsed. - securesystemslib.exceptions.CryptoError, if 'filepath' cannot be decrypted. - - 'password' is used to decrypt the 'filepath' key file. + Returns: + An ed25519 private key object conformant with 'ED25519KEY_SCHEMA'. - - An ed25519 key object of the form: - 'securesystemslib.formats.ED25519KEY_SCHEMA'. """ - - # Does 'filepath' have the correct format? - # Ensure the arguments have the appropriate number of objects and object - # types, and that all dict keys are properly named. - # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. securesystemslib.formats.PATH_SCHEMA.check_match(filepath) password = _get_key_file_decryption_password(password, prompt, filepath) if storage_backend is None: storage_backend = securesystemslib.storage.FilesystemBackend() - # Finally, regardless of password, try decrypting the key, if necessary. - # Otherwise, load it straight from storage. with storage_backend.get(filepath) as file_object: json_str = file_object.read() - return securesystemslib.keys.\ - import_ed25519key_from_private_json(json_str, password=password) + # Load custom on-disk JSON formatted key and convert to its custom + # in-memory dict key representation, decrypting it if password is not None + return securesystemslib.keys.import_ed25519key_from_private_json( + json_str, password=password) @@ -664,96 +512,68 @@ def import_ed25519_privatekey_from_file(filepath, password=None, prompt=False, def generate_and_write_ecdsa_keypair(filepath=None, password=None, prompt=False): - """ - - Generate an ECDSA keypair, where the encrypted key (using 'password' as the - passphrase) is saved to <'filepath'>. The public key portion of the - generated ECDSA key is saved to <'filepath'>.pub. If the filepath is not - given, the KEYID is used as the filename and the keypair saved to the - current working directory. - - The 'cryptography' library is currently supported. The private key is - encrypted according to 'cryptography's approach: "Encrypt using the best - available encryption for a given key's backend. This is a curated - encryption choice and the algorithm may change over time." + """Generates ecdsa key pair and writes custom JSON-formatted keys to disk. - - filepath: - The public and private key files are saved to .pub and - , respectively. If the filepath is not given, the public and - private keys are saved to the current working directory as .pub - and . KEYID is the generated key's KEYID. + If a password is passed or entered on the prompt, the private key is + encrypted using AES-256 in CTR mode, with the password strengthened in + PBKDF2-HMAC-SHA256. - password: - The password, or passphrase, to encrypt the private portion of the - generated ECDSA key. A symmetric encryption key is derived from - 'password', so it is not directly used. + Arguments: + filepath (optional): The path to write the private key to. If not passed, + the key is written to CWD using the keyid as filename. The public key + is written to the same path as the private key using the suffix '.pub'. + password (optional): An encryption password. + prompt (optional): A boolean indicating if the user should be prompted + for an encryption password. If the user enters an empty password, the + key is not encrypted. - - securesystemslib.exceptions.FormatError, if the arguments are improperly - formatted. + Raises: + UnsupportedLibraryError: pyca/pynacl or pyca/cryptography is not available. + FormatError: Arguments are malformed. + ValueError: An empty string is passed as 'password', or both a 'password' + is passed and 'prompt' is true. + StorageError: Key files cannot be written. - securesystemslib.exceptions.CryptoError, if 'filepath' cannot be encrypted. + Side Effects: + Writes key files to disk. - - Writes key files to '' and '.pub'. + Returns: + The private key filepath. - - The 'filepath' of the written key. """ - password = _get_key_file_encryption_password(password, prompt, filepath) - # Generate a new ECDSA key object. The 'cryptography' library is currently - # supported and performs the actual cryptographic operations. ecdsa_key = securesystemslib.keys.generate_ecdsa_key() + # Use passed 'filepath' or keyid as file name if not filepath: filepath = os.path.join(os.getcwd(), ecdsa_key['keyid']) - else: - logger.debug('The filepath has been specified. Not using the key\'s' - ' KEYID as the default filepath.') - - # Does 'filepath' have the correct format? - # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. securesystemslib.formats.PATH_SCHEMA.check_match(filepath) - # If the parent directory of filepath does not exist, - # create it (and all its parent directories, if necessary). + # Create intermediate directories as required securesystemslib.util.ensure_parent_dir(filepath) - # Create a temporary file, write the contents of the public key, and move - # to final destination. - file_object = tempfile.TemporaryFile() - - # Generate the ECDSA public key file contents in metadata format (i.e., does - # not include the keyid portion). + # Use custom JSON format for ecdsa keys on-disk keytype = ecdsa_key['keytype'] keyval = ecdsa_key['keyval'] scheme = ecdsa_key['scheme'] ecdsakey_metadata_format = securesystemslib.keys.format_keyval_to_metadata( keytype, scheme, keyval, private=False) + # Write public key to .pub + file_object = tempfile.TemporaryFile() file_object.write(json.dumps(ecdsakey_metadata_format).encode('utf-8')) - - # Write the public key (i.e., 'public', which is in PEM format) to - # '.pub'. (1) Create a temporary file, (2) write the contents of - # the public key, and (3) move to final destination. securesystemslib.util.persist_temp_file(file_object, filepath + '.pub') - # Write the encrypted key string, conformant to - # 'securesystemslib.formats.ENCRYPTEDKEY_SCHEMA', to ''. - file_object = tempfile.TemporaryFile() - + # Encrypt private key if we have a password, store as JSON string otherwise if password is not None: ecdsa_key = securesystemslib.keys.encrypt_key(ecdsa_key, password) - else: ecdsa_key = json.dumps(ecdsa_key) - # Raise 'securesystemslib.exceptions.CryptoError' if 'ecdsa_key' cannot be - # encrypted. + # Write private key to + file_object = tempfile.TemporaryFile() file_object.write(ecdsa_key.encode('utf-8')) securesystemslib.util.persist_temp_file(file_object, filepath) @@ -763,42 +583,27 @@ def generate_and_write_ecdsa_keypair(filepath=None, password=None, def import_ecdsa_publickey_from_file(filepath): - """ - - Load the ECDSA public key object (conformant to - 'securesystemslib.formats.KEY_SCHEMA') stored in 'filepath'. Return - 'filepath' in securesystemslib.formats.ECDSAKEY_SCHEMA format. + """Imports custom JSON-formatted ecdsa public key from disk. - If the key object in 'filepath' contains a private key, it is discarded. - - - filepath: - .pub file, a public key file. + Arguments: + filepath: The path to read the file from. - - securesystemslib.exceptions.FormatError, if 'filepath' is improperly - formatted or is an unexpected key type. + Raises: + FormatError: Argument is malformed. + StorageError: Key file cannot be read. + Error: Public key is malformed. - - The contents of 'filepath' is read and saved. + Returns: + An ecdsa public key object conformant with 'ECDSAKEY_SCHEMA'. - - An ECDSA key object conformant to - 'securesystemslib.formats.ECDSAKEY_SCHEMA'. """ - - # Does 'filepath' have the correct format? - # Ensure the arguments have the appropriate number of objects and object - # types, and that all dict keys are properly named. - # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. securesystemslib.formats.PATH_SCHEMA.check_match(filepath) - # ECDSA key objects are saved in json and metadata format. Return the - # loaded key object in securesystemslib.formats.ECDSAKEY_SCHEMA' format that - # also includes the keyid. + # Load custom on-disk JSON formatted key and convert to its custom in-memory + # dict key representation ecdsa_key_metadata = securesystemslib.util.load_json_file(filepath) - ecdsa_key, junk = \ - securesystemslib.keys.format_metadata_to_key(ecdsa_key_metadata) + ecdsa_key, junk = securesystemslib.keys.format_metadata_to_key( + ecdsa_key_metadata) return ecdsa_key @@ -808,46 +613,31 @@ def import_ecdsa_publickey_from_file(filepath): def import_ecdsa_privatekey_from_file(filepath, password=None, prompt=False, storage_backend=None): - """ - - Import the encrypted ECDSA key file in 'filepath', decrypt it, and return - the key object in 'securesystemslib.formats.ECDSAKEY_SCHEMA' format. - - The 'cryptography' library is currently supported and performs the actual - cryptographic routine. - - - filepath: - file, an ECDSA encrypted key file. - - password: - The password, or passphrase, to import the private key (i.e., the - encrypted key file 'filepath' must be decrypted before the ECDSA key - object can be returned. + """Imports custom JSON-formatted ecdsa private key from file storage. - storage_backend: - An object which implements - securesystemslib.storage.StorageBackendInterface. When no object is - passed a FilesystemBackend will be instantiated and used. + If a password is passed or entered on the prompt, the private key is + decrypted, otherwise it is treated as unencrypted. - - securesystemslib.exceptions.FormatError, if the arguments are improperly - formatted or the imported key object contains an invalid key type (i.e., - not 'ecdsa'). + Arguments: + filepath: The path to read the file from. + password (optional): A password to decrypt the key. + prompt (optional): A boolean indicating if the user should be prompted + for a decryption password. If the user enters an empty password, the + key is not decrypted. + storage_backend (optional): An object implementing StorageBackendInterface. + If not passed a default FilesystemBackend will be used. - securesystemslib.exceptions.CryptoError, if 'filepath' cannot be decrypted. + Raises: + UnsupportedLibraryError: pyca/cryptography is not available. + FormatError: Arguments are malformed. + ValueError: Both a 'password' is passed and 'prompt' is true. + StorageError: Key file cannot be read. + Error, CryptoError: Key cannot be parsed. - - 'password' is used to decrypt the 'filepath' key file. + Returns: + An ecdsa private key object conformant with 'ED25519KEY_SCHEMA'. - - An ECDSA key object of the form: 'securesystemslib.formats.ECDSAKEY_SCHEMA'. """ - - # Does 'filepath' have the correct format? - # Ensure the arguments have the appropriate number of objects and object - # types, and that all dict keys are properly named. - # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. securesystemslib.formats.PATH_SCHEMA.check_match(filepath) password = _get_key_file_decryption_password(password, prompt, filepath) @@ -855,21 +645,15 @@ def import_ecdsa_privatekey_from_file(filepath, password=None, prompt=False, if storage_backend is None: storage_backend = securesystemslib.storage.FilesystemBackend() - # Store the encrypted contents of 'filepath' prior to calling the decryption - # routine. with storage_backend.get(filepath) as file_object: key_data = file_object.read().decode('utf-8') - # Decrypt the loaded key file, calling the 'cryptography' library to generate - # the derived encryption key from 'password'. Raise - # 'securesystemslib.exceptions.CryptoError' if the decryption fails. + # Decrypt private key if we have a password, directly load JSON otherwise if password is not None: key_object = securesystemslib.keys.decrypt_key(key_data, password) - else: key_object = securesystemslib.util.load_json_string(key_data) - # Raise an exception if an unexpected key type is imported. # NOTE: we support keytype's of ecdsa-sha2-nistp256 and ecdsa-sha2-nistp384 # in order to support key files generated with older versions of @@ -893,14 +677,14 @@ def import_publickeys_from_file(filepaths, key_types=None): """Imports multiple public keys from files. NOTE: Use 'import_rsa_publickey_from_file' to specify any other than the - default signing schemes for an RSA key. + default signing scheme for an RSA key. Arguments: filepaths: A list of paths to public key files. key_types (optional): A list of types of keys to be imported associated - with filepaths by index. Must be one of KEY_TYPE_RSA, KEY_TYPE_ED25519 or - KEY_TYPE_ECDSA. If not specified, all keys are assumed to be - KEY_TYPE_RSA. + with filepaths by index. Must be one of KEY_TYPE_RSA, KEY_TYPE_ED25519 + or KEY_TYPE_ECDSA. If not specified, all keys are assumed to be + KEY_TYPE_RSA. Raises: TypeError: filepaths or key_types (if passed) is not iterable.