Skip to content

Commit

Permalink
Initial poc for RSA Accumulator snapshot: client side
Browse files Browse the repository at this point in the history
This is missing test data for the client

Signed-off-by: Marina Moore <[email protected]>
  • Loading branch information
mnm678 committed Jul 27, 2021
1 parent 1e0d3f1 commit 95d6b78
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 90 deletions.
20 changes: 10 additions & 10 deletions tests/test_updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')


Expand Down
145 changes: 65 additions & 80 deletions tuf/client/updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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.
<Exceptions>
tuf.exceptions.NoWorkingMirrorError:
The metadata could not be fetched. This is raised only when all known
Expand Down Expand Up @@ -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):
"""
<Purpose>
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.
<Arguments>
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.
<Exceptions>
Expand All @@ -1663,7 +1658,7 @@ def _update_merkle_metadata(self, merkle_filename, upperbound_filelength,
metadata have been tried and failed.
<Side Effects>
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.
Expand All @@ -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,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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



Expand Down Expand Up @@ -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):
"""
<Purpose>
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.
<Arguments>
metadata_role:
The name of the metadata role. This should not include a file extension.
<Exceptions>
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
<Returns>
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
Expand All @@ -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
Expand Down Expand Up @@ -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.')
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit 95d6b78

Please sign in to comment.