Skip to content

Commit 8afd335

Browse files
committed
Refactor _get_metadata_file() from updater.py
Currently, the `_get_metadata_file()` from `tuf/client/updater.py` is really large and does too many things and some of them partially. Right now, it validates the specification version and the metadata version in it, but doesn't validate if the metadata has expired or if the signature could be trusted. So, it's partially validating the metadata file and at the same time calling `_verify_metadata_file()` from updater.py for the rest of the work which doesn't make sense. It's logically better if we move the validation of the specification and the metadata version in separate functions which would be then called in ` _verify_metadata_file()` and that way all of the validation will be done in ` _verify_metadata_file()`. Signed-off-by: Martin Vrachev <[email protected]>
1 parent 771592a commit 8afd335

File tree

1 file changed

+132
-83
lines changed

1 file changed

+132
-83
lines changed

tuf/client/updater.py

Lines changed: 132 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1373,9 +1373,115 @@ def _verify_root_self_signed(self, signable):
13731373

13741374

13751375

1376+
def _validate_metadata_version(self, expected_version, metadata_role,
1377+
version_downloaded):
1378+
"""
1379+
<Purpose>
1380+
Non-public method validating the metadata version number.
1381+
If the expected_version is unspecified, ensure that the version number
1382+
downloaded is equal or greater than the currently trusted version number
1383+
for 'metadata_role'.
1384+
1385+
<Arguments>
1386+
expected_version:
1387+
An integer or "None" value representing the expected and required
1388+
version number of the 'metadata_role' file downloaded.
1389+
1390+
metadata_role:
1391+
The role name of the metadata (e.g., 'root', 'targets').
1392+
1393+
version_downloaded:
1394+
The version of the newly downloaded metadata file.
1395+
1396+
<Exceptions>
1397+
tuf.exceptions.BadVersionNumberError:
1398+
In case the expected_version is not None and version_downloaded
1399+
is not equal to it.
1400+
1401+
tuf.exceptions.ReplayedMetadataError:
1402+
if expected_version is None and version_downloaded is lower than
1403+
the current version.
1404+
"""
1405+
1406+
if expected_version is not None:
1407+
if version_downloaded != expected_version:
1408+
raise tuf.exceptions.BadVersionNumberError('Downloaded'
1409+
' version number: ' + repr(version_downloaded) + '. Version'
1410+
' number MUST be: ' + repr(expected_version))
1411+
1412+
# The caller does not know which version to download.
1413+
# Verify that the downloaded version is equal or
1414+
# greater than the currently trusted version number.
1415+
else:
1416+
try:
1417+
current_version = self.metadata['current'][metadata_role]['version']
1418+
1419+
if version_downloaded < current_version:
1420+
raise tuf.exceptions.ReplayedMetadataError(metadata_role,
1421+
version_downloaded, current_version)
1422+
1423+
except KeyError:
1424+
logger.info(metadata_role + ' not available locally.')
1425+
1426+
1427+
1428+
def _validate_spec_version(self, metadata_spec_version):
1429+
"""
1430+
<Purpose>
1431+
Non-public method verifying a metadata specification version.
1432+
It is assumed that "spec_version" is in (major.minor.fix) format,
1433+
(for example: "1.4.3") and that releases with the same major version
1434+
number maintain backward compatibility.
1435+
Consequently, if the major version number of new metadata equals our
1436+
expected major version number, the new metadata is safe to parse.
1437+
1438+
<Arguments>
1439+
metadata_spec_version:
1440+
A string representing the metadata spec version. It is assumed that
1441+
it is in semantic versioning (major.minor.fix) format.
1442+
1443+
<Exceptions>
1444+
tuf.exceptions.UnsupportedSpecificationError:
1445+
In case the metadata major spec version is not supported.
1446+
1447+
securesystemslib.exceptions.FormatError:
1448+
In case the metadata_spec_version string is not in the expected format.
1449+
"""
1450+
1451+
try:
1452+
metadata_spec_version_split = metadata_spec_version.split('.')
1453+
metadata_spec_major_version = int(metadata_spec_version_split[0])
1454+
metadata_spec_minor_version = int(metadata_spec_version_split[1])
1455+
1456+
code_spec_version_split = tuf.SPECIFICATION_VERSION.split('.')
1457+
code_spec_major_version = int(code_spec_version_split[0])
1458+
code_spec_minor_version = int(code_spec_version_split[1])
1459+
1460+
if metadata_spec_major_version != code_spec_major_version:
1461+
raise tuf.exceptions.UnsupportedSpecificationError(
1462+
'Downloaded metadata that specifies an unsupported spec_version. '
1463+
'This code supports major version number: ' +
1464+
repr(code_spec_major_version) + '; however,'
1465+
'metadata spec version is: ' + str(metadata_spec_version))
1466+
1467+
# report to user if minor versions do not match, continue with update
1468+
if metadata_spec_minor_version != code_spec_minor_version:
1469+
logger.info("Downloaded metadata that specifies a different minor " +
1470+
"spec_version.")
1471+
logger.info("This code has version " + tuf.SPECIFICATION_VERSION +
1472+
" and the metadata lists version number " +
1473+
str(metadata_spec_version) + ".")
1474+
logger.info("The update will continue as the major versions match.")
1475+
1476+
except (ValueError, TypeError) as error:
1477+
six.raise_from(securesystemslib.exceptions.FormatError('Improperly'
1478+
' formatted spec_version, which must be in major.minor.fix format'),
1479+
error)
1480+
1481+
13761482

