diff --git a/securesystemslib/interface.py b/securesystemslib/interface.py index e3869288..f2bd6000 100755 --- a/securesystemslib/interface.py +++ b/securesystemslib/interface.py @@ -123,7 +123,7 @@ def get_password(prompt='Password: ', confirm=False): def generate_and_write_rsa_keypair(filepath=None, bits=DEFAULT_RSA_KEY_BITS, - password=None): + password=None, prompt=True): """ Generate an RSA key pair. The public portion of the generated RSA key is @@ -287,51 +287,9 @@ def import_rsa_privatekey_from_file(filepath, password=None, 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 password and prompt: - raise ValueError("Passing 'password' and 'prompt' True is not allowed.") - - # If 'password' was passed check format and that it is not empty. - if password is not None: - securesystemslib.formats.PASSWORD_SCHEMA.check_match(password) - - # TODO: PASSWORD_SCHEMA should be securesystemslib.schema.AnyString(min=1) - if not len(password): - raise ValueError('Password must be 1 or more characters') - - elif prompt: - # Password confirmation disabled here, which should ideally happen only - # when creating encrypted key files (i.e., improve usability). - # It is safe to specify the full path of 'filepath' in the prompt and not - # worry about leaking sensitive information about the key's location. - # However, care should be taken when including the full path in exceptions - # and log files. - # NOTE: A user who gets prompted for a password, can only signal that the - # key is not encrypted by entering no password in the prompt, as opposed - # to a programmer who can call the function with or without a 'password'. - # Hence, we treat an empty password here, as if no 'password' was passed. - password = get_password('Enter a password for an encrypted RSA' - ' file \'' + TERM_RED + filepath + TERM_RESET + '\': ', - confirm=False) or None - - if password is not None: - # This check will not fail, because a mal-formatted passed password fails - # above and an entered password will always be a string (see get_password) - # However, we include it in case PASSWORD_SCHEMA or get_password changes. - securesystemslib.formats.PASSWORD_SCHEMA.check_match(password) - - else: - logger.debug('No password was given. Attempting to import an' - ' unencrypted file.') + password = _get_decryption_password(password, prompt, filepath) # Read the contents of 'filepath' that should be a PEM formatted private key. with open(filepath, 'rb') as file_object: @@ -612,51 +570,17 @@ def import_ed25519_privatekey_from_file(filepath, password=None, prompt=False): 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) - - if password and prompt: - raise ValueError("Passing 'password' and 'prompt' True is not allowed.") - - # If 'password' was passed check format and that it is not empty. - if password is not None: - securesystemslib.formats.PASSWORD_SCHEMA.check_match(password) - - # TODO: PASSWORD_SCHEMA should be securesystemslib.schema.AnyString(min=1) - if not len(password): - raise ValueError('Password must be 1 or more characters') - - elif prompt: - # Password confirmation disabled here, which should ideally happen only - # when creating encrypted key files (i.e., improve usability). - # It is safe to specify the full path of 'filepath' in the prompt and not - # worry about leaking sensitive information about the key's location. - # However, care should be taken when including the full path in exceptions - # and log files. - # NOTE: A user who gets prompted for a password, can only signal that the - # key is not encrypted by entering no password in the prompt, as opposed - # to a programmer who can call the function with or without a 'password'. - # Hence, we treat an empty password here, as if no 'password' was passed. - password = get_password('Enter a password for an encrypted RSA' - ' file \'' + TERM_RED + filepath + TERM_RESET + '\': ', - confirm=False) - - # If user sets an empty string for the password, explicitly set the - # password to None, because some functions may expect this later. - if len(password) == 0: # pragma: no cover - password = None + password = _get_decryption_password(password, prompt, filepath) # Finally, regardless of password, try decrypting the key, if necessary. # Otherwise, load it straight from the disk. with open(filepath, 'rb') as file_object: json_str = file_object.read() - return securesystemslib.keys.\ - import_ed25519key_from_private_json(json_str, password=password) + return securesystemslib.keys.import_ed25519key_from_private_json( + json_str, password=password) @@ -819,9 +743,7 @@ def import_ecdsa_publickey_from_file(filepath): - - -def import_ecdsa_privatekey_from_file(filepath, password=None): +def import_ecdsa_privatekey_from_file(filepath, password=None, prompt=False): """ Import the encrypted ECDSA key file in 'filepath', decrypt it, and return @@ -839,6 +761,10 @@ def import_ecdsa_privatekey_from_file(filepath, password=None): encrypted key file 'filepath' must be decrypted before the ECDSA key object can be returned. + prompt: + If True the user is prompted for a passphrase to decrypt 'filepath'. + Default is False. + securesystemslib.exceptions.FormatError, if the arguments are improperly formatted or the imported key object contains an invalid key type (i.e., @@ -851,29 +777,10 @@ def import_ecdsa_privatekey_from_file(filepath, password=None): 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) - - # If the caller does not provide a password argument, prompt for one. - # Password confirmation disabled here, which should ideally happen only - # when creating encrypted key files (i.e., improve usability). - if password is None: # pragma: no cover - - # It is safe to specify the full path of 'filepath' in the prompt and not - # worry about leaking sensitive information about the key's location. - # However, care should be taken when including the full path in exceptions - # and log files. - password = get_password('Enter a password for the encrypted ECDSA' - ' key (' + TERM_RED + filepath + TERM_RESET + '): ', - confirm=False) - - # Does 'password' have the correct format? - securesystemslib.formats.PASSWORD_SCHEMA.check_match(password) + _get_decryption_password(password, prompt, filepath) # Store the encrypted contents of 'filepath' prior to calling the decryption # routine. @@ -901,6 +808,37 @@ def import_ecdsa_privatekey_from_file(filepath, password=None): return key_object +def _get_encryption_password(password, pompt, path): + pass + + +def _get_decryption_password(password, prompt, path): + """Helper to prompt for and/or check a password. """ + + if password and prompt: + raise ValueError("passing 'password' and True for 'prompt' is not allowed") + + if prompt: + password = get_password("enter password to decrypt private key file " + "'" + TERM_RED + path + TERM_RESET + "' " + "(leave empty if key not encrypted): '", confirm=False) + + if len(password) == 0: + # A user who gets prompted for a password can only indicate that the key + # is not encrypted by entering no password in the prompt. + # To subsequently skip decryption we set the password to None. + password = None + + # Do some vetting if a password was passed or entered by the user + if password is not None: + securesystemslib.formats.PASSWORD_SCHEMA.check_match(password) + + if not len(password): + # TODO: It would be nice if PASSWORD_SCHEMA could do this + raise ValueError("'password' must be 1 or more characters long") + + return password + if __name__ == '__main__': # The interactive sessions of the documentation strings can