@@ -1085,7 +1085,8 @@ def refresh(self, unsafely_update_root_if_necessary=True):
10851085
10861086 if 'merkle_root' not in self .metadata ['current' ]['timestamp' ]:
10871087 # If merkle root is set, do not update snapshot metadata. Instead,
1088- # download the relevant merkle path when downloading a target.
1088+ # we will download the relevant merkle path later when downloading
1089+ # a target.
10891090 self ._update_metadata_if_changed ('snapshot' ,
10901091 referenced_metadata = 'timestamp' )
10911092 self ._update_metadata_if_changed ('targets' )
@@ -1753,8 +1754,124 @@ def _get_file(self, filepath, verify_file_function, file_type, file_length,
17531754
17541755
17551756
1756- def _update_metadata (self , metadata_role , upperbound_filelength , version = None ,
1757- snapshot_merkle = False ):
1757+ def _update_merkle_metadata (self , merkle_filename , upperbound_filelength ,
1758+ version = None ):
1759+ """
1760+ <Purpose>
1761+ Non-public method that downloads, verifies, and 'installs' the merkle
1762+ metadata belonging to 'merkle_filename'. Calling this method implies
1763+ that the 'merkle_filename' on the repository is newer than the client's,
1764+ and thus needs to be re-downloaded. The current and previous metadata
1765+ stores are updated if the newly downloaded metadata is successfully
1766+ downloaded and verified. This method also assumes that the store of
1767+ top-level metadata is the latest and exists.
1768+
1769+ <Arguments>
1770+ merkle_filename:
1771+ The name of the metadata. This is a merkle tree file and should
1772+ not end in '.json'. Examples: 'role1-merkle', 'targets-merkle'
1773+
1774+ upperbound_filelength:
1775+ The expected length, or upper bound, of the metadata file to be
1776+ downloaded.
1777+
1778+ version:
1779+ The expected and required version number of the 'merkle_filename' file
1780+ downloaded. 'expected_version' is an integer.
1781+
1782+ <Exceptions>
1783+ tuf.exceptions.NoWorkingMirrorError:
1784+ The metadata cannot be updated. This is not specific to a single
1785+ failure but rather indicates that all possible ways to update the
1786+ metadata have been tried and failed.
1787+
1788+ <Side Effects>
1789+ The metadata file belonging to 'merkle_filenaem' is downloaded from a
1790+ repository mirror. If the metadata is valid, it is stored in the
1791+ metadata store.
1792+
1793+ <Returns>
1794+ None.
1795+ """
1796+
1797+ # Construct the metadata filename as expected by the download/mirror
1798+ # modules.
1799+ metadata_filename = merkle_filename + '.json'
1800+
1801+ # Attempt a file download from each mirror until the file is downloaded and
1802+ # verified. If the signature of the downloaded file is valid, proceed,
1803+ # otherwise log a warning and try the next mirror. 'metadata_file_object'
1804+ # is the file-like object returned by 'download.py'. 'metadata_signable'
1805+ # is the object extracted from 'metadata_file_object'. Metadata saved to
1806+ # files are regarded as 'signable' objects, conformant to
1807+ # 'tuf.formats.SIGNABLE_SCHEMA'.
1808+ #
1809+ # Some metadata (presently timestamp) will be downloaded "unsafely", in the
1810+ # sense that we can only estimate its true length and know nothing about
1811+ # its version. This is because not all metadata will have other metadata
1812+ # for it; otherwise we will have an infinite regress of metadata signing
1813+ # for each other. In this case, we will download the metadata up to the
1814+ # best length we can get for it, not request a specific version, but
1815+ # perform the rest of the checks (e.g., signature verification).
1816+
1817+ remote_filename = metadata_filename
1818+ filename_version = ''
1819+
1820+ if self .consistent_snapshot and version :
1821+ filename_version = version
1822+ dirname , basename = os .path .split (remote_filename )
1823+ remote_filename = os .path .join (
1824+ dirname , str (filename_version ) + '.' + basename )
1825+
1826+ verification_fn = None
1827+
1828+ metadata_file_object = \
1829+ self ._get_metadata_file (merkle_filename , remote_filename ,
1830+ upperbound_filelength , version , verification_fn )
1831+
1832+ # The metadata has been verified. Move the metadata file into place.
1833+ # First, move the 'current' metadata file to the 'previous' directory
1834+ # if it exists.
1835+ current_filepath = os .path .join (self .metadata_directory ['current' ],
1836+ metadata_filename )
1837+ current_filepath = os .path .abspath (current_filepath )
1838+ securesystemslib .util .ensure_parent_dir (current_filepath )
1839+
1840+ previous_filepath = os .path .join (self .metadata_directory ['previous' ],
1841+ metadata_filename )
1842+ previous_filepath = os .path .abspath (previous_filepath )
1843+
1844+ if os .path .exists (current_filepath ):
1845+ # Previous metadata might not exist, say when delegations are added.
1846+ securesystemslib .util .ensure_parent_dir (previous_filepath )
1847+ shutil .move (current_filepath , previous_filepath )
1848+
1849+ # Next, move the verified updated metadata file to the 'current' directory.
1850+ metadata_file_object .seek (0 )
1851+ updated_metadata_object = \
1852+ securesystemslib .util .load_json_string (metadata_file_object .read ().decode ('utf-8' ))
1853+
1854+ securesystemslib .util .persist_temp_file (metadata_file_object , current_filepath )
1855+
1856+ # Extract the metadata object so we can store it to the metadata store.
1857+ # 'current_metadata_object' set to 'None' if there is not an object
1858+ # stored for 'merkle_filename'.
1859+ current_metadata_object = self .metadata ['current' ].get (merkle_filename )
1860+
1861+ # Finally, update the metadata and fileinfo stores, and rebuild the
1862+ # key and role info for the top-level roles if 'merkle_filename' is root.
1863+ # Rebuilding the key and role info is required if the newly-installed
1864+ # root metadata has revoked keys or updated any top-level role information.
1865+ logger .debug ('Updated ' + repr (current_filepath ) + '.' )
1866+ self .metadata ['previous' ][merkle_filename ] = current_metadata_object
1867+ self .metadata ['current' ][merkle_filename ] = updated_metadata_object
1868+
1869+
1870+
1871+
1872+
1873+
1874+ def _update_metadata (self , metadata_role , upperbound_filelength , version = None ):
17581875 """
17591876 <Purpose>
17601877 Non-public method that downloads, verifies, and 'installs' the metadata
@@ -1778,11 +1895,6 @@ def _update_metadata(self, metadata_role, upperbound_filelength, version=None,
17781895 The expected and required version number of the 'metadata_role' file
17791896 downloaded. 'expected_version' is an integer.
17801897
1781- snapshot_merkle:
1782- Is the metadata to be updated for a snapshot merkle file?
1783- Snapshot merkle metadata does not contain a signature, but must
1784- instead be verified using _verify_merkle_path.
1785-
17861898 <Exceptions>
17871899 tuf.exceptions.NoWorkingMirrorError:
17881900 The metadata cannot be updated. This is not specific to a single
@@ -1827,9 +1939,7 @@ def _update_metadata(self, metadata_role, upperbound_filelength, version=None,
18271939 remote_filename = os .path .join (
18281940 dirname , str (filename_version ) + '.' + basename )
18291941
1830- verification_fn = None
1831- if not snapshot_merkle :
1832- verification_fn = self .signable_verification
1942+ verification_fn = self .signable_verification
18331943
18341944 metadata_file_object = \
18351945 self ._get_metadata_file (metadata_role , remote_filename ,
@@ -1862,11 +1972,7 @@ def _update_metadata(self, metadata_role, upperbound_filelength, version=None,
18621972 # Extract the metadata object so we can store it to the metadata store.
18631973 # 'current_metadata_object' set to 'None' if there is not an object
18641974 # stored for 'metadata_role'.
1865- if snapshot_merkle :
1866- # Snaphot merkle files are not signed
1867- updated_metadata_object = metadata_signable
1868- else :
1869- updated_metadata_object = metadata_signable ['signed' ]
1975+ updated_metadata_object = metadata_signable ['signed' ]
18701976 current_metadata_object = self .metadata ['current' ].get (metadata_role )
18711977
18721978 # Finally, update the metadata and fileinfo stores, and rebuild the
@@ -1876,8 +1982,7 @@ def _update_metadata(self, metadata_role, upperbound_filelength, version=None,
18761982 logger .debug ('Updated ' + repr (current_filepath ) + '.' )
18771983 self .metadata ['previous' ][metadata_role ] = current_metadata_object
18781984 self .metadata ['current' ][metadata_role ] = updated_metadata_object
1879- if not snapshot_merkle :
1880- self ._update_versioninfo (metadata_filename )
1985+ self ._update_versioninfo (metadata_filename )
18811986
18821987
18831988
@@ -1893,76 +1998,81 @@ def _verify_merkle_path(self, metadata_role):
18931998 <Exceptions>
18941999 tuf.exceptions.RepositoryError:
18952000 If the snapshot merkle file is invalid or the verification fails
1896- Returns the snapshot information about metadata role.
2001+ <Returns>
2002+ A dictionary containing the snapshot information about metadata role,
2003+ conforming to VERSIONINFO_SCHEMA or METADATA_FILEINFO_SCHEMA
18972004 """
18982005 merkle_root = self .metadata ['current' ]['timestamp' ]['merkle_root' ]
18992006
2007+ metadata_rolename = metadata_role + '-snapshot'
2008+
19002009 # Download Merkle path
1901- self ._update_metadata (metadata_role + '-snapshot' , 1000 , snapshot_merkle = True )
2010+ upperbound_filelength = tuf .settings .MERKLE_FILELENGTH
2011+ self ._update_merkle_metadata (metadata_rolename , upperbound_filelength )
19022012 metadata_directory = self .metadata_directory ['current' ]
1903- metadata_filename = metadata_role + '-snapshot .json'
2013+ metadata_filename = metadata_rolename + '.json'
19042014 metadata_filepath = os .path .join (metadata_directory , metadata_filename )
1905- # Ensure the metadata path is valid/exists, else ignore the call.
1906- if os .path .exists (metadata_filepath ):
1907- try :
1908- snapshot_merkle = securesystemslib .util .load_json_file (
1909- metadata_filepath )
19102015
1911- # Although the metadata file may exist locally, it may not
1912- # be a valid json file. On the next refresh cycle, it will be
1913- # updated as required. If Root if cannot be loaded from disk
1914- # successfully, an exception should be raised by the caller.
1915- except securesystemslib .exceptions .Error :
1916- return
2016+ # Ensure the metadata path is valid/exists, else ignore the call.
2017+ if not os .path .exists (metadata_filepath ):
2018+ # No merkle path found
2019+ raise tuf .exceptions .RepositoryError ('No snapshot merkle file for ' +
2020+ metadata_role )
2021+ try :
2022+ snapshot_merkle = securesystemslib .util .load_json_file (
2023+ metadata_filepath )
2024+
2025+ # Although the metadata file may exist locally, it may not
2026+ # be a valid json file. On the next refresh cycle, it will be
2027+ # updated as required. If Root if cannot be loaded from disk
2028+ # successfully, an exception should be raised by the caller.
2029+ except securesystemslib .exceptions .Error :
2030+ return
19172031
1918- # verify the Merkle path
1919- tuf .formats .SNAPSHOT_MERKLE_SCHEMA .check_match (snapshot_merkle )
2032+ # verify the Merkle path
2033+ tuf .formats .SNAPSHOT_MERKLE_SCHEMA .check_match (snapshot_merkle )
2034+
2035+ # hash the contents to determine the leaf hash in the merkle tree
2036+ contents = snapshot_merkle ['leaf_contents' ]
2037+ json_contents = securesystemslib .formats .encode_canonical (contents )
2038+ digest_object = securesystemslib .hash .digest ()
2039+ digest_object .update ((json_contents ).encode ('utf-8' ))
2040+ node_hash = digest_object .hexdigest ()
2041+
2042+ # For each hash in the merkle_path, determine if the current node is
2043+ # a left of a right node using the path_directions, then combine
2044+ # the hash from merkle_path with the current node_hash to determine
2045+ # the next node_hash. At the end, the node_hash should match the hash
2046+ # in merkle_root
2047+ merkle_path = snapshot_merkle ['merkle_path' ]
2048+ path_directions = snapshot_merkle ['path_directions' ]
2049+
2050+ # If merkle_path and path_directions have different lengths,
2051+ # the verification will not be possible
2052+ if len (merkle_path ) != len (path_directions ):
2053+ raise tuf .exceptions .RepositoryError ('Invalid merkle path for ' +
2054+ metadata_role )
19202055
1921- # hash the contents to determine the leaf hash in the merkle tree
1922- contents = snapshot_merkle ['leaf_contents' ]
1923- json_contents = securesystemslib .formats .encode_canonical (contents )
1924- digest_object = securesystemslib .hash .digest ()
1925- digest_object .update ((json_contents ).encode ('utf-8' ))
2056+ for index in range (len (merkle_path )):
2057+ i = str (index )
2058+ if path_directions [i ] < 0 :
2059+ # The current node is a left node
2060+ digest_object = securesystemslib .hash .digest ()
2061+ digest_object .update ((node_hash + merkle_path [i ]).encode ('utf-8' ))
2062+ else :
2063+ # The current node is a right node
2064+ digest_object = securesystemslib .hash .digest ()
2065+ digest_object .update ((merkle_path [i ] + node_hash ).encode ('utf-8' ))
19262066 node_hash = digest_object .hexdigest ()
19272067
1928- # For each hash in the merkle_path, determine if the current node is
1929- # a left of a right node using the path_directions, then combine
1930- # the hash from merkle_path with the current node_hash to determine
1931- # the next node_hash. At the end, the node_hash should match the hash
1932- # in merkle_root
1933- merkle_path = snapshot_merkle ['merkle_path' ]
1934- path_directions = snapshot_merkle ['path_directions' ]
1935-
1936- # If merkle_path and path_directions have different lengths,
1937- # the verification will not be possible
1938- if len (merkle_path ) != len (path_directions ):
1939- raise tuf .exceptions .RepositoryError ('Invalid merkle path for ' +
1940- metadata_role )
1941-
1942- for index in range (len (merkle_path )):
1943- i = str (index )
1944- if path_directions [i ] < 0 :
1945- # The current node is a left node
1946- digest_object = securesystemslib .hash .digest ()
1947- digest_object .update ((node_hash + merkle_path [i ]).encode ('utf-8' ))
1948- else :
1949- # The current node is a right node
1950- digest_object = securesystemslib .hash .digest ()
1951- digest_object .update ((merkle_path [i ] + node_hash ).encode ('utf-8' ))
1952- node_hash = digest_object .hexdigest ()
1953-
1954- # Does the result match the merkle root?
1955- if node_hash != merkle_root :
1956- raise tuf .exceptions .RepositoryError ('The merkle root ' + merkle_root +
1957- ' does not match the hash ' + node_hash + ' for ' + metadata_role )
2068+ # Does the result match the merkle root?
2069+ if node_hash != merkle_root :
2070+ raise tuf .exceptions .RepositoryError ('The merkle root ' + merkle_root +
2071+ ' does not match the hash ' + node_hash + ' for ' + metadata_role )
19582072
1959- # return the verified snapshot contents
1960- return contents
2073+ # return the verified snapshot contents
2074+ return contents
19612075
1962- else :
1963- # No merkle path found
1964- raise tuf .exceptions .RepositoryError ('No snapshot merkle file for ' +
1965- metadata_role )
19662076
19672077
19682078
@@ -2637,7 +2747,7 @@ def _refresh_targets_metadata(self, rolename='targets',
26372747 roles_to_update = []
26382748
26392749 # Add the role if it is listed in snapshot. If snapshot merkle
2640- # trees are used, the snaphot check will be done later when
2750+ # trees are used, the snapshot check will be done later when
26412751 # the merkle tree is verified
26422752 if 'merkle_root' in self .metadata ['current' ]['timestamp' ] or rolename + '.json' in self .metadata ['current' ]['snapshot' ]['meta' ]:
26432753 roles_to_update .append (rolename )
0 commit comments