13771483
def _verify_metadata_file(self, metadata_file_object,
1378-
metadata_role):
1484+
metadata_role, expected_version):
13791485
"""
13801486
<Purpose>
13811487
Non-public method that verifies a metadata file. An exception is
@@ -1390,15 +1496,29 @@ def _verify_metadata_file(self, metadata_file_object,
13901496
The role name of the metadata (e.g., 'root', 'targets',
13911497
'unclaimed').
13921498
1499+
expected_version:
1500+
An integer or "None" value representing the expected and required
1501+
version number of the 'metadata_role' file downloaded.
1502+
13931503
<Exceptions>
13941504
securesystemslib.exceptions.FormatError:
1395-
In case the metadata file is valid JSON, but not valid TUF metadata.
1505+
In case the metadata file is valid JSON, but not valid TUF metadata
1506+
or when the metadata spec version is not in the rigth format.
13961507
13971508
tuf.exceptions.InvalidMetadataJSONError:
13981509
In case the metadata file is not valid JSON.
13991510
1511+
tuf.exceptions.UnsupportedSpecificationError:
1512+
In case the metadata spec version is not supported.
1513+
14001514
tuf.exceptions.ReplayedMetadataError:
1401-
In case the downloaded metadata file is older than the current one.
1515+
In case the downloaded metadata file is older than the current one or
1516+
if expected_version is None and version_downloaded is lower than the
1517+
trusted current version.
1518+
1519+
tuf.exceptions.BadVersionNumberError:
1520+
In case the expected_version is not None and version_downloaded is not
1521+
equal to it.
14021522
14031523
tuf.exceptions.RepositoryError:
14041524
In case the repository is somehow inconsistent; e.g. a parent has not
@@ -1429,6 +1549,11 @@ def _verify_metadata_file(self, metadata_file_object,
14291549
# 'securesystemslib.exceptions.FormatError' if not.
14301550
tuf.formats.check_signable_object_format(metadata_signable)
14311551

1552+
self._validate_spec_version(metadata_signable['signed']['spec_version'])
1553+
1554+
self._validate_metadata_version(expected_version, metadata_role,
1555+
metadata_signable['signed']['version'])
1556+
14321557
# Is 'metadata_signable' expired?
14331558
self._ensure_not_expired(metadata_signable['signed'], metadata_role)
14341559

@@ -1482,8 +1607,8 @@ def _get_metadata_file(self, metadata_role, remote_filename,
14821607
downloaded.
14831608
14841609
expected_version:
1485-
The expected and required version number of the 'metadata_role' file
1486-
downloaded. 'expected_version' is an integer.
1610+
An integer representing the expected and required version number
1611+
of the 'metadata_role' file downloaded.
14871612
14881613
<Exceptions>
14891614
tuf.exceptions.NoWorkingMirrorError:
@@ -1510,85 +1635,9 @@ def _get_metadata_file(self, metadata_role, remote_filename,
15101635
try:
15111636
file_object = tuf.download.unsafe_download(file_mirror,
15121637
upperbound_filelength)
1513-
file_object.seek(0)
1514-
1515-
# Verify 'file_object' according to the callable function.
1516-
# 'file_object' is also verified if decompressed above (i.e., the
1517-
# uncompressed version).
1518-
metadata_signable = \
1519-
securesystemslib.util.load_json_string(file_object.read().decode('utf-8'))
1520-
1521-
# Determine if the specification version number is supported. It is
1522-
# assumed that "spec_version" is in (major.minor.fix) format, (for
1523-
# example: "1.4.3") and that releases with the same major version
1524-
# number maintain backwards compatibility. Consequently, if the major
1525-
# version number of new metadata equals our expected major version
1526-
# number, the new metadata is safe to parse.
1527-
try:
1528-
metadata_spec_version = metadata_signable['signed']['spec_version']
1529-
metadata_spec_version_split = metadata_spec_version.split('.')
1530-
metadata_spec_major_version = int(metadata_spec_version_split[0])
1531-
metadata_spec_minor_version = int(metadata_spec_version_split[1])
1532-
1533-
code_spec_version_split = tuf.SPECIFICATION_VERSION.split('.')
1534-
code_spec_major_version = int(code_spec_version_split[0])
1535-
code_spec_minor_version = int(code_spec_version_split[1])
1536-
1537-
if metadata_spec_major_version != code_spec_major_version:
1538-
raise tuf.exceptions.UnsupportedSpecificationError(
1539-
'Downloaded metadata that specifies an unsupported '
1540-
'spec_version. This code supports major version number: ' +
1541-
repr(code_spec_major_version) + '; however, the obtained '
1542-
'metadata lists version number: ' + str(metadata_spec_version))
1543-
1544-
#report to user if minor versions do not match, continue with update
1545-
if metadata_spec_minor_version != code_spec_minor_version:
1546-
logger.info("Downloaded metadata that specifies a different minor " +
1547-
"spec_version. This code has version " +
1548-
str(tuf.SPECIFICATION_VERSION) +
1549-
" and the metadata lists version number " +
1550-
str(metadata_spec_version) +
1551-
". The update will continue as the major versions match.")
1552-
1553-
except (ValueError, TypeError) as error:
1554-
six.raise_from(securesystemslib.exceptions.FormatError('Improperly'
1555-
' formatted spec_version, which must be in major.minor.fix format'),
1556-
error)
1557-
1558-
# If the version number is unspecified, ensure that the version number
1559-
# downloaded is greater than the currently trusted version number for
1560-
# 'metadata_role'.
1561-
version_downloaded = metadata_signable['signed']['version']
1562-
1563-
if expected_version is not None:
1564-
# Verify that the downloaded version matches the version expected by
1565-
# the caller.
1566-
if version_downloaded != expected_version:
1567-
raise tuf.exceptions.BadVersionNumberError('Downloaded'
1568-
' version number: ' + repr(version_downloaded) + '. Version'
1569-
' number MUST be: ' + repr(expected_version))
1570-
1571-
# The caller does not know which version to download. Verify that the
1572-
# downloaded version is at least greater than the one locally
1573-
# available.
1574-
else:
1575-
# Verify that the version number of the locally stored
1576-
# 'timestamp.json', if available, is less than what was downloaded.
1577-
# Otherwise, accept the new timestamp with version number
1578-
# 'version_downloaded'.
1579-
1580-
try:
1581-
current_version = \
1582-
self.metadata['current'][metadata_role]['version']
1583-
1584-
if version_downloaded < current_version:
1585-
raise tuf.exceptions.ReplayedMetadataError(metadata_role,
1586-
version_downloaded, current_version)
1587-
1588-
except KeyError:
1589-
logger.info(metadata_role + ' not available locally.')
15901638

1591-
self._verify_metadata_file(file_object, metadata_role)
1639+
self._verify_metadata_file(file_object, metadata_role,
1640+
expected_version)
15921641

15931642
except Exception as exception:
15941643
# Remember the error from this mirror, and "reset" the target file.

0 commit comments

Comments
 (0)