From bee81d3cb739caaed4b2b1f830c5a5861d5512f1 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Wed, 14 Oct 2020 12:56:31 +0200 Subject: [PATCH] Add generic import_privatekey_from_file function 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 #251. For now the primary goal is to have a simple interface that is enough to close in-toto/in-toto#80. --- securesystemslib/interface.py | 51 +++++++++++++++++++++++++++++++++++ tests/test_interface.py | 34 ++++++++++++++++++++++- 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/securesystemslib/interface.py b/securesystemslib/interface.py index 8796603e9..dee9c8593 100755 --- a/securesystemslib/interface.py +++ b/securesystemslib/interface.py @@ -727,6 +727,57 @@ 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: Use 'import_rsa_privatekey_from_file' to specify any other than the + default signing scheme for an RSA key. + + 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: key_type is not supported. + See import_ed25519_privatekey_from_file, import_rsa_privatekey_from_file, + import_ecdsa_privatekey_from_file. + + 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: diff --git a/tests/test_interface.py b/tests/test_interface.py index ea7d9d819..49adfd6fa 100755 --- a/tests/test_interface.py +++ b/tests/test_interface.py @@ -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) @@ -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()