diff --git a/tests/repository_data/keystore/root_key2 b/tests/repository_data/keystore/root_key2 new file mode 100644 index 0000000000..82229465a6 --- /dev/null +++ b/tests/repository_data/keystore/root_key2 @@ -0,0 +1 @@ +77c02ab5647ee765d5f6c5fc202a5b32@@@@100000@@@@7c73c1100fab52dc8695c1b955d31770ed6e53f1820d9020aeb6541c948573d9@@@@98280307ffa9c5f6ff1fea1a4b79d0ea@@@@f3342882b1cf842e3377ab4205c0ca8fab564cc55fa742f55b364a1ac597e93d8c56a9a6e6bbb6a812556077be44a1066ac6781a6ed34b86beaf3985f846f007dab31c46af562e921f03c1ea8d299f15324ab137aa426ee61d396a7e20191aa71a70b670775b2ad48f25de367fb48881c55e93f468c6e59402907e82985c27c94c715161c85c5c1904353ba33c3d129988029f03a2d7d00720118697baaf73a3c4e72f8e538b4323866fe525ddccfcfc6dd45598545f65cd7ab581f5172bc253416283a66621eb03dbabaf33923bb1963f9f8cbae6fd6a1c86736a8f80c8d1ba3cbc3f53b0123ba9b0bdd44f25b65033b19a50ee978d2687d6a2ee724515a20026d0213ced59cda9bfdf37c82c59e1356795fd603d85996f448a3c9357b32de2042997a1d27353ee3866c0ed5218d633e0b28991119d77d147354c7fa2de8a168d17efdfd5fa9a8e528bd47ede4ff697 \ No newline at end of file diff --git a/tests/repository_data/keystore/root_key2.pub b/tests/repository_data/keystore/root_key2.pub new file mode 100644 index 0000000000..dd5c43b5f3 --- /dev/null +++ b/tests/repository_data/keystore/root_key2.pub @@ -0,0 +1 @@ +{"keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "3ba219e69666298bce5d1d653a166346aef807c02e32a846aaefcb5190fddeb4"}} \ No newline at end of file diff --git a/tests/repository_data/keystore/root_key3 b/tests/repository_data/keystore/root_key3 new file mode 100644 index 0000000000..1cf4e5abbd --- /dev/null +++ b/tests/repository_data/keystore/root_key3 @@ -0,0 +1 @@ +ce4624d30171067445ed3fa863f66127@@@@100000@@@@f10f918e9e895ba72fb784e2dccc1b09e4cbc17ff23eda55687e272e217bb09f@@@@63982fe353cdb82ed7825e9569804f0e@@@@9bd085924cbbfff9fbf4cad01f8119a3726b734ea46c573e4d39239c238285a332a73fe1d3e9966a6e958c387f4eec40a0b3e7883b6d347f5506d64f3acf06148261afeca9585e7fd71e2726373a481627bd82c545fdf031bbde9bb3db986e6de29efcad3fc5d7ae5c274d4e0e9b521af6e34263965fa7e5b7e10c7dee4c880424930d4f698ff5a0531ab4a430c74fe6bb831db7c40b0c097c6c22c6cae2b25c712cbf02638cee3cdfea9854ef690b4707e27851ed5e63cda60925e3e4e49467ef031e31dd9524b51e635697d3049cec09ea86cebf60fb2082247e784d48b0c71ef79a4fc4807ad2905140c051795f904baaacee334e34a4d82a78befe735810a77ac40d1a977460026f8513dbccd7fd5c980cc5397d223adc132a6b7230448685f6f1aec40e2a9d6ab39d818c94c7ace66e6a87cc94b690a2018fb3b376010c05ac0587631122d6979b0f575d18655d \ No newline at end of file diff --git a/tests/repository_data/keystore/root_key3.pub b/tests/repository_data/keystore/root_key3.pub new file mode 100644 index 0000000000..496fdcb120 --- /dev/null +++ b/tests/repository_data/keystore/root_key3.pub @@ -0,0 +1 @@ +{"keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "e68d6e173fe21d8bc4a558606784abdbb71f31cd13fa2aeef29972f60f5c5809"}} \ No newline at end of file diff --git a/tests/test_updater_root_rotation_integration.py b/tests/test_updater_root_rotation_integration.py index 49177ee9a2..a86c8b4e76 100755 --- a/tests/test_updater_root_rotation_integration.py +++ b/tests/test_updater_root_rotation_integration.py @@ -51,6 +51,7 @@ import subprocess import sys import unittest +import filecmp import tuf import tuf.log @@ -60,6 +61,7 @@ import tuf.repository_tool as repo_tool import tuf.unittest_toolbox as unittest_toolbox import tuf.client.updater as updater +import tuf.settings import securesystemslib import six @@ -213,9 +215,98 @@ def test_root_rotation(self): shutil.rmtree(os.path.join(self.repository_directory, 'metadata')) shutil.copytree(os.path.join(self.repository_directory, 'metadata.staged'), os.path.join(self.repository_directory, 'metadata')) + self.repository_updater.refresh() + + + + def test_root_rotation_full(self): + """Test that a client whose root is outdated by multiple versions and who + has none of the latest nor next-to-latest root keys can still update and + does so by incrementally verifying all roots until the most recent one. """ + # Load initial repository with 1.root.json == root.json, signed by "root" + # key. This is the root.json that is already on the client. + repository = repo_tool.load_repository(self.repository_directory) + + # 1st rotation: 1.root.json --> 2.root.json + # 2.root.json will be signed by previous "root" key and by new "root2" key + repository.root.load_signing_key(self.role_keys['root']['private']) + repository.root.add_verification_key(self.role_keys['root2']['public']) + repository.root.load_signing_key(self.role_keys['root2']['private']) + repository.writeall() + + # 2nd rotation: 2.root.json --> 3.root.json + # 3.root.json will be signed by previous "root2" key and by new "root3" key + repository.root.unload_signing_key(self.role_keys['root']['private']) + repository.root.remove_verification_key(self.role_keys['root']['public']) + repository.root.add_verification_key(self.role_keys['root3']['public']) + repository.root.load_signing_key(self.role_keys['root3']['private']) + repository.writeall() + # Move staged metadata to "live" metadata + shutil.rmtree(os.path.join(self.repository_directory, 'metadata')) + shutil.copytree(os.path.join(self.repository_directory, 'metadata.staged'), + os.path.join(self.repository_directory, 'metadata')) + + # Update on client 1.root.json --> 2.root.json --> 3.root.json self.repository_updater.refresh() + # Assert that client updated to the latest root from the repository + self.assertTrue(filecmp.cmp( + os.path.join(self.repository_directory, 'metadata', '3.root.json'), + os.path.join(self.client_metadata_current, 'root.json'))) + + + + def test_root_rotation_max(self): + """Test that client does not rotate beyond a configured upper bound, i.e. + `current_version + MAX_NUMBER_ROOT_ROTATIONS`. """ + # NOTE: The nature of below root changes is irrelevant. Here we only want + # the client to update but not beyond a configured upper bound. + + # 1.root.json --> 2.root.json (add root2 and root3 keys) + repository = repo_tool.load_repository(self.repository_directory) + repository.root.load_signing_key(self.role_keys['root']['private']) + repository.root.add_verification_key(self.role_keys['root2']['public']) + repository.root.load_signing_key(self.role_keys['root2']['private']) + repository.root.add_verification_key(self.role_keys['root3']['public']) + repository.root.load_signing_key(self.role_keys['root3']['private']) + repository.writeall() + + # 2.root.json --> 3.root.json (change threshold) + repository.root.threshold = 2 + repository.writeall() + + # 3.root.json --> 4.root.json (change threshold again) + repository.root.threshold = 3 + repository.writeall() + + # Move staged metadata to "live" metadata + shutil.rmtree(os.path.join(self.repository_directory, 'metadata')) + shutil.copytree(os.path.join(self.repository_directory, 'metadata.staged'), + os.path.join(self.repository_directory, 'metadata')) + + # Assert that repo indeed has "4.root.json" and that it's the latest root + self.assertTrue(filecmp.cmp( + os.path.join(self.repository_directory, 'metadata', '4.root.json'), + os.path.join(self.repository_directory, 'metadata', 'root.json'))) + + # Lower max root rotation cap so that client stops updating early + max_rotation_backup = tuf.settings.MAX_NUMBER_ROOT_ROTATIONS + tuf.settings.MAX_NUMBER_ROOT_ROTATIONS = 2 + + # Update on client 1.root.json --> 2.root.json --> 3.root.json, + # but stop before updating to 4.root.json + self.repository_updater.refresh() + + # Assert that the client indeed only updated until 3.root.json + self.assertTrue(filecmp.cmp( + os.path.join(self.repository_directory, 'metadata', '3.root.json'), + os.path.join(self.client_metadata_current, 'root.json'))) + + # reset + tuf.settings.MAX_NUMBER_ROOT_ROTATIONS = max_rotation_backup + + def test_root_rotation_missing_keys(self): repository = repo_tool.load_repository(self.repository_directory) @@ -327,6 +418,7 @@ def test_root_rotation_unmet_threshold(self): + def _load_role_keys(keystore_directory): # Populating 'self.role_keys' by importing the required public and private @@ -342,17 +434,23 @@ def _load_role_keys(keystore_directory): role_keys = {} root_key_file = os.path.join(keystore_directory, 'root_key') + root2_key_file = os.path.join(keystore_directory, 'root_key2') + root3_key_file = os.path.join(keystore_directory, 'root_key3') targets_key_file = os.path.join(keystore_directory, 'targets_key') snapshot_key_file = os.path.join(keystore_directory, 'snapshot_key') timestamp_key_file = os.path.join(keystore_directory, 'timestamp_key') delegation_key_file = os.path.join(keystore_directory, 'delegation_key') - role_keys = {'root': {}, 'targets': {}, 'snapshot': {}, 'timestamp': {}, - 'role1': {}} + role_keys = {'root': {}, 'root2': {}, 'root3': {}, 'targets': {}, 'snapshot': + {}, 'timestamp': {}, 'role1': {}} # Import the top-level and delegated role public keys. role_keys['root']['public'] = \ repo_tool.import_rsa_publickey_from_file(root_key_file+'.pub') + role_keys['root2']['public'] = \ + repo_tool.import_ed25519_publickey_from_file(root2_key_file+'.pub') + role_keys['root3']['public'] = \ + repo_tool.import_ed25519_publickey_from_file(root3_key_file+'.pub') role_keys['targets']['public'] = \ repo_tool.import_ed25519_publickey_from_file(targets_key_file+'.pub') role_keys['snapshot']['public'] = \ @@ -366,6 +464,12 @@ def _load_role_keys(keystore_directory): role_keys['root']['private'] = \ repo_tool.import_rsa_privatekey_from_file(root_key_file, EXPECTED_KEYFILE_PASSWORD) + role_keys['root2']['private'] = \ + repo_tool.import_ed25519_privatekey_from_file(root2_key_file, + EXPECTED_KEYFILE_PASSWORD) + role_keys['root3']['private'] = \ + repo_tool.import_ed25519_privatekey_from_file(root3_key_file, + EXPECTED_KEYFILE_PASSWORD) role_keys['targets']['private'] = \ repo_tool.import_ed25519_privatekey_from_file(targets_key_file, EXPECTED_KEYFILE_PASSWORD)