Skip to content

Commit

Permalink
Add 'prompt' arg to import rsa privatekey function
Browse files Browse the repository at this point in the history
Add an optional boolean 'prompt' argument to
interface.import_rsa_privatekey_from_file and change the behavior
of the function like so:

If password is passed use passed password for decryption.
If prompt is True use entered password for decryption.
If no password is passed or entered, or if the entered password
is an empty string, omit decryption.
Passing and prompting for a passowrd is not possible.

See code comments or secure-systems-lab#122 for
more details.

This commit also adopts the unit tests accordingly.
  • Loading branch information
lukpueh committed Mar 8, 2018
1 parent 9f6424b commit c5c9d73
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 31 deletions.
92 changes: 66 additions & 26 deletions securesystemslib/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,11 +237,20 @@ 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'):
scheme='rsassa-pss-sha256', prompt=False):
"""
<Purpose>
Import the encrypted PEM file in 'filepath', decrypt it, and return the key
object in 'securesystemslib.formats.RSAKEY_SCHEMA' format.
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 or entered, or if the entered password is an empty
string, omit decryption.
Passing and prompting for a passowrd is not possible.
The returned key is an object in the
'securesystemslib.formats.RSAKEY_SCHEMA' format.
<Arguments>
filepath:
Expand All @@ -254,18 +263,35 @@ def import_rsa_privatekey_from_file(filepath, password=None,
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.
<Exceptions>
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.CryptoError, if 'filepath' is not a valid
encrypted key file.
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.
<Side Effects>
The contents of 'filepath' is read, decrypted, and the key stored.
The contents of 'filepath' are read, optionally decrypted, and returned.
<Returns>
An RSA key object, conformant to 'securesystemslib.formats.RSAKEY_SCHEMA'.
"""

# Does 'filepath' have the correct format?
Expand All @@ -277,37 +303,51 @@ def import_rsa_privatekey_from_file(filepath, password=None,
# Is 'scheme' properly formatted?
securesystemslib.formats.RSA_SCHEME_SCHEMA.check_match(scheme)

# 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
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 charcters')

elif prompt: # pragma: no cover
# 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.
password = get_password('Enter a password for the encrypted RSA'
' file (' + Fore.RED + filepath + Fore.RESET + '): ',
confirm=False)
# 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 \'' + Fore.RED + filepath + Fore.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)

# Does 'password' have the correct format?
securesystemslib.formats.PASSWORD_SCHEMA.check_match(password)
else:
logger.debug('No password was given. Attempting to import an'
' unencrypted file.')

# Read the contents of 'filepath' that should be an encrypted PEM.
# Read the contents of 'filepath' that should be a PEM formatted private key.
with open(filepath, 'rb') as file_object:
pem = file_object.read().decode('utf-8')
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 len(password):
rsa_key = securesystemslib.keys.import_rsakey_from_private_pem(pem,
scheme, password)

else:
logger.debug('An empty password was given. Attempting to import an'
' unencrypted file.')
rsa_key = securesystemslib.keys.import_rsakey_from_private_pem(pem,
scheme, password=None)
# If 'password' is None decryption will be omitted.
rsa_key = securesystemslib.keys.import_rsakey_from_private_pem(pem_key,
scheme, password)

return rsa_key

Expand Down
21 changes: 16 additions & 5 deletions tests/test_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,8 @@ def test_generate_and_write_rsa_keypair(self):
'pw')
self.assertTrue(securesystemslib.formats.RSAKEY_SCHEMA.matches(imported_privkey))

# Try to import the unencrypted key file.
imported_privkey = interface.import_rsa_privatekey_from_file(test_keypath_unencrypted,
'')
# Try to import the unencrypted key file, by not passing a password
interface.import_rsa_privatekey_from_file(test_keypath_unencrypted)
self.assertTrue(securesystemslib.formats.RSAKEY_SCHEMA.matches(imported_privkey))

# Custom 'bits' argument.
Expand Down Expand Up @@ -167,7 +166,7 @@ def test_import_rsa_privatekey_from_file(self):
# Load one of the pre-generated key files from
# 'securesystemslib/tests/repository_data'. 'password' unlocks the
# pre-generated key files.
key_filepath = os.path.join(os.path.dirname(os.path.realpath(__file__)),
key_filepath = os.path.join(os.path.dirname(os.path.realpath(__file__)),
'data', 'keystore', 'rsa_key')
self.assertTrue(os.path.exists(key_filepath))

Expand All @@ -176,10 +175,22 @@ def test_import_rsa_privatekey_from_file(self):
self.assertTrue(securesystemslib.formats.RSAKEY_SCHEMA.matches(imported_rsa_key))


# Test improperly formatted argument.
# Test improperly formatted 'filepath' argument.
self.assertRaises(securesystemslib.exceptions.FormatError,
interface.import_rsa_privatekey_from_file, 3, 'pw')

# Test improperly formatted 'password' argument.
with self.assertRaises(securesystemslib.exceptions.FormatError):
interface.import_rsa_privatekey_from_file(key_filepath, 123)

# Test unallowed empty 'password'
with self.assertRaises(ValueError):
interface.import_rsa_privatekey_from_file(key_filepath, '')

# Test unallowed passing 'prompt' and 'password'
with self.assertRaises(ValueError):
interface.import_rsa_privatekey_from_file(key_filepath,
password='pw', prompt=True)

# Test invalid argument.
# Non-existent key file.
Expand Down

0 comments on commit c5c9d73

Please sign in to comment.