-
Notifications
You must be signed in to change notification settings - Fork 50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Consolidate key interface password handling and general overhaul #288
Changes from 1 commit
9ee6998
a8508ee
bc62907
7d00ecb
2379b7f
7d37d55
698f54f
1db7f21
fae4ebc
dd2c5fc
edcf74e
1cb5bcd
9a74f30
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -122,6 +122,38 @@ def get_password(prompt='Password: ', confirm=False): | |
|
||
|
||
|
||
def _get_key_file_decryption_password(password, prompt, path): | ||
"""Decryption password helper. | ||
|
||
- Fail if 'password' is passed and 'prompt' is True (precedence unclear) | ||
- Return None on empty pw on prompt (suggests desire to not decrypt) | ||
|
||
""" | ||
securesystemslib.formats.BOOLEAN_SCHEMA.check_match(prompt) | ||
|
||
# We don't want to decide which takes precedence so we fail | ||
if password is not None and prompt: | ||
raise ValueError("passing 'password' and 'prompt=True' is not allowed") | ||
|
||
# Prompt user for password | ||
if prompt: | ||
password = get_password("enter password to decrypt private key file " | ||
"'" + TERM_RED + str(path) + TERM_RESET + "' " | ||
"(leave empty if key not encrypted): '", confirm=False) | ||
|
||
# Treat empty password as no password. A user on the prompt can only | ||
# indicate the desire to not decrypt by entering no password. | ||
if not len(password): | ||
return None | ||
|
||
if password is not None: | ||
securesystemslib.formats.PASSWORD_SCHEMA.check_match(password) | ||
# No additional vetting needed. Decryption will show if it was correct. | ||
|
||
return password | ||
|
||
|
||
|
||
def generate_and_write_rsa_keypair(filepath=None, bits=DEFAULT_RSA_KEY_BITS, | ||
password=None): | ||
""" | ||
|
@@ -302,42 +334,7 @@ def import_rsa_privatekey_from_file(filepath, password=None, | |
|
||
# 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_key_file_decryption_password(password, prompt, filepath) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This call would fail if the user does not set either the password or prompt, correct? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, it will return None. Because callers may use See |
||
|
||
if storage_backend is None: | ||
storage_backend = securesystemslib.storage.FilesystemBackend() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Off-topic, but: now that I see how we keep using this, we should make a singleton out of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea! Mind submitting a feature request? |
||
|
@@ -643,41 +640,11 @@ def import_ed25519_privatekey_from_file(filepath, password=None, prompt=False, | |
# 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.") | ||
password = _get_key_file_decryption_password(password, prompt, filepath) | ||
|
||
if storage_backend is None: | ||
storage_backend = securesystemslib.storage.FilesystemBackend() | ||
|
||
# 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: | ||
password = None | ||
|
||
# Finally, regardless of password, try decrypting the key, if necessary. | ||
# Otherwise, load it straight from storage. | ||
with storage_backend.get(filepath) as file_object: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we allow empty string decryption passwords here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly.