diff --git a/requirements-pinned.txt b/requirements-pinned.txt index ffc922a95d..1cd39f6afe 100644 --- a/requirements-pinned.txt +++ b/requirements-pinned.txt @@ -6,7 +6,6 @@ cryptography==3.1.1 # via securesystemslib enum34==1.1.6 ; python_version < '3' # via cryptography idna==2.10 # via requests ipaddress==1.0.23 ; python_version < '3' # via cryptography -iso8601==0.1.13 pycparser==2.20 # via cffi pynacl==1.4.0 # via securesystemslib python-dateutil==2.8.1 # via securesystemslib diff --git a/requirements.txt b/requirements.txt index c4bcce87c6..7191031347 100644 --- a/requirements.txt +++ b/requirements.txt @@ -44,4 +44,3 @@ securesystemslib[colors, crypto, pynacl] requests six -iso8601 diff --git a/setup.py b/setup.py index cee67ac851..4d6f523cbf 100755 --- a/setup.py +++ b/setup.py @@ -114,7 +114,6 @@ }, python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4", install_requires = [ - 'iso8601>=0.1.12', 'requests>=2.19.1', 'six>=1.11.0', 'securesystemslib>=0.16.0' diff --git a/tests/test_formats.py b/tests/test_formats.py index dbff791c4e..53fbb579ba 100755 --- a/tests/test_formats.py +++ b/tests/test_formats.py @@ -723,6 +723,23 @@ def test_build_dict_conforming_to_schema(self): + def test_expiry_string_to_datetime(self): + dt = tuf.formats.expiry_string_to_datetime('1985-10-21T13:20:00Z') + self.assertEqual(dt, datetime.datetime(1985, 10, 21, 13, 20, 0)) + dt = tuf.formats.expiry_string_to_datetime('2038-01-19T03:14:08Z') + self.assertEqual(dt, datetime.datetime(2038, 1, 19, 3, 14, 8)) + + # First 3 fail via securesystemslib schema, last one because of strptime() + invalid_inputs = [ + '2038-1-19T03:14:08Z', # leading zeros not optional + '2038-01-19T031408Z', # strict time parsing + '2038-01-19T03:14:08Z-06:00', # timezone not allowed + '2038-13-19T03:14:08Z', # too many months + ] + for invalid_input in invalid_inputs: + with self.assertRaises(securesystemslib.exceptions.FormatError): + tuf.formats.expiry_string_to_datetime(invalid_input) + def test_unix_timestamp_to_datetime(self): diff --git a/tuf/api/metadata.py b/tuf/api/metadata.py index ee5ab8eed1..9200fa7b9b 100644 --- a/tuf/api/metadata.py +++ b/tuf/api/metadata.py @@ -294,9 +294,8 @@ def from_dict(cls, signed_dict: JsonDict) -> 'Signed': # Convert 'expires' TUF metadata string to a datetime object, which is # what the constructor expects and what we store. The inverse operation # is implemented in 'to_dict'. - signed_dict['expires'] = datetime.strptime( - signed_dict['expires'], - "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=None) + signed_dict['expires'] = tuf.formats.expiry_string_to_datetime( + signed_dict['expires']) # NOTE: We write the converted 'expires' back into 'signed_dict' above # so that we can pass it to the constructor as '**signed_dict' below, # along with other fields that belong to Signed subclasses. diff --git a/tuf/client/updater.py b/tuf/client/updater.py index bbc266f970..4e6211244c 100755 --- a/tuf/client/updater.py +++ b/tuf/client/updater.py @@ -145,7 +145,6 @@ import securesystemslib.keys import securesystemslib.util import six -import iso8601 import requests.exceptions # The Timestamp role does not have signed metadata about it; otherwise we @@ -163,11 +162,6 @@ # See 'log.py' to learn how logging is handled in TUF. logger = logging.getLogger(__name__) -# Disable 'iso8601' logger messages to prevent 'iso8601' from clogging the -# log file. -iso8601_logger = logging.getLogger('iso8601') -iso8601_logger.disabled = True - class MultiRepoUpdater(object): """ @@ -2377,7 +2371,8 @@ def _ensure_not_expired(self, metadata_object, metadata_rolename): tuf.exceptions.ExpiredMetadataError: If 'metadata_rolename' has expired. - + securesystemslib.exceptions.FormatError: + If the expiration cannot be parsed correctly None. @@ -2385,22 +2380,15 @@ def _ensure_not_expired(self, metadata_object, metadata_rolename): None. """ - # Extract the expiration time. - expires = metadata_object['expires'] - - # If the current time has surpassed the expiration date, raise an - # exception. 'expires' is in - # 'securesystemslib.formats.ISO8601_DATETIME_SCHEMA' format (e.g., - # '1985-10-21T01:22:00Z'.) Convert it to a unix timestamp and compare it + # Extract the expiration time. Convert it to a unix timestamp and compare it # against the current time.time() (also in Unix/POSIX time format, although # with microseconds attached.) - current_time = int(time.time()) - - # Generate a user-friendly error message if 'expires' is less than the - # current time (i.e., a local time.) - expires_datetime = iso8601.parse_date(expires) + expires_datetime = tuf.formats.expiry_string_to_datetime( + metadata_object['expires']) expires_timestamp = tuf.formats.datetime_to_unix_timestamp(expires_datetime) + current_time = int(time.time()) + if expires_timestamp < current_time: message = 'Metadata '+repr(metadata_rolename)+' expired on ' + \ expires_datetime.ctime() + ' (UTC).' diff --git a/tuf/formats.py b/tuf/formats.py index da0dc25c56..e96341768f 100755 --- a/tuf/formats.py +++ b/tuf/formats.py @@ -612,6 +612,37 @@ def build_dict_conforming_to_schema(schema, **kwargs): + +def expiry_string_to_datetime(expires): + """ + + Convert an expiry string to a datetime object. + + expires: + The expiry date-time string in the ISO8601 format that is defined + in securesystemslib.ISO8601_DATETIME_SCHEMA. E.g. '2038-01-19T03:14:08Z' + + securesystemslib.exceptions.FormatError, if 'expires' cannot be + parsed correctly. + + None. + + A datetime object representing the expiry time. + """ + + # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. + securesystemslib.formats.ISO8601_DATETIME_SCHEMA.check_match(expires) + + try: + return datetime.datetime.strptime(expires, "%Y-%m-%dT%H:%M:%SZ") + except ValueError as error: + six.raise_from(securesystemslib.exceptions.FormatError( + 'Failed to parse ' + repr(expires) + ' as an expiry time'), + error) + + + + def datetime_to_unix_timestamp(datetime_object): """ diff --git a/tuf/repository_lib.py b/tuf/repository_lib.py index 9ffe8d977b..06744541dd 100644 --- a/tuf/repository_lib.py +++ b/tuf/repository_lib.py @@ -52,7 +52,6 @@ import securesystemslib.hash import securesystemslib.interface import securesystemslib.util -import iso8601 import six import securesystemslib.storage @@ -61,11 +60,6 @@ # See 'log.py' to learn how logging is handled in TUF. logger = logging.getLogger(__name__) -# Disable 'iso8601' logger messages to prevent 'iso8601' from clogging the -# log file. -iso8601_logger = logging.getLogger('iso8601') -iso8601_logger.disabled = True - # The extension of TUF metadata. METADATA_EXTENSION = '.json' @@ -704,7 +698,8 @@ def _log_warning_if_expires_soon(rolename, expires_iso8601_timestamp, # unix timestamp, subtract from current time.time() (also in POSIX time) # and compare against 'seconds_remaining_to_warn'. Log a warning message # to console if 'rolename' expires soon. - datetime_object = iso8601.parse_date(expires_iso8601_timestamp) + datetime_object = tuf.formats.expiry_string_to_datetime( + expires_iso8601_timestamp) expires_unix_timestamp = \ tuf.formats.datetime_to_unix_timestamp(datetime_object) seconds_until_expires = expires_unix_timestamp - int(time.time()) diff --git a/tuf/repository_tool.py b/tuf/repository_tool.py index 9dd7e7ff60..59eab5be8a 100755 --- a/tuf/repository_tool.py +++ b/tuf/repository_tool.py @@ -53,7 +53,6 @@ import securesystemslib.keys import securesystemslib.formats import securesystemslib.util -import iso8601 import six import securesystemslib.storage @@ -1317,15 +1316,12 @@ def expiration(self): A getter method that returns the role's expiration datetime. - >>> - >>> - >>> - None. - None. + securesystemslib.exceptions.FormatError, if the expiration cannot be + parsed correctly None. @@ -1337,9 +1333,7 @@ def expiration(self): roleinfo = tuf.roledb.get_roleinfo(self.rolename, self._repository_name) expires = roleinfo['expires'] - expires_datetime_object = iso8601.parse_date(expires) - - return expires_datetime_object + return tuf.formats.expiry_string_to_datetime(expires) diff --git a/tuf/sig.py b/tuf/sig.py index 2494765bf2..2351e0e381 100755 --- a/tuf/sig.py +++ b/tuf/sig.py @@ -60,12 +60,6 @@ # See 'log.py' to learn how logging is handled in TUF. logger = logging.getLogger(__name__) -# Disable 'iso8601' logger messages to prevent 'iso8601' from clogging the -# log file. -iso8601_logger = logging.getLogger('iso8601') -iso8601_logger.disabled = True - - def get_signature_status(signable, role=None, repository_name='default', threshold=None, keyids=None): """