diff --git a/securesystemslib/interface.py b/securesystemslib/interface.py index 8796603e..dee9c859 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 ea7d9d81..49adfd6f 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()