#!/usr/bin/env python # Copyright 2016 - 2017, New York University and the TUF contributors # SPDX-License-Identifier: MIT OR Apache-2.0 """ test_root_versioning_integration.py Evan Cordell. July 21, 2016. See LICENSE-MIT OR LICENSE for licensing information. Test root versioning for efficient root key rotation. """ import os import logging import tempfile import shutil import unittest import sys import tuf import tuf.log import tuf.formats import tuf.exceptions import tuf.roledb import tuf.keydb import tuf.repository_tool as repo_tool from tests import utils import securesystemslib import securesystemslib.storage logger = logging.getLogger(__name__) repo_tool.disable_console_log_messages() class TestRepository(unittest.TestCase): @classmethod def setUpClass(cls): cls.temporary_directory = tempfile.mkdtemp(dir=os.getcwd()) @classmethod def tearDownClass(cls): shutil.rmtree(cls.temporary_directory) def tearDown(self): tuf.roledb.clear_roledb() tuf.keydb.clear_keydb() def test_init(self): # Test normal case. storage_backend = securesystemslib.storage.FilesystemBackend() repository = repo_tool.Repository('repository_directory/', 'metadata_directory/', 'targets_directory/', storage_backend) self.assertTrue(isinstance(repository.root, repo_tool.Root)) self.assertTrue(isinstance(repository.snapshot, repo_tool.Snapshot)) self.assertTrue(isinstance(repository.timestamp, repo_tool.Timestamp)) self.assertTrue(isinstance(repository.targets, repo_tool.Targets)) # Test improperly formatted arguments. self.assertRaises(securesystemslib.exceptions.FormatError, repo_tool.Repository, 3, 'metadata_directory/', 'targets_directory', storage_backend) self.assertRaises(securesystemslib.exceptions.FormatError, repo_tool.Repository, 'repository_directory', 3, 'targets_directory', storage_backend) self.assertRaises(securesystemslib.exceptions.FormatError, repo_tool.Repository, 'repository_directory', 'metadata_directory', storage_backend, 3) def test_root_role_versioning(self): # Test root role versioning # # 1. Import public and private keys. # 2. Add verification keys. # 3. Load signing keys. # 4. Add target files. # 5. Perform delegation. # 6. writeall() # # Copy the target files from 'tuf/tests/repository_data' so that writeall() # has target fileinfo to include in metadata. temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory) targets_directory = os.path.join(temporary_directory, 'repository', repo_tool.TARGETS_DIRECTORY_NAME) original_targets_directory = os.path.join('repository_data', 'repository', 'targets') shutil.copytree(original_targets_directory, targets_directory) # In this case, create_new_repository() creates the 'repository/' # sub-directory in 'temporary_directory' if it does not exist. repository_directory = os.path.join(temporary_directory, 'repository') metadata_directory = os.path.join(repository_directory, repo_tool.METADATA_STAGED_DIRECTORY_NAME) repository = repo_tool.create_new_repository(repository_directory) # (1) Load the public and private keys of the top-level roles, and one # delegated role. keystore_directory = os.path.join('repository_data', 'keystore') # Load the public keys. root_pubkey_path = os.path.join(keystore_directory, 'root_key.pub') targets_pubkey_path = os.path.join(keystore_directory, 'targets_key.pub') snapshot_pubkey_path = os.path.join(keystore_directory, 'snapshot_key.pub') timestamp_pubkey_path = os.path.join(keystore_directory, 'timestamp_key.pub') role1_pubkey_path = os.path.join(keystore_directory, 'delegation_key.pub') root_pubkey = repo_tool.import_rsa_publickey_from_file(root_pubkey_path) targets_pubkey = repo_tool.import_ed25519_publickey_from_file(targets_pubkey_path) snapshot_pubkey = \ repo_tool.import_ed25519_publickey_from_file(snapshot_pubkey_path) timestamp_pubkey = \ repo_tool.import_ed25519_publickey_from_file(timestamp_pubkey_path) role1_pubkey = repo_tool.import_ed25519_publickey_from_file(role1_pubkey_path) # Load the private keys. root_privkey_path = os.path.join(keystore_directory, 'root_key') targets_privkey_path = os.path.join(keystore_directory, 'targets_key') snapshot_privkey_path = os.path.join(keystore_directory, 'snapshot_key') timestamp_privkey_path = os.path.join(keystore_directory, 'timestamp_key') role1_privkey_path = os.path.join(keystore_directory, 'delegation_key') root_privkey = \ repo_tool.import_rsa_privatekey_from_file(root_privkey_path, 'password') targets_privkey = \ repo_tool.import_ed25519_privatekey_from_file(targets_privkey_path, 'password') snapshot_privkey = \ repo_tool.import_ed25519_privatekey_from_file(snapshot_privkey_path, 'password') timestamp_privkey = \ repo_tool.import_ed25519_privatekey_from_file(timestamp_privkey_path, 'password') role1_privkey = \ repo_tool.import_ed25519_privatekey_from_file(role1_privkey_path, 'password') # (2) Add top-level verification keys. repository.root.add_verification_key(root_pubkey) repository.targets.add_verification_key(targets_pubkey) repository.snapshot.add_verification_key(snapshot_pubkey) repository.timestamp.add_verification_key(timestamp_pubkey) # (3) Load top-level signing keys. repository.root.load_signing_key(root_privkey) repository.targets.load_signing_key(targets_privkey) repository.snapshot.load_signing_key(snapshot_privkey) repository.timestamp.load_signing_key(timestamp_privkey) # (4) Add target files. target1 = 'file1.txt' target2 = 'file2.txt' target3 = 'file3.txt' repository.targets.add_target(target1) repository.targets.add_target(target2) # (5) Perform delegation. repository.targets.delegate('role1', [role1_pubkey], [target3]) repository.targets('role1').load_signing_key(role1_privkey) # (6) Write repository. repository.writeall() self.assertTrue(os.path.exists(os.path.join(metadata_directory, 'root.json'))) self.assertTrue(os.path.exists(os.path.join(metadata_directory, '1.root.json'))) # Verify that the expected metadata is written. root_filepath = os.path.join(metadata_directory, 'root.json') root_1_filepath = os.path.join(metadata_directory, '1.root.json') root_2_filepath = os.path.join(metadata_directory, '2.root.json') old_root_signable = securesystemslib.util.load_json_file(root_filepath) root_1_signable = securesystemslib.util.load_json_file(root_1_filepath) # Make a change to the root keys repository.root.add_verification_key(targets_pubkey) repository.root.load_signing_key(targets_privkey) repository.root.threshold = 2 repository.writeall() new_root_signable = securesystemslib.util.load_json_file(root_filepath) root_2_signable = securesystemslib.util.load_json_file(root_2_filepath) for role_signable in [old_root_signable, new_root_signable, root_1_signable, root_2_signable]: # Raise 'securesystemslib.exceptions.FormatError' if 'role_signable' is an # invalid signable. tuf.formats.check_signable_object_format(role_signable) # Verify contents of versioned roots self.assertEqual(old_root_signable, root_1_signable) self.assertEqual(new_root_signable, root_2_signable) self.assertEqual(root_1_signable['signed']['version'], 1) self.assertEqual(root_2_signable['signed']['version'], 2) repository.root.remove_verification_key(root_pubkey) repository.root.unload_signing_key(root_privkey) repository.root.threshold = 2 # Errors, not enough signing keys to satisfy old threshold self.assertRaises(tuf.exceptions.UnsignedMetadataError, repository.writeall) # No error, write() ignore's root's threshold and allows it to be written # to disk partially signed. repository.write('root') if __name__ == '__main__': utils.configure_test_logging(sys.argv) unittest.main()