Skip to content

Commit

Permalink
Add generic import_privatekey_from_file function
Browse files Browse the repository at this point in the history
Add convenience dispatcher for other private key import interface
functions, to import any of the supported private key types from
file (rsa, ecdsa, ed25519).

This transfers a similar function, currently implemented in
in-toto.util, in order to close in-toto/in-toto#80.

Caveat:
- The key type must be specified via argument (or defaults to RSA).
  In the future we might want to let the parser infer the
  key type, as we do in the related in-toto-golang implementation. See
  https://github.com/in-toto/in-toto-golang/blob/5fba7c22a062a30b6e373f33362d647eabf15822/in_toto/keylib.go#L281-L361

- Currently, the function does not support a signing scheme
  parameter and thus assigns the default value from
  import_rsa_privatekey_from_file to the returned key. For the
  other keep types, the scheme is encoded in the on-disk format.
  In the future we might want to consolidate this as part of secure-systems-lab#251.
  For now the primary goal is to have a simple interface that is
  enough to close in-toto/in-toto#80.
  • Loading branch information
lukpueh committed Oct 16, 2020
1 parent 7d37d55 commit 698f54f
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 1 deletion.
55 changes: 55 additions & 0 deletions securesystemslib/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,61 @@ def import_publickeys_from_file(filepaths, key_types=None):
return key_dict



def import_privatekey_from_file(filepath, key_type=None, password=None,
prompt=False):
"""Imports private key from file.
If a password is passed or entered on the prompt, the private key is
decrypted, otherwise it is treated as unencrypted.
NOTE: The default signing scheme 'rsassa-pss-sha256' is assigned to RSA keys.
Use 'import_rsa_publickey_from_file' to specify any other than the default
signing scheme for an RSA key. ed25519 and ecdsa keys have the signing scheme
included in the custom key format (see generate functions).
Arguments:
filepath: The path to read the file from.
key_type (optional): One of KEY_TYPE_RSA, KEY_TYPE_ED25519 or
KEY_TYPE_ECDSA. Default is KEY_TYPE_RSA.
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.
Raises:
FormatError: Arguments are malformed or 'key_type' is not supported.
ValueError: Both a 'password' is passed and 'prompt' is true.
UnsupportedLibraryError: pyca/cryptography is not available.
StorageError: Key file cannot be read.
Error, CryptoError: Key cannot be parsed.
Returns:
A private key object conformant with one of 'ED25519KEY_SCHEMA',
'RSAKEY_SCHEMA' or 'ECDSAKEY_SCHEMA'.
"""
if key_type is None:
key_type = KEY_TYPE_RSA

if key_type == KEY_TYPE_ED25519:
return import_ed25519_privatekey_from_file(
filepath, password=password, prompt=prompt)

elif key_type == KEY_TYPE_RSA:
return import_rsa_privatekey_from_file(
filepath, password=password, prompt=prompt)

elif key_type == KEY_TYPE_ECDSA:
return import_ecdsa_privatekey_from_file(
filepath, password=password, prompt=prompt)

else:
raise securesystemslib.exceptions.FormatError(
"Unsupported key type '{}'. Must be '{}', '{}' or '{}'.".format(
key_type, KEY_TYPE_RSA, KEY_TYPE_ED25519, KEY_TYPE_ECDSA))


if __name__ == '__main__':
# The interactive sessions of the documentation strings can
# be tested by running interface.py as a standalone module:
Expand Down
34 changes: 33 additions & 1 deletion tests/test_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@
generate_and_write_ecdsa_keypair,
import_ecdsa_publickey_from_file,
import_ecdsa_privatekey_from_file,
import_publickeys_from_file)
import_publickeys_from_file,
import_privatekey_from_file)



Expand Down Expand Up @@ -667,6 +668,37 @@ def test_import_publickeys_from_file(self):
[KEY_TYPE_ED25519])


def test_import_privatekey_from_file(self):
"""Test generic private key import function. """

pw = "password"
for idx, (path, key_type, key_schema) in enumerate([
(self.path_rsa, None, RSAKEY_SCHEMA), # default key type
(self.path_rsa, KEY_TYPE_RSA, RSAKEY_SCHEMA),
(self.path_ed25519, KEY_TYPE_ED25519, ED25519KEY_SCHEMA),
(self.path_ecdsa, KEY_TYPE_ECDSA, ECDSAKEY_SCHEMA)]):

# Successfully import key per supported type, with ...
# ... passed password
key = import_privatekey_from_file(path, key_type=key_type, password=pw)
self.assertTrue(key_schema.matches(key), "(row {})".format(idx))

# ... entered password on mock-prompt
with mock.patch("securesystemslib.interface.get_password", return_value=pw):
key = import_privatekey_from_file(path, key_type=key_type, prompt=True)
self.assertTrue(key_schema.matches(key), "(row {})".format(idx))

# Error on wrong key for default key type
with self.assertRaises(Error):
import_privatekey_from_file(self.path_ed25519, password=pw)

# Error on unsupported key type
with self.assertRaises(FormatError):
import_privatekey_from_file(
self.path_rsa, key_type="KEY_TYPE_UNSUPPORTED", password=pw)



# Run the test cases.
if __name__ == '__main__':
unittest.main()

0 comments on commit 698f54f

Please sign in to comment.