def setUpClass(cls): # Create a temporary directory to store the repository, metadata, and # target files. 'temporary_directory' must be deleted in # TearDownClass() so that temporary files are always removed, even when # exceptions occur. cls.temporary_directory = tempfile.mkdtemp(dir=os.getcwd()) test_repo_data = os.path.join( os.path.dirname(os.path.realpath(__file__)), 'repository_data') cls.repo_dir = os.path.join(cls.temporary_directory, 'repository') shutil.copytree( os.path.join(test_repo_data, 'repository'), cls.repo_dir) cls.keystore_dir = os.path.join(cls.temporary_directory, 'keystore') shutil.copytree( os.path.join(test_repo_data, 'keystore'), cls.keystore_dir) # Load keys into memory cls.keystore = {} for role in ['delegation', 'snapshot', 'targets', 'timestamp']: cls.keystore[role] = { 'private': import_ed25519_privatekey_from_file( os.path.join(cls.keystore_dir, role + '_key'), password="******"), 'public': import_ed25519_publickey_from_file( os.path.join(cls.keystore_dir, role + '_key.pub')) }
def test_root_add_key_and_revoke_key(self) -> None: root_path = os.path.join(self.repo_dir, "metadata", "root.json") root = Metadata[Root].from_file(root_path) # Create a new key root_key2 = import_ed25519_publickey_from_file( os.path.join(self.keystore_dir, "root_key2.pub")) keyid = root_key2["keyid"] key_metadata = Key( keyid, root_key2["keytype"], root_key2["scheme"], root_key2["keyval"], ) # Assert that root does not contain the new key self.assertNotIn(keyid, root.signed.roles[Root.type].keyids) self.assertNotIn(keyid, root.signed.keys) # Assert that add_key with old argument order will raise an error with self.assertRaises(ValueError): root.signed.add_key(Root.type, key_metadata) # type: ignore # Add new root key root.signed.add_key(key_metadata, Root.type) # Assert that key is added self.assertIn(keyid, root.signed.roles[Root.type].keyids) self.assertIn(keyid, root.signed.keys) # Confirm that the newly added key does not break # the object serialization root.to_dict() # Try adding the same key again and assert its ignored. pre_add_keyid = root.signed.roles[Root.type].keyids.copy() root.signed.add_key(key_metadata, Root.type) self.assertEqual(pre_add_keyid, root.signed.roles[Root.type].keyids) # Add the same key to targets role as well root.signed.add_key(key_metadata, Targets.type) # Add the same key to a nonexistent role. with self.assertRaises(ValueError): root.signed.add_key(key_metadata, "nosuchrole") # Remove the key from root role (targets role still uses it) root.signed.revoke_key(keyid, Root.type) self.assertNotIn(keyid, root.signed.roles[Root.type].keyids) self.assertIn(keyid, root.signed.keys) # Remove the key from targets as well root.signed.revoke_key(keyid, Targets.type) self.assertNotIn(keyid, root.signed.roles[Targets.type].keyids) self.assertNotIn(keyid, root.signed.keys) with self.assertRaises(ValueError): root.signed.revoke_key("nosuchkey", Root.type) with self.assertRaises(ValueError): root.signed.revoke_key(keyid, "nosuchrole")
def test_metadata_root(self): root_path = os.path.join( self.repo_dir, 'metadata', 'root.json') root = Metadata.from_file(root_path) # Add a second key to root role root_key2 = import_ed25519_publickey_from_file( os.path.join(self.keystore_dir, 'root_key2.pub')) keyid = root_key2['keyid'] key_metadata = format_keyval_to_metadata( root_key2['keytype'], root_key2['scheme'], root_key2['keyval']) # Assert that root does not contain the new key self.assertNotIn(keyid, root.signed.roles['root']['keyids']) self.assertNotIn(keyid, root.signed.keys) # Add new root key root.signed.add_key('root', keyid, key_metadata) # Assert that key is added self.assertIn(keyid, root.signed.roles['root']['keyids']) self.assertIn(keyid, root.signed.keys) # Remove the key root.signed.remove_key('root', keyid) # Assert that root does not contain the new key anymore self.assertNotIn(keyid, root.signed.roles['root']['keyids']) self.assertNotIn(keyid, root.signed.keys)
def test_generate_and_write_ed25519_keypair(self): # Test normal case. temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory) test_keypath = os.path.join(temporary_directory, 'ed25519_key') interface.generate_and_write_ed25519_keypair(test_keypath, password='******') self.assertTrue(os.path.exists(test_keypath)) self.assertTrue(os.path.exists(test_keypath + '.pub')) # Ensure the generated key files are importable. imported_pubkey = \ interface.import_ed25519_publickey_from_file(test_keypath + '.pub') self.assertTrue(securesystemslib.formats.ED25519KEY_SCHEMA.matches(imported_pubkey)) imported_privkey = \ interface.import_ed25519_privatekey_from_file(test_keypath, 'pw') self.assertTrue(securesystemslib.formats.ED25519KEY_SCHEMA.matches(imported_privkey)) # Test improperly formatted arguments. self.assertRaises(securesystemslib.exceptions.FormatError, interface.generate_and_write_ed25519_keypair, 3, password='******') self.assertRaises(securesystemslib.exceptions.FormatError, interface.generate_and_write_rsa_keypair, test_keypath, password=3)
def test_create_and_import_ed25519(self): """Create ed25519 key and import private and public key separately. """ name = "key6" generate_and_write_ed25519_keypair(name) private_key = import_ed25519_privatekey_from_file(name) public_key = import_ed25519_publickey_from_file(name + ".pub") securesystemslib.formats.KEY_SCHEMA.check_match(private_key) self.assertTrue(private_key["keyval"].get("private")) self.assertTrue( securesystemslib.formats.PUBLIC_KEY_SCHEMA.matches(public_key))
def import_public_keys_from_files_as_dict(filepaths, key_types=None): """ <Purpose> Takes a list of filepaths to RSA public keys and returns them as a dictionary conformant with securesystemslib.formats.KEYDICT_SCHEMA. <Arguments> filepaths: List of paths to the public keys. key_types: (optional) List types of each of the keys being imported into the dict. If not specified, all keys are assumed to be RSA. <Exceptions> securesystemslib.exceptions.FormatError, if the arguments are don't have the same length. UnsupportedKeyTypeError, if the key_type specified is unsupported. <Side Effects> Each file in 'filepaths' is read and its contents extracted <Returns> A key dict object conformant with securesystemslib.formats.KEYDICT_SCHEMA """ # are key_types needed? # we could figure it out using the key format if key_types is None: key_types = [KEY_TYPE_RSA] * len(filepaths) if len(key_types) != len(filepaths): raise securesystemslib.exceptions.FormatError( "number of key_types should match with the number" " of layout keys specified") key_dict = {} for idx, filepath in enumerate(filepaths): if key_types[idx] == KEY_TYPE_ED25519: key = import_ed25519_publickey_from_file(filepath) elif key_types[idx] == KEY_TYPE_RSA: key = import_rsa_key_from_file(filepath) else: # pragma: no cover # This branch is never possible as argparse already checks valid keys # via the choices parameter. raise UnsupportedKeyTypeError('Unsupported keytype: ' + key_types[idx]) securesystemslib.formats.PUBLIC_KEY_SCHEMA.check_match(key) keyid = key["keyid"] key_dict[keyid] = key return key_dict
def test_import_ed25519_publickey_from_file(self): # Test normal case. # Generate ed25519 keys that can be imported. temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory) ed25519_keypath = os.path.join(temporary_directory, 'ed25519_key') interface.generate_and_write_ed25519_keypair(ed25519_keypath, password='******') imported_ed25519_key = \ interface.import_ed25519_publickey_from_file(ed25519_keypath + '.pub') self.assertTrue( securesystemslib.formats.ED25519KEY_SCHEMA.matches( imported_ed25519_key)) # Test improperly formatted argument. self.assertRaises(securesystemslib.exceptions.FormatError, interface.import_ed25519_publickey_from_file, 3) # Test invalid argument. # Non-existent key file. nonexistent_keypath = os.path.join(temporary_directory, 'nonexistent_keypath') self.assertRaises(IOError, interface.import_ed25519_publickey_from_file, nonexistent_keypath) # Invalid key file argument. invalid_keyfile = os.path.join(temporary_directory, 'invalid_keyfile') with open(invalid_keyfile, 'wb') as file_object: file_object.write(b'bad keyfile') self.assertRaises(securesystemslib.exceptions.Error, interface.import_ed25519_publickey_from_file, invalid_keyfile) # Invalid public key imported (contains unexpected keytype.) keytype = imported_ed25519_key['keytype'] keyval = imported_ed25519_key['keyval'] scheme = imported_ed25519_key['scheme'] ed25519key_metadata_format = \ securesystemslib.keys.format_keyval_to_metadata(keytype, scheme, keyval, private=False) ed25519key_metadata_format['keytype'] = 'invalid_keytype' with open(ed25519_keypath + '.pub', 'wb') as file_object: file_object.write( json.dumps(ed25519key_metadata_format).encode('utf-8')) self.assertRaises(securesystemslib.exceptions.FormatError, interface.import_ed25519_publickey_from_file, ed25519_keypath + '.pub')
def read_keypair(functionary: str, keypath: Keypath, i: int = 1, n: int = 1, passphrase: Optional[str] = None) -> Keypair: assert isinstance(keypath, Keypath) private_keypath = keypath.private private_key_obj = import_ed25519_privatekey_from_file(keypath.private, password=passphrase) private_key = Key(private_keypath, private_key_obj) # and its corresponding public key. public_keypath = keypath.public public_key_obj = import_ed25519_publickey_from_file(keypath.public) public_key = Key(public_keypath, public_key_obj) return Keypair(private_key, public_key)
def test_root_add_key_and_remove_key(self): root_path = os.path.join( self.repo_dir, 'metadata', 'root.json') root = Metadata[Root].from_file(root_path) # Create a new key root_key2 = import_ed25519_publickey_from_file( os.path.join(self.keystore_dir, 'root_key2.pub')) keyid = root_key2['keyid'] key_metadata = Key(keyid, root_key2['keytype'], root_key2['scheme'], root_key2['keyval']) # Assert that root does not contain the new key self.assertNotIn(keyid, root.signed.roles['root'].keyids) self.assertNotIn(keyid, root.signed.keys) # Add new root key root.signed.add_key('root', key_metadata) # Assert that key is added self.assertIn(keyid, root.signed.roles['root'].keyids) self.assertIn(keyid, root.signed.keys) # Confirm that the newly added key does not break # the object serialization root.to_dict() # Try adding the same key again and assert its ignored. pre_add_keyid = root.signed.roles['root'].keyids.copy() root.signed.add_key('root', key_metadata) self.assertEqual(pre_add_keyid, root.signed.roles['root'].keyids) # Add the same key to targets role as well root.signed.add_key('targets', key_metadata) # Remove the key from root role (targets role still uses it) root.signed.remove_key('root', keyid) self.assertNotIn(keyid, root.signed.roles['root'].keyids) self.assertIn(keyid, root.signed.keys) # Remove the key from targets as well root.signed.remove_key('targets', keyid) self.assertNotIn(keyid, root.signed.roles['targets'].keyids) self.assertNotIn(keyid, root.signed.keys) with self.assertRaises(KeyError): root.signed.remove_key('root', 'nosuchkey')
def test_ed25519(self): """Test ed25519 key _generation and import interface functions. """ # TEST: Generate default keys and import # Assert location and format fn_default = "default" fn_default_ret = _generate_and_write_ed25519_keypair( filepath=fn_default) pub = import_ed25519_publickey_from_file(fn_default + ".pub") priv = import_ed25519_privatekey_from_file(fn_default) self.assertEqual(fn_default, fn_default_ret) self.assertTrue(ED25519KEY_SCHEMA.matches(pub)) self.assertTrue(PUBLIC_KEY_SCHEMA.matches(pub)) self.assertTrue(ED25519KEY_SCHEMA.matches(priv)) # NOTE: There is no private key schema, at least check it has a value self.assertTrue(priv["keyval"]["private"]) # TEST: Generate unencrypted keys with empty prompt # Assert importable with empty prompt password and without password fn_empty_prompt = "empty_prompt" with mock.patch("securesystemslib.interface.get_password", return_value=""): _generate_and_write_ed25519_keypair(filepath=fn_empty_prompt) import_ed25519_privatekey_from_file(fn_empty_prompt, prompt=True) import_ed25519_privatekey_from_file(fn_empty_prompt) # TEST: Generate keys with auto-filename, i.e. keyid # Assert filename is keyid fn_keyid = _generate_and_write_ed25519_keypair() pub = import_ed25519_publickey_from_file(fn_keyid + ".pub") priv = import_ed25519_privatekey_from_file(fn_keyid) self.assertTrue( os.path.basename(fn_keyid) == pub["keyid"] == priv["keyid"]) # TEST: Generate two keypairs with encrypted private keys using ... pw = "pw" fn_encrypted = "encrypted" fn_prompt = "prompt" # ... a passed pw ... _generate_and_write_ed25519_keypair(filepath=fn_encrypted, password=pw) with mock.patch("securesystemslib.interface.get_password", return_value=pw): # ... and a prompted pw. _generate_and_write_ed25519_keypair(filepath=fn_prompt, prompt=True) # Assert that both private keys are importable using the prompted pw ... import_ed25519_privatekey_from_file(fn_prompt, prompt=True) import_ed25519_privatekey_from_file(fn_encrypted, prompt=True) # ... and the passed pw. import_ed25519_privatekey_from_file(fn_prompt, password=pw) import_ed25519_privatekey_from_file(fn_encrypted, password=pw) # TEST: Import existing keys with encrypted private key (test regression) # Assert format pub = import_ed25519_publickey_from_file(self.path_ed25519 + ".pub") priv = import_ed25519_privatekey_from_file(self.path_ed25519, "password") self.assertTrue(PUBLIC_KEY_SCHEMA.matches(pub)) self.assertTrue(ED25519KEY_SCHEMA.matches(pub)) self.assertTrue(ED25519KEY_SCHEMA.matches(priv)) # NOTE: There is no private key schema, at least check it has a value self.assertTrue(priv["keyval"]["private"]) # TEST: Unexpected behavior # FIXME: Should 'import_ed25519_publickey_from_file' be able to import a # a non-encrypted ed25519 private key? I think it should not, but it is: priv = import_ed25519_publickey_from_file(fn_default) self.assertTrue(ED25519KEY_SCHEMA.matches(priv)) self.assertTrue(priv["keyval"]["private"]) # FIXME: Should 'import_ed25519_privatekey_from_file' be able to import a # an ed25519 public key? I think it should not, but it is: pub = import_ed25519_privatekey_from_file(fn_default + ".pub") self.assertTrue(PUBLIC_KEY_SCHEMA.matches(pub)) # TEST: Generation errors for idx, (kwargs, err_msg) in enumerate([ # Error on empty password ({ "password": "" }, "encryption password must be 1 or more characters long"), # Error on 'password' and 'prompt=True' ({ "password": pw, "prompt": True }, "passing 'password' and 'prompt=True' is not allowed") ]): with self.assertRaises(ValueError, msg="(row {})".format(idx)) as ctx: _generate_and_write_ed25519_keypair(**kwargs) self.assertEqual( err_msg, str(ctx.exception), "expected: '{}' got: '{}' (row {})".format( err_msg, ctx.exception, idx)) # Error on bad argument format for idx, kwargs in enumerate([ { "filepath": 123456 }, # Not a string { "password": 123456 }, # Not a string { "prompt": "not-a-bool" } ]): with self.assertRaises(FormatError, msg="(row {})".format(idx)): _generate_and_write_ed25519_keypair(**kwargs) # TEST: Import errors # Error on public key import... for idx, (fn, err_msg) in enumerate([ # Error on invalid json (custom key format) (fn_encrypted, "Cannot deserialize to a Python object"), # Error on invalid custom key format (self.path_no_key, "Missing key"), # Error on invalid key type (self.path_ecdsa + ".pub", "Invalid key type loaded") ]): with self.assertRaises(Error, msg="(row {})".format(idx)) as ctx: import_ed25519_publickey_from_file(fn) self.assertTrue( err_msg in str(ctx.exception), "expected: '{}' got: '{}' (row {})".format( err_msg, ctx.exception, idx)) # Error on private key import... for idx, (args, kwargs, err, err_msg) in enumerate([ # Error on not an ed25519 private key ([self.path_ecdsa], {}, CryptoError, "Malformed Ed25519 key JSON, possibly due to encryption, " "but no password provided?"), # Error on not encrypted ([fn_default], { "password": pw }, CryptoError, "Invalid encrypted file."), # Error on encrypted but no pw ([fn_encrypted], {}, CryptoError, "Malformed Ed25519 key JSON, possibly due to encryption, " "but no password provided?"), # Error on encrypted but empty pw ([fn_encrypted], { "password": "" }, CryptoError, "Decryption failed."), # Error on encrypted but bad pw passed ([fn_encrypted], { "password": "******" }, CryptoError, "Decryption failed."), # Error on pw and prompt ([fn_default], { "password": pw, "prompt": True }, ValueError, "passing 'password' and 'prompt=True' is not allowed") ]): with self.assertRaises(err, msg="(row {})".format(idx)) as ctx: import_ed25519_privatekey_from_file(*args, **kwargs) self.assertTrue( err_msg in str(ctx.exception), "expected: '{}' got: '{}' (row {})".format( err_msg, ctx.exception, idx)) # Error on encrypted but bad pw prompted err_msg = ("Malformed Ed25519 key JSON, possibly due to encryption, " "but no password provided?") with self.assertRaises(CryptoError) as ctx, mock.patch( "securesystemslib.interface.get_password", return_value="bad_pw"): import_ed25519_privatekey_from_file(fn_encrypted) self.assertTrue( err_msg in str(ctx.exception), "expected: '{}' got: '{}'".format(err_msg, ctx.exception)) # Error on bad path format with self.assertRaises(FormatError): import_ed25519_publickey_from_file(123456) with self.assertRaises(FormatError): import_ed25519_privatekey_from_file(123456) # Error on bad password format with self.assertRaises(FormatError): import_ed25519_privatekey_from_file(fn_default, password=123456) # Error on bad prompt format with self.assertRaises(FormatError): import_ed25519_privatekey_from_file(fn_default, prompt="not-a-bool")
def test_generate_and_write_ed25519_keypair(self): # Test normal case. temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory) test_keypath = os.path.join(temporary_directory, 'ed25519_key') test_keypath_unencrypted = os.path.join(temporary_directory, 'ed25519_key_unencrypted') returned_path = interface.generate_and_write_ed25519_keypair( test_keypath, password='******') self.assertTrue(os.path.exists(test_keypath)) self.assertTrue(os.path.exists(test_keypath + '.pub')) self.assertEqual(returned_path, test_keypath) # If an empty string is given for 'password', the private key file # is written to disk unencrypted. interface.generate_and_write_ed25519_keypair(test_keypath_unencrypted, password='') self.assertTrue(os.path.exists(test_keypath_unencrypted)) self.assertTrue(os.path.exists(test_keypath_unencrypted + '.pub')) # Ensure the generated key files are importable. imported_pubkey = \ interface.import_ed25519_publickey_from_file(test_keypath + '.pub') self.assertTrue(securesystemslib.formats.ED25519KEY_SCHEMA\ .matches(imported_pubkey)) imported_privkey = \ interface.import_ed25519_privatekey_from_file(test_keypath, 'pw') self.assertTrue(securesystemslib.formats.ED25519KEY_SCHEMA\ .matches(imported_privkey)) # Fail importing encrypted key passing password and prompt with self.assertRaises(ValueError): interface.import_ed25519_privatekey_from_file(test_keypath, password='******', prompt=True) # Fail importing encrypted key passing an empty string for passwd with self.assertRaises(ValueError): interface.import_ed25519_privatekey_from_file(test_keypath, password='') # Try to import the unencrypted key file, by not passing a password imported_privkey = \ interface.import_ed25519_privatekey_from_file(test_keypath_unencrypted) self.assertTrue(securesystemslib.formats.ED25519KEY_SCHEMA.\ matches(imported_privkey)) # Try to import the unencrypted key file, by entering an empty password with mock.patch('securesystemslib.interface.get_password', return_value=''): imported_privkey = \ interface.import_ed25519_privatekey_from_file(test_keypath_unencrypted, prompt=True) self.assertTrue( securesystemslib.formats.ED25519KEY_SCHEMA.matches(imported_privkey)) # Fail importing unencrypted key passing a password with self.assertRaises(securesystemslib.exceptions.CryptoError): interface.import_ed25519_privatekey_from_file(test_keypath_unencrypted, 'pw') # Fail importing encrypted key passing no password with self.assertRaises(securesystemslib.exceptions.CryptoError): interface.import_ed25519_privatekey_from_file(test_keypath) # Test for a default filepath. If 'filepath' is not given, the key's # KEYID is used as the filename. The key is saved to the current working # directory. default_keypath = interface.generate_and_write_ed25519_keypair(password='******') self.assertTrue(os.path.exists(default_keypath)) self.assertTrue(os.path.exists(default_keypath + '.pub')) written_key = interface.import_ed25519_publickey_from_file(default_keypath + '.pub') self.assertEqual(written_key['keyid'], os.path.basename(default_keypath)) os.remove(default_keypath) os.remove(default_keypath + '.pub') # Test improperly formatted arguments. self.assertRaises(securesystemslib.exceptions.FormatError, interface.generate_and_write_ed25519_keypair, 3, password='******') self.assertRaises(securesystemslib.exceptions.FormatError, interface.generate_and_write_rsa_keypair, test_keypath, password=3)
def get_public_key(): init_keys() return import_ed25519_publickey_from_file(KEY_NAME + '.pub')