From c2edd30669e3c482bdeeb27a67c512e8ea2b7acd Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Thu, 29 Feb 2024 15:12:18 +0200 Subject: [PATCH] Metadata API: Make sure Signed.expires is UTC * Most importantly use strftime() to serialize the datetime * Force the timezone as UTC when deserializing Signed-off-by: Jussi Kukkonen --- tests/generated_data/generate_md.py | 5 ++--- tests/test_api.py | 3 ++- tests/test_trusted_metadata_set.py | 10 +++++----- tuf/api/_payload.py | 3 ++- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/tests/generated_data/generate_md.py b/tests/generated_data/generate_md.py index df459c1d..884e97e2 100644 --- a/tests/generated_data/generate_md.py +++ b/tests/generated_data/generate_md.py @@ -5,7 +5,7 @@ import os import sys -from datetime import datetime +from datetime import UTC, datetime from typing import Dict, List, Optional from securesystemslib.signer import SSlibKey, SSlibSigner @@ -48,8 +48,7 @@ } ) -expires_str = "2050-01-01T00:00:00Z" -EXPIRY = datetime.strptime(expires_str, "%Y-%m-%dT%H:%M:%SZ") +EXPIRY = datetime(2050, 1, 1, tzinfo=UTC) OUT_DIR = "generated_data/ed25519_metadata" if not os.path.exists(OUT_DIR): os.mkdir(OUT_DIR) diff --git a/tests/test_api.py b/tests/test_api.py index c3b14fe5..b0bd937f 100755 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -313,7 +313,8 @@ def test_metadata_signed_is_expired(self) -> None: snapshot_path = os.path.join(self.repo_dir, "metadata", "snapshot.json") md = Metadata.from_file(snapshot_path) - self.assertEqual(md.signed.expires, datetime(2030, 1, 1, 0, 0)) + expected_expiry = datetime(2030, 1, 1, 0, 0, tzinfo=timezone.utc) + self.assertEqual(md.signed.expires, expected_expiry) # Test is_expired with reference_time provided is_expired = md.signed.is_expired(md.signed.expires) diff --git a/tests/test_trusted_metadata_set.py b/tests/test_trusted_metadata_set.py index b5ab042d..ac5b557c 100644 --- a/tests/test_trusted_metadata_set.py +++ b/tests/test_trusted_metadata_set.py @@ -4,7 +4,7 @@ import os import sys import unittest -from datetime import datetime +from datetime import UTC, datetime from typing import Callable, ClassVar, Dict, List, Optional, Tuple from securesystemslib.interface import ( @@ -279,7 +279,7 @@ def test_update_root_new_root_ver_same_as_trusted_root_ver(self) -> None: def test_root_expired_final_root(self) -> None: def root_expired_modifier(root: Root) -> None: - root.expires = datetime(1970, 1, 1) + root.expires = datetime(1970, 1, 1, tzinfo=UTC) # intermediate root can be expired root = self.modify_metadata(Root.type, root_expired_modifier) @@ -329,7 +329,7 @@ def bump_snapshot_version(timestamp: Timestamp) -> None: def test_update_timestamp_expired(self) -> None: # new_timestamp has expired def timestamp_expired_modifier(timestamp: Timestamp) -> None: - timestamp.expires = datetime(1970, 1, 1) + timestamp.expires = datetime(1970, 1, 1, tzinfo=UTC) # expired intermediate timestamp is loaded but raises timestamp = self.modify_metadata( @@ -406,7 +406,7 @@ def test_update_snapshot_expired_new_snapshot(self) -> None: self.trusted_set.update_timestamp(self.metadata[Timestamp.type]) def snapshot_expired_modifier(snapshot: Snapshot) -> None: - snapshot.expires = datetime(1970, 1, 1) + snapshot.expires = datetime(1970, 1, 1, tzinfo=UTC) # expired intermediate snapshot is loaded but will raise snapshot = self.modify_metadata( @@ -486,7 +486,7 @@ def test_update_targets_expired_new_target(self) -> None: # new_delegated_target has expired def target_expired_modifier(target: Targets) -> None: - target.expires = datetime(1970, 1, 1) + target.expires = datetime(1970, 1, 1, tzinfo=UTC) targets = self.modify_metadata(Targets.type, target_expired_modifier) with self.assertRaises(exceptions.ExpiredMetadataError): diff --git a/tuf/api/_payload.py b/tuf/api/_payload.py index 6e61ff97..dc7c0972 100644 --- a/tuf/api/_payload.py +++ b/tuf/api/_payload.py @@ -176,6 +176,7 @@ def _common_fields_from_dict( # what the constructor expects and what we store. The inverse operation # is implemented in '_common_fields_to_dict'. expires = datetime.strptime(expires_str, "%Y-%m-%dT%H:%M:%SZ") + expires = expires.replace(tzinfo=timezone.utc) return version, spec_version, expires @@ -190,7 +191,7 @@ def _common_fields_to_dict(self) -> Dict[str, Any]: "_type": self._type, "version": self.version, "spec_version": self.spec_version, - "expires": self.expires.isoformat() + "Z", + "expires": self.expires.strftime("%Y-%m-%dT%H:%M:%SZ"), **self.unrecognized_fields, }