diff --git a/tests/test_updater.py b/tests/test_updater.py index ce0c870352..7427e3f724 100755 --- a/tests/test_updater.py +++ b/tests/test_updater.py @@ -1779,29 +1779,29 @@ def test_13__targets_of_role(self): - def test_snapshot_merkle(self): - # replace timestamp with a merkle timestamp and create the updater - merkle_timestamp = os.path.join(self.repository_directory, 'metadata', 'timestamp-merkle.json') + def test_snapshot_rsa_acc(self): + # replace timestamp with an RSA accumulator timestamp and create the updater + rsa_acc_timestamp = os.path.join(self.repository_directory, 'metadata', 'timestamp-rsa.json') timestamp = os.path.join(self.repository_directory, 'metadata', 'timestamp.json') - shutil.move(merkle_timestamp, timestamp) + shutil.move(rsa_acc_timestamp, timestamp) repository_updater = updater.Updater(self.repository_name, self.repository_mirrors) repository_updater.refresh() - # Test verify merkle path - snapshot_info = repository_updater.verify_merkle_path('targets') + # Test verify RSA accumulator proof + snapshot_info = repository_updater.verify_rsa_acc_proof('targets') self.assertEqual(snapshot_info['version'], 1) - snapshot_info = repository_updater.verify_merkle_path('role1') + snapshot_info = repository_updater.verify_rsa_acc_proof('role1') self.assertEqual(snapshot_info['version'], 1) - # verify merkle path with invalid role + # verify RSA accumulator with invalid role self.assertRaises(tuf.exceptions.NoWorkingMirrorError, - repository_updater.verify_merkle_path, 'foo') + repository_updater.verify_rsa_acc_proof, 'foo') - # Test get_one_valid_targetinfo with snapshot merkle + # Test get_one_valid_targetinfo with snapshot RSA accumulator repository_updater.get_one_valid_targetinfo('file1.txt') diff --git a/tuf/client/updater.py b/tuf/client/updater.py index e5df452382..4641c0d0a7 100755 --- a/tuf/client/updater.py +++ b/tuf/client/updater.py @@ -1081,9 +1081,9 @@ def refresh(self, unsafely_update_root_if_necessary=True): # require strict checks on its required length. self._update_metadata('timestamp', DEFAULT_TIMESTAMP_UPPERLENGTH) - if 'merkle_root' not in self.metadata['current']['timestamp']: - # If merkle root is set, do not update snapshot metadata. Instead, - # we will download the relevant merkle path later when downloading + if 'rsa_acc' not in self.metadata['current']['timestamp']: + # If an RSA Accumulator is defined, do not update snapshot metadata. Instead, + # we will download the relevant proof files later when downloading # a target. self._update_metadata_if_changed('snapshot', referenced_metadata='timestamp') @@ -1489,11 +1489,6 @@ def _get_metadata_file(self, metadata_role, remote_filename, The expected and required version number of the 'metadata_role' file downloaded. 'expected_version' is an integer. - snapshot_merkle: - Is the metadata file a snapshot merkle file? Snapshot merkle files - are not signed and so should skip some of the verification steps here. - Instead, they must be verified using verify_merkle_path. - tuf.exceptions.NoWorkingMirrorError: The metadata could not be fetched. This is raised only when all known @@ -1631,29 +1626,29 @@ def signable_verification(self, metadata_role, file_object, expected_version): - def _update_merkle_metadata(self, merkle_filename, upperbound_filelength, + def _update_rsa_acc_metadata(self, proof_filename, upperbound_filelength, version=None): """ - Non-public method that downloads, verifies, and 'installs' the merkle - metadata belonging to 'merkle_filename'. Calling this method implies - that the 'merkle_filename' on the repository is newer than the client's, + Non-public method that downloads, verifies, and 'installs' the proof + metadata belonging to 'proof_filename'. Calling this method implies + that the 'proof_filename' on the repository is newer than the client's, and thus needs to be re-downloaded. The current and previous metadata stores are updated if the newly downloaded metadata is successfully downloaded and verified. This method also assumes that the store of top-level metadata is the latest and exists. - merkle_filename: - The name of the metadata. This is a merkle tree file and should - not end in '.json'. Examples: 'role1-merkle', 'targets-merkle' + proof_filename: + The name of the metadata. This is an RSA accumulator proof file and should + not end in '.json'. Examples: 'role1-snapshot', 'targets-snapshot' upperbound_filelength: The expected length, or upper bound, of the metadata file to be downloaded. version: - The expected and required version number of the 'merkle_filename' file + The expected and required version number of the 'proof_filename' file downloaded. 'version' is an integer. @@ -1663,7 +1658,7 @@ def _update_merkle_metadata(self, merkle_filename, upperbound_filelength, metadata have been tried and failed. - The metadata file belonging to 'merkle_filenaem' is downloaded from a + The metadata file belonging to 'proof_filename' is downloaded from a repository mirror. If the metadata is valid, it is stored in the metadata store. @@ -1673,7 +1668,7 @@ def _update_merkle_metadata(self, merkle_filename, upperbound_filelength, # Construct the metadata filename as expected by the download/mirror # modules. - metadata_filename = merkle_filename + '.json' + metadata_filename = proof_filename + '.json' # Attempt a file download from each mirror until the file is downloaded and # verified. If the signature of the downloaded file is valid, proceed, @@ -1703,7 +1698,7 @@ def _update_merkle_metadata(self, merkle_filename, upperbound_filelength, verification_fn = None metadata_file_object = \ - self._get_metadata_file(merkle_filename, remote_filename, + self._get_metadata_file(proof_filename, remote_filename, upperbound_filelength, version, verification_fn) # The metadata has been verified. Move the metadata file into place. @@ -1732,16 +1727,16 @@ def _update_merkle_metadata(self, merkle_filename, upperbound_filelength, # Extract the metadata object so we can store it to the metadata store. # 'current_metadata_object' set to 'None' if there is not an object - # stored for 'merkle_filename'. - current_metadata_object = self.metadata['current'].get(merkle_filename) + # stored for 'proof_filename'. + current_metadata_object = self.metadata['current'].get(proof_filename) # Finally, update the metadata and fileinfo stores, and rebuild the - # key and role info for the top-level roles if 'merkle_filename' is root. + # key and role info for the top-level roles if 'proof_filename' is root. # Rebuilding the key and role info is required if the newly-installed # root metadata has revoked keys or updated any top-level role information. logger.debug('Updated ' + repr(current_filepath) + '.') - self.metadata['previous'][merkle_filename] = current_metadata_object - self.metadata['current'][merkle_filename] = updated_metadata_object + self.metadata['previous'][proof_filename] = current_metadata_object + self.metadata['current'][proof_filename] = updated_metadata_object @@ -1865,39 +1860,52 @@ def _update_metadata(self, metadata_role, upperbound_filelength, version=None): - def verify_merkle_path(self, metadata_role, version=None, merkle_root=None): + def verify_rsa_acc_proof(self, metadata_role, version=None, rsa_acc=None): """ - Download the merkle path associated with metadata_role and verify the hashes. + Download the RSA accumulator proof associated with metadata_role and verify the hashes. metadata_role: The name of the metadata role. This should not include a file extension. tuf.exceptions.RepositoryError: - If the snapshot merkle file is invalid or the verification fails + If the snapshot rsa accumulator file is invalid or the verification fails A dictionary containing the snapshot information about metadata role, conforming to VERSIONINFO_SCHEMA or METADATA_FILEINFO_SCHEMA """ - if not merkle_root: - merkle_root = self.metadata['current']['timestamp']['merkle_root'] + + # Modulus from https://en.wikipedia.org/wiki/RSA_numbers#RSA-2048 + # We will want to generate a new one + # This is duplicate code from repo lib, should live somewhere else + Modulus = "2519590847565789349402718324004839857142928212620403202777713783604366202070759555626401852588078" + \ + "4406918290641249515082189298559149176184502808489120072844992687392807287776735971418347270261896375014971" + \ + "8246911650776133798590957000973304597488084284017974291006424586918171951187461215151726546322822168699875" + \ + "4918242243363725908514186546204357679842338718477444792073993423658482382428119816381501067481045166037730" + \ + "6056201619676256133844143603833904414952634432190114657544454178424020924616515723350778707749817125772467" + \ + "962926386356373289912154831438167899885040445364023527381951378636564391212010397122822120720357" + m = int(Modulus, 10) + + + if not rsa_acc: + rsa_acc = self.metadata['current']['timestamp']['rsa_acc'] metadata_rolename = metadata_role + '-snapshot' - # Download Merkle path + # Download RSA accumulator proof upperbound_filelength = tuf.settings.MERKLE_FILELENGTH - self._update_merkle_metadata(metadata_rolename, upperbound_filelength, version) + self._update_rsa_acc_metadata(metadata_rolename, upperbound_filelength, version) metadata_directory = self.metadata_directory['current'] metadata_filename = metadata_rolename + '.json' metadata_filepath = os.path.join(metadata_directory, metadata_filename) # Ensure the metadata path is valid/exists, else ignore the call. if not os.path.exists(metadata_filepath): - # No merkle path found - raise tuf.exceptions.RepositoryError('No snapshot merkle file for ' + + # No RSA accumulator proof found + raise tuf.exceptions.RepositoryError('No snapshot rsa accumulator proof file for ' + metadata_role) try: - snapshot_merkle = securesystemslib.util.load_json_file( + snapshot_rsa_acc_proof = securesystemslib.util.load_json_file( metadata_filepath) # Although the metadata file may exist locally, it may not @@ -1907,46 +1915,23 @@ def verify_merkle_path(self, metadata_role, version=None, merkle_root=None): except securesystemslib.exceptions.Error: return - # verify the Merkle path - tuf.formats.SNAPSHOT_MERKLE_SCHEMA.check_match(snapshot_merkle) + # check the format + tuf.formats.SNAPSHOT_RSA_ACC_SCHEMA.check_match(snapshot_rsa_acc_proof) - # hash the contents to determine the leaf hash in the merkle tree - contents = snapshot_merkle['leaf_contents'] + # canonicalize the contents to determine the RSA accumulator prime + contents = snapshot_rsa_acc_proof['leaf_contents'] json_contents = securesystemslib.formats.encode_canonical(contents) - digest_object = securesystemslib.hash.digest() - digest_object.update((json_contents).encode('utf-8')) - node_hash = "a" + digest_object.hexdigest() - - # For each hash in the merkle_path, determine if the current node is - # a left of a right node using the path_directions, then combine - # the hash from merkle_path with the current node_hash to determine - # the next node_hash. At the end, the node_hash should match the hash - # in merkle_root - merkle_path = snapshot_merkle['merkle_path'] - path_directions = snapshot_merkle['path_directions'] - - # If merkle_path and path_directions have different lengths, - # the verification will not be possible - if len(merkle_path) != len(path_directions): - raise tuf.exceptions.RepositoryError('Invalid merkle path for ' + - metadata_role) - for index in range(len(merkle_path)): - i = str(index) - if path_directions[i] < 0: - # The current node is a left node - digest_object = securesystemslib.hash.digest() - digest_object.update((node_hash + merkle_path[i]).encode('utf-8')) - else: - # The current node is a right node - digest_object = securesystemslib.hash.digest() - digest_object.update((merkle_path[i] + node_hash).encode('utf-8')) - node_hash = "b" + digest_object.hexdigest() + prime = repository_lib.hash_to_prime(json_contents) + + # RSA accumulator proof + proof = snapshot_rsa_acc_proof['rsa_acc_proof'] + rsa_acc_proof_test = pow(proof, prime, m) - # Does the result match the merkle root? - if node_hash != merkle_root: - raise tuf.exceptions.RepositoryError('The merkle root ' + merkle_root + - ' does not match the hash ' + node_hash + ' for ' + metadata_role) + # Does the result match the RSA accumulator? + if rsa_acc_proof_test != rsa_acc: + raise tuf.exceptions.RepositoryError('RSA accumulator ' + rsa_acc + + ' does not match the proof ' + proof + ' for ' + metadata_role) # return the verified snapshot contents return contents @@ -2025,9 +2010,9 @@ def _update_metadata_if_changed(self, metadata_role, # Ensure the referenced metadata has been loaded. The 'root' role may be # updated without having 'snapshot' available. - # When snapshot merkle trees are used, there will not be a snapshot file. - # Instead, if the snapshot merkle file is missing, this will error below. - if 'merkle_root' not in self.metadata['current']['timestamp'] and referenced_metadata not in self.metadata['current']: + # When a snapshot rsa accumulator is used, there will not be a snapshot file. + # Instead, if the snapshot rsa proof is missing, this will error below. + if 'rsa_acc' not in self.metadata['current']['timestamp'] and referenced_metadata not in self.metadata['current']: raise tuf.exceptions.RepositoryError('Cannot update' ' ' + repr(metadata_role) + ' because ' + referenced_metadata + ' is' ' missing.') @@ -2039,9 +2024,9 @@ def _update_metadata_if_changed(self, metadata_role, repr(referenced_metadata)+ '. ' + repr(metadata_role) + ' may be updated.') - if 'merkle_root' in self.metadata['current']['timestamp']: - # Download version information from merkle tree - contents = self.verify_merkle_path(metadata_role) + if 'rsa_acc' in self.metadata['current']['timestamp']: + # Download version information from RSA accumulator proof + contents = self.verify_rsa_acc_proof(metadata_role) expected_versioninfo = contents else: @@ -2621,10 +2606,10 @@ def _refresh_targets_metadata(self, rolename='targets', roles_to_update = [] - # Add the role if it is listed in snapshot. If snapshot merkle - # trees are used, the snapshot check will be done later when - # the merkle tree is verified - if 'merkle_root' in self.metadata['current']['timestamp'] or rolename + '.json' in self.metadata['current']['snapshot']['meta']: + # Add the role if it is listed in snapshot. If a snapshot rsa + # accumulator is used, the snapshot check will be done later when + # the proof is verified + if 'rsa_acc' in self.metadata['current']['timestamp'] or rolename + '.json' in self.metadata['current']['snapshot']['meta']: roles_to_update.append(rolename) if refresh_all_delegated_roles: