@@ -1087,8 +1087,12 @@ def refresh(self, unsafely_update_root_if_necessary=True):
1087
1087
# require strict checks on its required length.
1088
1088
self ._update_metadata ('timestamp' , DEFAULT_TIMESTAMP_UPPERLENGTH )
1089
1089
1090
- self ._update_metadata_if_changed ('snapshot' ,
1091
- referenced_metadata = 'timestamp' )
1090
+ if 'rsa_acc' not in self .metadata ['current' ]['timestamp' ]:
1091
+ # If an RSA Accumulator is defined, do not update snapshot metadata. Instead,
1092
+ # we will download the relevant proof files later when downloading
1093
+ # a target.
1094
+ self ._update_metadata_if_changed ('snapshot' ,
1095
+ referenced_metadata = 'timestamp' )
1092
1096
self ._update_metadata_if_changed ('targets' )
1093
1097
1094
1098
@@ -1616,6 +1620,206 @@ def _get_metadata_file(self, metadata_role, remote_filename,
1616
1620
1617
1621
1618
1622
1623
+ def signable_verification (self , metadata_role , file_object , expected_version ):
1624
+ # Verify 'file_object' according to the callable function.
1625
+ # 'file_object' is also verified if decompressed above (i.e., the
1626
+ # uncompressed version).
1627
+ metadata_signable = \
1628
+ securesystemslib .util .load_json_string (file_object .read ().decode ('utf-8' ))
1629
+
1630
+ # Determine if the specification version number is supported. It is
1631
+ # assumed that "spec_version" is in (major.minor.fix) format, (for
1632
+ # example: "1.4.3") and that releases with the same major version
1633
+ # number maintain backwards compatibility. Consequently, if the major
1634
+ # version number of new metadata equals our expected major version
1635
+ # number, the new metadata is safe to parse.
1636
+ try :
1637
+ metadata_spec_version = metadata_signable ['signed' ]['spec_version' ]
1638
+ metadata_spec_version_split = metadata_spec_version .split ('.' )
1639
+ metadata_spec_major_version = int (metadata_spec_version_split [0 ])
1640
+ metadata_spec_minor_version = int (metadata_spec_version_split [1 ])
1641
+
1642
+ code_spec_version_split = tuf .SPECIFICATION_VERSION .split ('.' )
1643
+ code_spec_major_version = int (code_spec_version_split [0 ])
1644
+ code_spec_minor_version = int (code_spec_version_split [1 ])
1645
+
1646
+ if metadata_spec_major_version != code_spec_major_version :
1647
+ raise tuf .exceptions .UnsupportedSpecificationError (
1648
+ 'Downloaded metadata that specifies an unsupported '
1649
+ 'spec_version. This code supports major version number: ' +
1650
+ repr (code_spec_major_version ) + '; however, the obtained '
1651
+ 'metadata lists version number: ' + str (metadata_spec_version ))
1652
+
1653
+ #report to user if minor versions do not match, continue with update
1654
+ if metadata_spec_minor_version != code_spec_minor_version :
1655
+ logger .info ("Downloaded metadata that specifies a different minor " +
1656
+ "spec_version. This code has version " +
1657
+ str (tuf .SPECIFICATION_VERSION ) +
1658
+ " and the metadata lists version number " +
1659
+ str (metadata_spec_version ) +
1660
+ ". The update will continue as the major versions match." )
1661
+
1662
+ except (ValueError , TypeError ) as error :
1663
+ six .raise_from (securesystemslib .exceptions .FormatError ('Improperly'
1664
+ ' formatted spec_version, which must be in major.minor.fix format' ),
1665
+ error )
1666
+
1667
+ # If the version number is unspecified, ensure that the version number
1668
+ # downloaded is greater than the currently trusted version number for
1669
+ # 'metadata_role'.
1670
+ version_downloaded = metadata_signable ['signed' ]['version' ]
1671
+
1672
+ if expected_version is not None :
1673
+ # Verify that the downloaded version matches the version expected by
1674
+ # the caller.
1675
+ if version_downloaded != expected_version :
1676
+ raise tuf .exceptions .BadVersionNumberError ('Downloaded'
1677
+ ' version number: ' + repr (version_downloaded ) + '. Version'
1678
+ ' number MUST be: ' + repr (expected_version ))
1679
+
1680
+ # The caller does not know which version to download. Verify that the
1681
+ # downloaded version is at least greater than the one locally
1682
+ # available.
1683
+ else :
1684
+ # Verify that the version number of the locally stored
1685
+ # 'timestamp.json', if available, is less than what was downloaded.
1686
+ # Otherwise, accept the new timestamp with version number
1687
+ # 'version_downloaded'.
1688
+
1689
+ try :
1690
+ current_version = \
1691
+ self .metadata ['current' ][metadata_role ]['version' ]
1692
+
1693
+ if version_downloaded < current_version :
1694
+ raise tuf .exceptions .ReplayedMetadataError (metadata_role ,
1695
+ version_downloaded , current_version )
1696
+
1697
+ except KeyError :
1698
+ logger .info (metadata_role + ' not available locally.' )
1699
+
1700
+ self ._verify_metadata_file (file_object , metadata_role )
1701
+
1702
+
1703
+
1704
+
1705
+
1706
+
1707
+ def _update_rsa_acc_metadata (self , proof_filename , upperbound_filelength ,
1708
+ version = None ):
1709
+ """
1710
+ <Purpose>
1711
+ Non-public method that downloads, verifies, and 'installs' the proof
1712
+ metadata belonging to 'proof_filename'. Calling this method implies
1713
+ that the 'proof_filename' on the repository is newer than the client's,
1714
+ and thus needs to be re-downloaded. The current and previous metadata
1715
+ stores are updated if the newly downloaded metadata is successfully
1716
+ downloaded and verified. This method also assumes that the store of
1717
+ top-level metadata is the latest and exists.
1718
+
1719
+ <Arguments>
1720
+ proof_filename:
1721
+ The name of the metadata. This is an RSA accumulator proof file and should
1722
+ not end in '.json'. Examples: 'role1-snapshot', 'targets-snapshot'
1723
+
1724
+ upperbound_filelength:
1725
+ The expected length, or upper bound, of the metadata file to be
1726
+ downloaded.
1727
+
1728
+ version:
1729
+ The expected and required version number of the 'proof_filename' file
1730
+ downloaded. 'version' is an integer.
1731
+
1732
+ <Exceptions>
1733
+ tuf.exceptions.NoWorkingMirrorError:
1734
+ The metadata cannot be updated. This is not specific to a single
1735
+ failure but rather indicates that all possible ways to update the
1736
+ metadata have been tried and failed.
1737
+
1738
+ <Side Effects>
1739
+ The metadata file belonging to 'proof_filename' is downloaded from a
1740
+ repository mirror. If the metadata is valid, it is stored in the
1741
+ metadata store.
1742
+
1743
+ <Returns>
1744
+ None.
1745
+ """
1746
+
1747
+ # Construct the metadata filename as expected by the download/mirror
1748
+ # modules.
1749
+ metadata_filename = proof_filename + '.json'
1750
+
1751
+ # Attempt a file download from each mirror until the file is downloaded and
1752
+ # verified. If the signature of the downloaded file is valid, proceed,
1753
+ # otherwise log a warning and try the next mirror. 'metadata_file_object'
1754
+ # is the file-like object returned by 'download.py'. 'metadata_signable'
1755
+ # is the object extracted from 'metadata_file_object'. Metadata saved to
1756
+ # files are regarded as 'signable' objects, conformant to
1757
+ # 'tuf.formats.SIGNABLE_SCHEMA'.
1758
+ #
1759
+ # Some metadata (presently timestamp) will be downloaded "unsafely", in the
1760
+ # sense that we can only estimate its true length and know nothing about
1761
+ # its version. This is because not all metadata will have other metadata
1762
+ # for it; otherwise we will have an infinite regress of metadata signing
1763
+ # for each other. In this case, we will download the metadata up to the
1764
+ # best length we can get for it, not request a specific version, but
1765
+ # perform the rest of the checks (e.g., signature verification).
1766
+
1767
+ remote_filename = metadata_filename
1768
+ filename_version = ''
1769
+
1770
+ if self .consistent_snapshot and version :
1771
+ filename_version = version
1772
+ dirname , basename = os .path .split (remote_filename )
1773
+ remote_filename = os .path .join (
1774
+ dirname , str (filename_version ) + '.' + basename )
1775
+
1776
+ verification_fn = None
1777
+
1778
+ metadata_file_object = \
1779
+ self ._get_metadata_file (proof_filename , remote_filename ,
1780
+ upperbound_filelength , version , verification_fn )
1781
+
1782
+ # The metadata has been verified. Move the metadata file into place.
1783
+ # First, move the 'current' metadata file to the 'previous' directory
1784
+ # if it exists.
1785
+ current_filepath = os .path .join (self .metadata_directory ['current' ],
1786
+ metadata_filename )
1787
+ current_filepath = os .path .abspath (current_filepath )
1788
+ securesystemslib .util .ensure_parent_dir (current_filepath )
1789
+
1790
+ previous_filepath = os .path .join (self .metadata_directory ['previous' ],
1791
+ metadata_filename )
1792
+ previous_filepath = os .path .abspath (previous_filepath )
1793
+
1794
+ if os .path .exists (current_filepath ):
1795
+ # Previous metadata might not exist, say when delegations are added.
1796
+ securesystemslib .util .ensure_parent_dir (previous_filepath )
1797
+ shutil .move (current_filepath , previous_filepath )
1798
+
1799
+ # Next, move the verified updated metadata file to the 'current' directory.
1800
+ metadata_file_object .seek (0 )
1801
+ updated_metadata_object = \
1802
+ securesystemslib .util .load_json_string (metadata_file_object .read ().decode ('utf-8' ))
1803
+
1804
+ securesystemslib .util .persist_temp_file (metadata_file_object , current_filepath )
1805
+
1806
+ # Extract the metadata object so we can store it to the metadata store.
1807
+ # 'current_metadata_object' set to 'None' if there is not an object
1808
+ # stored for 'proof_filename'.
1809
+ current_metadata_object = self .metadata ['current' ].get (proof_filename )
1810
+
1811
+ # Finally, update the metadata and fileinfo stores, and rebuild the
1812
+ # key and role info for the top-level roles if 'proof_filename' is root.
1813
+ # Rebuilding the key and role info is required if the newly-installed
1814
+ # root metadata has revoked keys or updated any top-level role information.
1815
+ logger .debug ('Updated ' + repr (current_filepath ) + '.' )
1816
+ self .metadata ['previous' ][proof_filename ] = current_metadata_object
1817
+ self .metadata ['current' ][proof_filename ] = updated_metadata_object
1818
+
1819
+
1820
+
1821
+
1822
+
1619
1823
1620
1824
def _update_metadata (self , metadata_role , upperbound_filelength , version = None ):
1621
1825
"""
@@ -1732,6 +1936,87 @@ def _update_metadata(self, metadata_role, upperbound_filelength, version=None):
1732
1936
1733
1937
1734
1938
1939
+ def verify_rsa_acc_proof (self , metadata_role , version = None , rsa_acc = None ):
1940
+ """
1941
+ <Purpose>
1942
+ Download the RSA accumulator proof associated with metadata_role and verify the hashes.
1943
+ <Arguments>
1944
+ metadata_role:
1945
+ The name of the metadata role. This should not include a file extension.
1946
+ <Exceptions>
1947
+ tuf.exceptions.RepositoryError:
1948
+ If the snapshot rsa accumulator file is invalid or the verification fails
1949
+ <Returns>
1950
+ A dictionary containing the snapshot information about metadata role,
1951
+ conforming to VERSIONINFO_SCHEMA or METADATA_FILEINFO_SCHEMA
1952
+ """
1953
+
1954
+ # Modulus from https://en.wikipedia.org/wiki/RSA_numbers#RSA-2048
1955
+ # We will want to generate a new one
1956
+ # This is duplicate code from repo lib, should live somewhere else
1957
+ Modulus = "2519590847565789349402718324004839857142928212620403202777713783604366202070759555626401852588078" + \
1958
+ "4406918290641249515082189298559149176184502808489120072844992687392807287776735971418347270261896375014971" + \
1959
+ "8246911650776133798590957000973304597488084284017974291006424586918171951187461215151726546322822168699875" + \
1960
+ "4918242243363725908514186546204357679842338718477444792073993423658482382428119816381501067481045166037730" + \
1961
+ "6056201619676256133844143603833904414952634432190114657544454178424020924616515723350778707749817125772467" + \
1962
+ "962926386356373289912154831438167899885040445364023527381951378636564391212010397122822120720357"
1963
+ m = int (Modulus , 10 )
1964
+
1965
+
1966
+ if not rsa_acc :
1967
+ rsa_acc = self .metadata ['current' ]['timestamp' ]['rsa_acc' ]
1968
+
1969
+ metadata_rolename = metadata_role + '-snapshot'
1970
+
1971
+ # Download RSA accumulator proof
1972
+ upperbound_filelength = tuf .settings .MERKLE_FILELENGTH
1973
+ self ._update_rsa_acc_metadata (metadata_rolename , upperbound_filelength , version )
1974
+ metadata_directory = self .metadata_directory ['current' ]
1975
+ metadata_filename = metadata_rolename + '.json'
1976
+ metadata_filepath = os .path .join (metadata_directory , metadata_filename )
1977
+
1978
+ # Ensure the metadata path is valid/exists, else ignore the call.
1979
+ if not os .path .exists (metadata_filepath ):
1980
+ # No RSA accumulator proof found
1981
+ raise tuf .exceptions .RepositoryError ('No snapshot rsa accumulator proof file for ' +
1982
+ metadata_role )
1983
+ try :
1984
+ snapshot_rsa_acc_proof = securesystemslib .util .load_json_file (
1985
+ metadata_filepath )
1986
+
1987
+ # Although the metadata file may exist locally, it may not
1988
+ # be a valid json file. On the next refresh cycle, it will be
1989
+ # updated as required. If Root if cannot be loaded from disk
1990
+ # successfully, an exception should be raised by the caller.
1991
+ except securesystemslib .exceptions .Error :
1992
+ return
1993
+
1994
+ # check the format
1995
+ tuf .formats .SNAPSHOT_RSA_ACC_SCHEMA .check_match (snapshot_rsa_acc_proof )
1996
+
1997
+ # canonicalize the contents to determine the RSA accumulator prime
1998
+ contents = snapshot_rsa_acc_proof ['leaf_contents' ]
1999
+ json_contents = securesystemslib .formats .encode_canonical (contents )
2000
+
2001
+ prime = repository_lib .hash_to_prime (json_contents )
2002
+
2003
+ # RSA accumulator proof
2004
+ proof = snapshot_rsa_acc_proof ['rsa_acc_proof' ]
2005
+ rsa_acc_proof_test = pow (proof , prime , m )
2006
+
2007
+ # Does the result match the RSA accumulator?
2008
+ if rsa_acc_proof_test != rsa_acc :
2009
+ raise tuf .exceptions .RepositoryError ('RSA accumulator ' + rsa_acc +
2010
+ ' does not match the proof ' + proof + ' for ' + metadata_role )
2011
+
2012
+ # return the verified snapshot contents
2013
+ return contents
2014
+
2015
+
2016
+
2017
+
2018
+
2019
+
1735
2020
def _update_metadata_if_changed (self , metadata_role ,
1736
2021
referenced_metadata = 'snapshot' ):
1737
2022
"""
@@ -1801,7 +2086,9 @@ def _update_metadata_if_changed(self, metadata_role,
1801
2086
1802
2087
# Ensure the referenced metadata has been loaded. The 'root' role may be
1803
2088
# updated without having 'snapshot' available.
1804
- if referenced_metadata not in self .metadata ['current' ]:
2089
+ # When a snapshot rsa accumulator is used, there will not be a snapshot file.
2090
+ # Instead, if the snapshot rsa proof is missing, this will error below.
2091
+ if 'rsa_acc' not in self .metadata ['current' ]['timestamp' ] and referenced_metadata not in self .metadata ['current' ]:
1805
2092
raise exceptions .RepositoryError ('Cannot update'
1806
2093
' ' + repr (metadata_role ) + ' because ' + referenced_metadata + ' is'
1807
2094
' missing.' )
@@ -1813,12 +2100,18 @@ def _update_metadata_if_changed(self, metadata_role,
1813
2100
repr (referenced_metadata )+ '. ' + repr (metadata_role ) +
1814
2101
' may be updated.' )
1815
2102
1816
- # Simply return if the metadata for 'metadata_role' has not been updated,
1817
- # according to the uncompressed metadata provided by the referenced
1818
- # metadata. The metadata is considered updated if its version number is
1819
- # strictly greater than its currently trusted version number.
1820
- expected_versioninfo = self .metadata ['current' ][referenced_metadata ] \
1821
- ['meta' ][metadata_filename ]
2103
+ if 'rsa_acc' in self .metadata ['current' ]['timestamp' ]:
2104
+ # Download version information from RSA accumulator proof
2105
+ contents = self .verify_rsa_acc_proof (metadata_role )
2106
+ expected_versioninfo = contents
2107
+
2108
+ else :
2109
+ # Simply return if the metadata for 'metadata_role' has not been updated,
2110
+ # according to the uncompressed metadata provided by the referenced
2111
+ # metadata. The metadata is considered updated if its version number is
2112
+ # strictly greater than its currently trusted version number.
2113
+ expected_versioninfo = self .metadata ['current' ][referenced_metadata ] \
2114
+ ['meta' ][metadata_filename ]
1822
2115
1823
2116
if not self ._versioninfo_has_been_updated (metadata_filename ,
1824
2117
expected_versioninfo ):
@@ -2388,7 +2681,10 @@ def _refresh_targets_metadata(self, rolename='targets',
2388
2681
2389
2682
roles_to_update = []
2390
2683
2391
- if rolename + '.json' in self .metadata ['current' ]['snapshot' ]['meta' ]:
2684
+ # Add the role if it is listed in snapshot. If a snapshot rsa
2685
+ # accumulator is used, the snapshot check will be done later when
2686
+ # the proof is verified
2687
+ if 'rsa_acc' in self .metadata ['current' ]['timestamp' ] or rolename + '.json' in self .metadata ['current' ]['snapshot' ]['meta' ]:
2392
2688
roles_to_update .append (rolename )
2393
2689
2394
2690
if refresh_all_delegated_roles :
0 commit comments