Skip to content

Commit 95d6b78

Browse files
committed
Initial poc for RSA Accumulator snapshot: client side
This is missing test data for the client Signed-off-by: Marina Moore <mnm678@gmail.com>
1 parent 1e0d3f1 commit 95d6b78

File tree

2 files changed

+75
-90
lines changed

2 files changed

+75
-90
lines changed

tests/test_updater.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1779,29 +1779,29 @@ def test_13__targets_of_role(self):
17791779

17801780

17811781

1782-
def test_snapshot_merkle(self):
1783-
# replace timestamp with a merkle timestamp and create the updater
1784-
merkle_timestamp = os.path.join(self.repository_directory, 'metadata', 'timestamp-merkle.json')
1782+
def test_snapshot_rsa_acc(self):
1783+
# replace timestamp with an RSA accumulator timestamp and create the updater
1784+
rsa_acc_timestamp = os.path.join(self.repository_directory, 'metadata', 'timestamp-rsa.json')
17851785
timestamp = os.path.join(self.repository_directory, 'metadata', 'timestamp.json')
17861786

1787-
shutil.move(merkle_timestamp, timestamp)
1787+
shutil.move(rsa_acc_timestamp, timestamp)
17881788

17891789
repository_updater = updater.Updater(self.repository_name,
17901790
self.repository_mirrors)
17911791
repository_updater.refresh()
17921792

1793-
# Test verify merkle path
1794-
snapshot_info = repository_updater.verify_merkle_path('targets')
1793+
# Test verify RSA accumulator proof
1794+
snapshot_info = repository_updater.verify_rsa_acc_proof('targets')
17951795
self.assertEqual(snapshot_info['version'], 1)
17961796

1797-
snapshot_info = repository_updater.verify_merkle_path('role1')
1797+
snapshot_info = repository_updater.verify_rsa_acc_proof('role1')
17981798
self.assertEqual(snapshot_info['version'], 1)
17991799

1800-
# verify merkle path with invalid role
1800+
# verify RSA accumulator with invalid role
18011801
self.assertRaises(tuf.exceptions.NoWorkingMirrorError,
1802-
repository_updater.verify_merkle_path, 'foo')
1802+
repository_updater.verify_rsa_acc_proof, 'foo')
18031803

1804-
# Test get_one_valid_targetinfo with snapshot merkle
1804+
# Test get_one_valid_targetinfo with snapshot RSA accumulator
18051805
repository_updater.get_one_valid_targetinfo('file1.txt')
18061806

18071807

tuf/client/updater.py

Lines changed: 65 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,9 +1081,9 @@ def refresh(self, unsafely_update_root_if_necessary=True):
10811081
# require strict checks on its required length.
10821082
self._update_metadata('timestamp', DEFAULT_TIMESTAMP_UPPERLENGTH)
10831083

1084-
if 'merkle_root' not in self.metadata['current']['timestamp']:
1085-
# If merkle root is set, do not update snapshot metadata. Instead,
1086-
# we will download the relevant merkle path later when downloading
1084+
if 'rsa_acc' not in self.metadata['current']['timestamp']:
1085+
# If an RSA Accumulator is defined, do not update snapshot metadata. Instead,
1086+
# we will download the relevant proof files later when downloading
10871087
# a target.
10881088
self._update_metadata_if_changed('snapshot',
10891089
referenced_metadata='timestamp')
@@ -1489,11 +1489,6 @@ def _get_metadata_file(self, metadata_role, remote_filename,
14891489
The expected and required version number of the 'metadata_role' file
14901490
downloaded. 'expected_version' is an integer.
14911491
1492-
snapshot_merkle:
1493-
Is the metadata file a snapshot merkle file? Snapshot merkle files
1494-
are not signed and so should skip some of the verification steps here.
1495-
Instead, they must be verified using verify_merkle_path.
1496-
14971492
<Exceptions>
14981493
tuf.exceptions.NoWorkingMirrorError:
14991494
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):
16311626

16321627

16331628

1634-
def _update_merkle_metadata(self, merkle_filename, upperbound_filelength,
1629+
def _update_rsa_acc_metadata(self, proof_filename, upperbound_filelength,
16351630
version=None):
16361631
"""
16371632
<Purpose>
1638-
Non-public method that downloads, verifies, and 'installs' the merkle
1639-
metadata belonging to 'merkle_filename'. Calling this method implies
1640-
that the 'merkle_filename' on the repository is newer than the client's,
1633+
Non-public method that downloads, verifies, and 'installs' the proof
1634+
metadata belonging to 'proof_filename'. Calling this method implies
1635+
that the 'proof_filename' on the repository is newer than the client's,
16411636
and thus needs to be re-downloaded. The current and previous metadata
16421637
stores are updated if the newly downloaded metadata is successfully
16431638
downloaded and verified. This method also assumes that the store of
16441639
top-level metadata is the latest and exists.
16451640
16461641
<Arguments>
1647-
merkle_filename:
1648-
The name of the metadata. This is a merkle tree file and should
1649-
not end in '.json'. Examples: 'role1-merkle', 'targets-merkle'
1642+
proof_filename:
1643+
The name of the metadata. This is an RSA accumulator proof file and should
1644+
not end in '.json'. Examples: 'role1-snapshot', 'targets-snapshot'
16501645
16511646
upperbound_filelength:
16521647
The expected length, or upper bound, of the metadata file to be
16531648
downloaded.
16541649
16551650
version:
1656-
The expected and required version number of the 'merkle_filename' file
1651+
The expected and required version number of the 'proof_filename' file
16571652
downloaded. 'version' is an integer.
16581653
16591654
<Exceptions>
@@ -1663,7 +1658,7 @@ def _update_merkle_metadata(self, merkle_filename, upperbound_filelength,
16631658
metadata have been tried and failed.
16641659
16651660
<Side Effects>
1666-
The metadata file belonging to 'merkle_filenaem' is downloaded from a
1661+
The metadata file belonging to 'proof_filename' is downloaded from a
16671662
repository mirror. If the metadata is valid, it is stored in the
16681663
metadata store.
16691664
@@ -1673,7 +1668,7 @@ def _update_merkle_metadata(self, merkle_filename, upperbound_filelength,
16731668

16741669
# Construct the metadata filename as expected by the download/mirror
16751670
# modules.
1676-
metadata_filename = merkle_filename + '.json'
1671+
metadata_filename = proof_filename + '.json'
16771672

16781673
# Attempt a file download from each mirror until the file is downloaded and
16791674
# verified. If the signature of the downloaded file is valid, proceed,
@@ -1703,7 +1698,7 @@ def _update_merkle_metadata(self, merkle_filename, upperbound_filelength,
17031698
verification_fn = None
17041699

17051700
metadata_file_object = \
1706-
self._get_metadata_file(merkle_filename, remote_filename,
1701+
self._get_metadata_file(proof_filename, remote_filename,
17071702
upperbound_filelength, version, verification_fn)
17081703

17091704
# The metadata has been verified. Move the metadata file into place.
@@ -1732,16 +1727,16 @@ def _update_merkle_metadata(self, merkle_filename, upperbound_filelength,
17321727

17331728
# Extract the metadata object so we can store it to the metadata store.
17341729
# 'current_metadata_object' set to 'None' if there is not an object
1735-
# stored for 'merkle_filename'.
1736-
current_metadata_object = self.metadata['current'].get(merkle_filename)
1730+
# stored for 'proof_filename'.
1731+
current_metadata_object = self.metadata['current'].get(proof_filename)
17371732

17381733
# Finally, update the metadata and fileinfo stores, and rebuild the
1739-
# key and role info for the top-level roles if 'merkle_filename' is root.
1734+
# key and role info for the top-level roles if 'proof_filename' is root.
17401735
# Rebuilding the key and role info is required if the newly-installed
17411736
# root metadata has revoked keys or updated any top-level role information.
17421737
logger.debug('Updated ' + repr(current_filepath) + '.')
1743-
self.metadata['previous'][merkle_filename] = current_metadata_object
1744-
self.metadata['current'][merkle_filename] = updated_metadata_object
1738+
self.metadata['previous'][proof_filename] = current_metadata_object
1739+
self.metadata['current'][proof_filename] = updated_metadata_object
17451740

17461741

17471742

@@ -1865,39 +1860,52 @@ def _update_metadata(self, metadata_role, upperbound_filelength, version=None):
18651860

18661861

18671862

1868-
def verify_merkle_path(self, metadata_role, version=None, merkle_root=None):
1863+
def verify_rsa_acc_proof(self, metadata_role, version=None, rsa_acc=None):
18691864
"""
18701865
<Purpose>
1871-
Download the merkle path associated with metadata_role and verify the hashes.
1866+
Download the RSA accumulator proof associated with metadata_role and verify the hashes.
18721867
<Arguments>
18731868
metadata_role:
18741869
The name of the metadata role. This should not include a file extension.
18751870
<Exceptions>
18761871
tuf.exceptions.RepositoryError:
1877-
If the snapshot merkle file is invalid or the verification fails
1872+
If the snapshot rsa accumulator file is invalid or the verification fails
18781873
<Returns>
18791874
A dictionary containing the snapshot information about metadata role,
18801875
conforming to VERSIONINFO_SCHEMA or METADATA_FILEINFO_SCHEMA
18811876
"""
1882-
if not merkle_root:
1883-
merkle_root = self.metadata['current']['timestamp']['merkle_root']
1877+
1878+
# Modulus from https://en.wikipedia.org/wiki/RSA_numbers#RSA-2048
1879+
# We will want to generate a new one
1880+
# This is duplicate code from repo lib, should live somewhere else
1881+
Modulus = "2519590847565789349402718324004839857142928212620403202777713783604366202070759555626401852588078" + \
1882+
"4406918290641249515082189298559149176184502808489120072844992687392807287776735971418347270261896375014971" + \
1883+
"8246911650776133798590957000973304597488084284017974291006424586918171951187461215151726546322822168699875" + \
1884+
"4918242243363725908514186546204357679842338718477444792073993423658482382428119816381501067481045166037730" + \
1885+
"6056201619676256133844143603833904414952634432190114657544454178424020924616515723350778707749817125772467" + \
1886+
"962926386356373289912154831438167899885040445364023527381951378636564391212010397122822120720357"
1887+
m = int(Modulus, 10)
1888+
1889+
1890+
if not rsa_acc:
1891+
rsa_acc = self.metadata['current']['timestamp']['rsa_acc']
18841892

18851893
metadata_rolename = metadata_role + '-snapshot'
18861894

1887-
# Download Merkle path
1895+
# Download RSA accumulator proof
18881896
upperbound_filelength = tuf.settings.MERKLE_FILELENGTH
1889-
self._update_merkle_metadata(metadata_rolename, upperbound_filelength, version)
1897+
self._update_rsa_acc_metadata(metadata_rolename, upperbound_filelength, version)
18901898
metadata_directory = self.metadata_directory['current']
18911899
metadata_filename = metadata_rolename + '.json'
18921900
metadata_filepath = os.path.join(metadata_directory, metadata_filename)
18931901

18941902
# Ensure the metadata path is valid/exists, else ignore the call.
18951903
if not os.path.exists(metadata_filepath):
1896-
# No merkle path found
1897-
raise tuf.exceptions.RepositoryError('No snapshot merkle file for ' +
1904+
# No RSA accumulator proof found
1905+
raise tuf.exceptions.RepositoryError('No snapshot rsa accumulator proof file for ' +
18981906
metadata_role)
18991907
try:
1900-
snapshot_merkle = securesystemslib.util.load_json_file(
1908+
snapshot_rsa_acc_proof = securesystemslib.util.load_json_file(
19011909
metadata_filepath)
19021910

19031911
# 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):
19071915
except securesystemslib.exceptions.Error:
19081916
return
19091917

1910-
# verify the Merkle path
1911-
tuf.formats.SNAPSHOT_MERKLE_SCHEMA.check_match(snapshot_merkle)
1918+
# check the format
1919+
tuf.formats.SNAPSHOT_RSA_ACC_SCHEMA.check_match(snapshot_rsa_acc_proof)
19121920

1913-
# hash the contents to determine the leaf hash in the merkle tree
1914-
contents = snapshot_merkle['leaf_contents']
1921+
# canonicalize the contents to determine the RSA accumulator prime
1922+
contents = snapshot_rsa_acc_proof['leaf_contents']
19151923
json_contents = securesystemslib.formats.encode_canonical(contents)
1916-
digest_object = securesystemslib.hash.digest()
1917-
digest_object.update((json_contents).encode('utf-8'))
1918-
node_hash = "a" + digest_object.hexdigest()
1919-
1920-
# For each hash in the merkle_path, determine if the current node is
1921-
# a left of a right node using the path_directions, then combine
1922-
# the hash from merkle_path with the current node_hash to determine
1923-
# the next node_hash. At the end, the node_hash should match the hash
1924-
# in merkle_root
1925-
merkle_path = snapshot_merkle['merkle_path']
1926-
path_directions = snapshot_merkle['path_directions']
1927-
1928-
# If merkle_path and path_directions have different lengths,
1929-
# the verification will not be possible
1930-
if len(merkle_path) != len(path_directions):
1931-
raise tuf.exceptions.RepositoryError('Invalid merkle path for ' +
1932-
metadata_role)
19331924

1934-
for index in range(len(merkle_path)):
1935-
i = str(index)
1936-
if path_directions[i] < 0:
1937-
# The current node is a left node
1938-
digest_object = securesystemslib.hash.digest()
1939-
digest_object.update((node_hash + merkle_path[i]).encode('utf-8'))
1940-
else:
1941-
# The current node is a right node
1942-
digest_object = securesystemslib.hash.digest()
1943-
digest_object.update((merkle_path[i] + node_hash).encode('utf-8'))
1944-
node_hash = "b" + digest_object.hexdigest()
1925+
prime = repository_lib.hash_to_prime(json_contents)
1926+
1927+
# RSA accumulator proof
1928+
proof = snapshot_rsa_acc_proof['rsa_acc_proof']
1929+
rsa_acc_proof_test = pow(proof, prime, m)
19451930

1946-
# Does the result match the merkle root?
1947-
if node_hash != merkle_root:
1948-
raise tuf.exceptions.RepositoryError('The merkle root ' + merkle_root +
1949-
' does not match the hash ' + node_hash + ' for ' + metadata_role)
1931+
# Does the result match the RSA accumulator?
1932+
if rsa_acc_proof_test != rsa_acc:
1933+
raise tuf.exceptions.RepositoryError('RSA accumulator ' + rsa_acc +
1934+
' does not match the proof ' + proof + ' for ' + metadata_role)
19501935

19511936
# return the verified snapshot contents
19521937
return contents
@@ -2025,9 +2010,9 @@ def _update_metadata_if_changed(self, metadata_role,
20252010

20262011
# Ensure the referenced metadata has been loaded. The 'root' role may be
20272012
# updated without having 'snapshot' available.
2028-
# When snapshot merkle trees are used, there will not be a snapshot file.
2029-
# Instead, if the snapshot merkle file is missing, this will error below.
2030-
if 'merkle_root' not in self.metadata['current']['timestamp'] and referenced_metadata not in self.metadata['current']:
2013+
# When a snapshot rsa accumulator is used, there will not be a snapshot file.
2014+
# Instead, if the snapshot rsa proof is missing, this will error below.
2015+
if 'rsa_acc' not in self.metadata['current']['timestamp'] and referenced_metadata not in self.metadata['current']:
20312016
raise tuf.exceptions.RepositoryError('Cannot update'
20322017
' ' + repr(metadata_role) + ' because ' + referenced_metadata + ' is'
20332018
' missing.')
@@ -2039,9 +2024,9 @@ def _update_metadata_if_changed(self, metadata_role,
20392024
repr(referenced_metadata)+ '. ' + repr(metadata_role) +
20402025
' may be updated.')
20412026

2042-
if 'merkle_root' in self.metadata['current']['timestamp']:
2043-
# Download version information from merkle tree
2044-
contents = self.verify_merkle_path(metadata_role)
2027+
if 'rsa_acc' in self.metadata['current']['timestamp']:
2028+
# Download version information from RSA accumulator proof
2029+
contents = self.verify_rsa_acc_proof(metadata_role)
20452030
expected_versioninfo = contents
20462031

20472032
else:
@@ -2621,10 +2606,10 @@ def _refresh_targets_metadata(self, rolename='targets',
26212606

26222607
roles_to_update = []
26232608

2624-
# Add the role if it is listed in snapshot. If snapshot merkle
2625-
# trees are used, the snapshot check will be done later when
2626-
# the merkle tree is verified
2627-
if 'merkle_root' in self.metadata['current']['timestamp'] or rolename + '.json' in self.metadata['current']['snapshot']['meta']:
2609+
# Add the role if it is listed in snapshot. If a snapshot rsa
2610+
# accumulator is used, the snapshot check will be done later when
2611+
# the proof is verified
2612+
if 'rsa_acc' in self.metadata['current']['timestamp'] or rolename + '.json' in self.metadata['current']['snapshot']['meta']:
26282613
roles_to_update.append(rolename)
26292614

26302615
if refresh_all_delegated_roles:

0 commit comments

Comments
 (0)