2021-10-13 16:56:53 +00:00
|
|
|
"""Unit tests for 'tuf/ngclient/_internal/trusted_metadata_set.py'."""
|
2024-02-01 20:10:31 +00:00
|
|
|
|
2024-11-29 10:29:32 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
2021-05-21 10:51:49 +00:00
|
|
|
import logging
|
|
|
|
|
import os
|
|
|
|
|
import sys
|
|
|
|
|
import unittest
|
2024-02-29 13:45:56 +00:00
|
|
|
from datetime import datetime, timezone
|
2026-01-08 11:06:24 +00:00
|
|
|
from typing import TYPE_CHECKING, ClassVar
|
2021-05-21 10:51:49 +00:00
|
|
|
|
2024-04-24 08:36:57 +00:00
|
|
|
from securesystemslib.signer import Signer
|
2021-10-12 13:54:10 +00:00
|
|
|
|
|
|
|
|
from tests import utils
|
2021-12-13 16:20:26 +00:00
|
|
|
from tuf.api import exceptions
|
2023-10-12 09:55:45 +00:00
|
|
|
from tuf.api.dsse import SimpleEnvelope
|
2021-08-16 15:13:01 +00:00
|
|
|
from tuf.api.metadata import (
|
|
|
|
|
Metadata,
|
2021-10-12 13:54:10 +00:00
|
|
|
MetaFile,
|
2021-08-16 15:13:01 +00:00
|
|
|
Root,
|
2023-08-09 09:19:39 +00:00
|
|
|
Signed,
|
2021-08-16 15:13:01 +00:00
|
|
|
Snapshot,
|
2021-10-12 13:14:24 +00:00
|
|
|
Targets,
|
2021-10-12 13:54:10 +00:00
|
|
|
Timestamp,
|
2021-08-16 15:13:01 +00:00
|
|
|
)
|
2021-12-21 16:12:30 +00:00
|
|
|
from tuf.api.serialization.json import JSONSerializer
|
2023-10-12 09:55:45 +00:00
|
|
|
from tuf.ngclient._internal.trusted_metadata_set import (
|
|
|
|
|
TrustedMetadataSet,
|
|
|
|
|
_load_from_simple_envelope,
|
|
|
|
|
)
|
2023-10-12 09:14:15 +00:00
|
|
|
from tuf.ngclient.config import EnvelopeType
|
2021-07-19 11:49:03 +00:00
|
|
|
|
2026-01-08 11:06:24 +00:00
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
from collections.abc import Callable
|
|
|
|
|
|
2021-05-21 10:51:49 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
2023-02-01 14:33:03 +00:00
|
|
|
|
2021-10-12 13:14:24 +00:00
|
|
|
class TestTrustedMetadataSet(unittest.TestCase):
|
2021-10-13 16:56:53 +00:00
|
|
|
"""Tests for all public API of the TrustedMetadataSet class."""
|
|
|
|
|
|
2024-11-04 04:21:23 +00:00
|
|
|
keystore: ClassVar[dict[str, Signer]]
|
|
|
|
|
metadata: ClassVar[dict[str, bytes]]
|
2021-11-18 16:58:16 +00:00
|
|
|
repo_dir: ClassVar[str]
|
|
|
|
|
|
|
|
|
|
@classmethod
|
2021-08-16 15:13:01 +00:00
|
|
|
def modify_metadata(
|
2021-11-18 16:58:16 +00:00
|
|
|
cls, rolename: str, modification_func: Callable
|
2021-08-16 15:13:01 +00:00
|
|
|
) -> bytes:
|
|
|
|
|
"""Instantiate metadata from rolename type, call modification_func and
|
|
|
|
|
sign it again with self.keystore[rolename] signer.
|
|
|
|
|
|
|
|
|
|
Attributes:
|
2021-10-13 16:56:53 +00:00
|
|
|
rolename: Denoting the name of the metadata which will be modified.
|
2021-08-16 15:13:01 +00:00
|
|
|
modification_func: Function that will be called to modify the signed
|
|
|
|
|
portion of metadata bytes.
|
|
|
|
|
"""
|
2021-11-18 16:58:16 +00:00
|
|
|
metadata = Metadata.from_bytes(cls.metadata[rolename])
|
2021-08-16 15:13:01 +00:00
|
|
|
modification_func(metadata.signed)
|
2021-11-18 16:58:16 +00:00
|
|
|
metadata.sign(cls.keystore[rolename])
|
2021-12-21 16:12:30 +00:00
|
|
|
return metadata.to_bytes(JSONSerializer(validate=True))
|
2021-08-16 15:13:01 +00:00
|
|
|
|
2021-06-29 16:14:56 +00:00
|
|
|
@classmethod
|
2021-11-18 16:58:16 +00:00
|
|
|
def setUpClass(cls) -> None:
|
2021-06-29 16:14:56 +00:00
|
|
|
cls.repo_dir = os.path.join(
|
2022-01-24 10:52:38 +00:00
|
|
|
utils.TESTS_DIR, "repository_data", "repository", "metadata"
|
2021-06-29 16:14:56 +00:00
|
|
|
)
|
|
|
|
|
cls.metadata = {}
|
2021-10-12 13:14:24 +00:00
|
|
|
for md in [
|
2021-11-17 12:24:03 +00:00
|
|
|
Root.type,
|
|
|
|
|
Timestamp.type,
|
|
|
|
|
Snapshot.type,
|
|
|
|
|
Targets.type,
|
2021-10-12 13:14:24 +00:00
|
|
|
"role1",
|
|
|
|
|
"role2",
|
|
|
|
|
]:
|
2021-06-29 16:14:56 +00:00
|
|
|
with open(os.path.join(cls.repo_dir, f"{md}.json"), "rb") as f:
|
|
|
|
|
cls.metadata[md] = f.read()
|
|
|
|
|
|
2022-01-24 10:52:38 +00:00
|
|
|
keystore_dir = os.path.join(
|
|
|
|
|
utils.TESTS_DIR, "repository_data", "keystore"
|
|
|
|
|
)
|
2024-04-24 08:36:57 +00:00
|
|
|
root = Metadata[Root].from_bytes(cls.metadata[Root.type]).signed
|
|
|
|
|
|
2021-07-01 17:14:13 +00:00
|
|
|
cls.keystore = {}
|
2024-04-24 08:36:57 +00:00
|
|
|
for role in [
|
|
|
|
|
Root.type,
|
|
|
|
|
Snapshot.type,
|
|
|
|
|
Targets.type,
|
|
|
|
|
Timestamp.type,
|
|
|
|
|
]:
|
|
|
|
|
uri = f"file2:{os.path.join(keystore_dir, role + '_key')}"
|
|
|
|
|
role_obj = root.get_delegated_role(role)
|
|
|
|
|
key = root.get_key(role_obj.keyids[0])
|
|
|
|
|
cls.keystore[role] = Signer.from_priv_key_uri(uri, key)
|
2021-05-21 10:51:49 +00:00
|
|
|
|
2021-08-16 15:13:01 +00:00
|
|
|
def hashes_length_modifier(timestamp: Timestamp) -> None:
|
2021-06-14 11:13:15 +00:00
|
|
|
timestamp.snapshot_meta.hashes = None
|
|
|
|
|
timestamp.snapshot_meta.length = None
|
2021-08-16 15:13:01 +00:00
|
|
|
|
2021-11-17 12:24:03 +00:00
|
|
|
cls.metadata[Timestamp.type] = cls.modify_metadata(
|
|
|
|
|
Timestamp.type, hashes_length_modifier
|
2021-08-16 15:13:01 +00:00
|
|
|
)
|
|
|
|
|
|
2021-07-01 17:14:13 +00:00
|
|
|
def setUp(self) -> None:
|
2023-10-12 09:14:15 +00:00
|
|
|
self.trusted_set = TrustedMetadataSet(
|
|
|
|
|
self.metadata[Root.type], EnvelopeType.METADATA
|
|
|
|
|
)
|
2021-05-21 10:51:49 +00:00
|
|
|
|
2021-07-19 11:49:03 +00:00
|
|
|
def _update_all_besides_targets(
|
|
|
|
|
self,
|
2024-11-29 10:29:32 +00:00
|
|
|
timestamp_bytes: bytes | None = None,
|
|
|
|
|
snapshot_bytes: bytes | None = None,
|
2021-11-18 16:58:16 +00:00
|
|
|
) -> None:
|
2021-07-19 11:49:03 +00:00
|
|
|
"""Update all metadata roles besides targets.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
timestamp_bytes:
|
|
|
|
|
Bytes used when calling trusted_set.update_timestamp().
|
2021-11-17 12:24:03 +00:00
|
|
|
Default self.metadata[Timestamp.type].
|
2021-07-19 11:49:03 +00:00
|
|
|
snapshot_bytes:
|
|
|
|
|
Bytes used when calling trusted_set.update_snapshot().
|
2021-11-17 12:24:03 +00:00
|
|
|
Default self.metadata[Snapshot.type].
|
2021-07-19 11:49:03 +00:00
|
|
|
|
|
|
|
|
"""
|
2021-08-18 12:32:40 +00:00
|
|
|
|
2021-11-17 12:24:03 +00:00
|
|
|
timestamp_bytes = timestamp_bytes or self.metadata[Timestamp.type]
|
2021-08-18 12:32:40 +00:00
|
|
|
self.trusted_set.update_timestamp(timestamp_bytes)
|
2021-11-17 12:24:03 +00:00
|
|
|
snapshot_bytes = snapshot_bytes or self.metadata[Snapshot.type]
|
2021-07-19 11:49:03 +00:00
|
|
|
self.trusted_set.update_snapshot(snapshot_bytes)
|
|
|
|
|
|
2021-11-18 16:58:16 +00:00
|
|
|
def test_update(self) -> None:
|
2021-11-17 12:24:03 +00:00
|
|
|
self.trusted_set.update_timestamp(self.metadata[Timestamp.type])
|
|
|
|
|
self.trusted_set.update_snapshot(self.metadata[Snapshot.type])
|
|
|
|
|
self.trusted_set.update_targets(self.metadata[Targets.type])
|
2021-07-01 17:14:13 +00:00
|
|
|
self.trusted_set.update_delegated_targets(
|
2021-11-17 12:24:03 +00:00
|
|
|
self.metadata["role1"], "role1", Targets.type
|
2021-06-29 16:14:56 +00:00
|
|
|
)
|
2021-07-01 17:14:13 +00:00
|
|
|
self.trusted_set.update_delegated_targets(
|
2021-06-29 16:14:56 +00:00
|
|
|
self.metadata["role2"], "role2", "role1"
|
|
|
|
|
)
|
2021-07-01 17:14:13 +00:00
|
|
|
# the 4 top level metadata objects + 2 additional delegated targets
|
|
|
|
|
self.assertTrue(len(self.trusted_set), 6)
|
2021-05-21 10:51:49 +00:00
|
|
|
|
2021-08-17 12:50:01 +00:00
|
|
|
count = 0
|
|
|
|
|
for md in self.trusted_set:
|
2023-08-09 09:19:39 +00:00
|
|
|
self.assertIsInstance(md, Signed)
|
2021-08-17 12:50:01 +00:00
|
|
|
count += 1
|
|
|
|
|
|
|
|
|
|
self.assertTrue(count, 6)
|
|
|
|
|
|
2021-11-18 16:58:16 +00:00
|
|
|
def test_update_metadata_output(self) -> None:
|
|
|
|
|
timestamp = self.trusted_set.update_timestamp(
|
|
|
|
|
self.metadata["timestamp"]
|
|
|
|
|
)
|
2021-11-16 15:24:21 +00:00
|
|
|
snapshot = self.trusted_set.update_snapshot(self.metadata["snapshot"])
|
|
|
|
|
targets = self.trusted_set.update_targets(self.metadata["targets"])
|
2025-03-10 20:48:43 +00:00
|
|
|
delegated_targets_1 = self.trusted_set.update_delegated_targets(
|
2021-11-16 15:24:21 +00:00
|
|
|
self.metadata["role1"], "role1", "targets"
|
|
|
|
|
)
|
2025-03-10 20:48:43 +00:00
|
|
|
delegated_targets_2 = self.trusted_set.update_delegated_targets(
|
2021-11-16 15:24:21 +00:00
|
|
|
self.metadata["role2"], "role2", "role1"
|
|
|
|
|
)
|
2023-08-09 09:19:39 +00:00
|
|
|
self.assertIsInstance(timestamp, Timestamp)
|
|
|
|
|
self.assertIsInstance(snapshot, Snapshot)
|
|
|
|
|
self.assertIsInstance(targets, Targets)
|
2025-03-10 20:48:43 +00:00
|
|
|
self.assertIsInstance(delegated_targets_1, Targets)
|
|
|
|
|
self.assertIsInstance(delegated_targets_2, Targets)
|
2021-11-16 15:24:21 +00:00
|
|
|
|
2021-11-18 16:58:16 +00:00
|
|
|
def test_out_of_order_ops(self) -> None:
|
2021-05-21 10:51:49 +00:00
|
|
|
# Update snapshot before timestamp
|
|
|
|
|
with self.assertRaises(RuntimeError):
|
2021-11-17 12:24:03 +00:00
|
|
|
self.trusted_set.update_snapshot(self.metadata[Snapshot.type])
|
2021-05-21 10:51:49 +00:00
|
|
|
|
2021-11-17 12:24:03 +00:00
|
|
|
self.trusted_set.update_timestamp(self.metadata[Timestamp.type])
|
2021-05-21 10:51:49 +00:00
|
|
|
|
2021-08-18 06:43:19 +00:00
|
|
|
# Update root after timestamp
|
|
|
|
|
with self.assertRaises(RuntimeError):
|
2021-11-17 12:24:03 +00:00
|
|
|
self.trusted_set.update_root(self.metadata[Root.type])
|
2021-08-18 06:43:19 +00:00
|
|
|
|
2021-05-21 10:51:49 +00:00
|
|
|
# Update targets before snapshot
|
|
|
|
|
with self.assertRaises(RuntimeError):
|
2021-11-17 12:24:03 +00:00
|
|
|
self.trusted_set.update_targets(self.metadata[Targets.type])
|
2021-05-21 10:51:49 +00:00
|
|
|
|
2021-11-17 12:24:03 +00:00
|
|
|
self.trusted_set.update_snapshot(self.metadata[Snapshot.type])
|
2021-05-21 10:51:49 +00:00
|
|
|
|
2021-06-29 16:14:56 +00:00
|
|
|
# update timestamp after snapshot
|
2021-05-21 10:51:49 +00:00
|
|
|
with self.assertRaises(RuntimeError):
|
2021-11-17 12:24:03 +00:00
|
|
|
self.trusted_set.update_timestamp(self.metadata[Timestamp.type])
|
2021-05-21 10:51:49 +00:00
|
|
|
|
|
|
|
|
# Update delegated targets before targets
|
|
|
|
|
with self.assertRaises(RuntimeError):
|
2021-07-01 17:14:13 +00:00
|
|
|
self.trusted_set.update_delegated_targets(
|
2021-11-17 12:24:03 +00:00
|
|
|
self.metadata["role1"], "role1", Targets.type
|
2021-06-29 16:14:56 +00:00
|
|
|
)
|
2021-05-21 10:51:49 +00:00
|
|
|
|
2021-11-17 12:24:03 +00:00
|
|
|
self.trusted_set.update_targets(self.metadata[Targets.type])
|
2021-05-21 10:51:49 +00:00
|
|
|
|
2025-03-10 20:48:43 +00:00
|
|
|
# Update snapshot after successful targets update
|
2021-07-01 17:14:13 +00:00
|
|
|
with self.assertRaises(RuntimeError):
|
2021-11-17 12:24:03 +00:00
|
|
|
self.trusted_set.update_snapshot(self.metadata[Snapshot.type])
|
2021-07-01 17:14:13 +00:00
|
|
|
|
|
|
|
|
self.trusted_set.update_delegated_targets(
|
2021-11-17 12:24:03 +00:00
|
|
|
self.metadata["role1"], "role1", Targets.type
|
2021-06-29 16:14:56 +00:00
|
|
|
)
|
2021-05-21 10:51:49 +00:00
|
|
|
|
2023-10-12 09:14:15 +00:00
|
|
|
def test_bad_initial_root(self) -> None:
|
|
|
|
|
# root is not json
|
|
|
|
|
with self.assertRaises(exceptions.RepositoryError):
|
|
|
|
|
TrustedMetadataSet(b"", EnvelopeType.METADATA)
|
2021-07-01 17:14:13 +00:00
|
|
|
|
2023-10-12 09:14:15 +00:00
|
|
|
# root is invalid
|
|
|
|
|
root = Metadata.from_bytes(self.metadata[Root.type])
|
|
|
|
|
root.signed.version += 1
|
|
|
|
|
with self.assertRaises(exceptions.UnsignedMetadataError):
|
|
|
|
|
TrustedMetadataSet(root.to_bytes(), EnvelopeType.METADATA)
|
2021-05-21 10:51:49 +00:00
|
|
|
|
2023-10-12 09:14:15 +00:00
|
|
|
# metadata is of wrong type
|
|
|
|
|
with self.assertRaises(exceptions.RepositoryError):
|
|
|
|
|
TrustedMetadataSet(
|
|
|
|
|
self.metadata[Snapshot.type], EnvelopeType.METADATA
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def test_bad_root_update(self) -> None:
|
|
|
|
|
# root is not json
|
|
|
|
|
with self.assertRaises(exceptions.RepositoryError):
|
|
|
|
|
self.trusted_set.update_root(b"")
|
|
|
|
|
|
|
|
|
|
# root is invalid
|
|
|
|
|
root = Metadata.from_bytes(self.metadata[Root.type])
|
|
|
|
|
root.signed.version += 1
|
|
|
|
|
with self.assertRaises(exceptions.UnsignedMetadataError):
|
|
|
|
|
self.trusted_set.update_root(root.to_bytes())
|
|
|
|
|
|
|
|
|
|
# metadata is of wrong type
|
|
|
|
|
with self.assertRaises(exceptions.RepositoryError):
|
|
|
|
|
self.trusted_set.update_root(self.metadata[Snapshot.type])
|
2021-07-01 17:14:13 +00:00
|
|
|
|
2021-11-18 16:58:16 +00:00
|
|
|
def test_top_level_md_with_invalid_json(self) -> None:
|
2024-11-04 04:21:23 +00:00
|
|
|
top_level_md: list[tuple[bytes, Callable[[bytes], Signed]]] = [
|
2021-11-17 12:24:03 +00:00
|
|
|
(self.metadata[Timestamp.type], self.trusted_set.update_timestamp),
|
|
|
|
|
(self.metadata[Snapshot.type], self.trusted_set.update_snapshot),
|
|
|
|
|
(self.metadata[Targets.type], self.trusted_set.update_targets),
|
2021-05-21 10:51:49 +00:00
|
|
|
]
|
|
|
|
|
for metadata, update_func in top_level_md:
|
2021-07-01 17:14:13 +00:00
|
|
|
md = Metadata.from_bytes(metadata)
|
2021-05-21 10:51:49 +00:00
|
|
|
# metadata is not json
|
|
|
|
|
with self.assertRaises(exceptions.RepositoryError):
|
|
|
|
|
update_func(b"")
|
2021-09-02 12:24:16 +00:00
|
|
|
|
2021-05-21 10:51:49 +00:00
|
|
|
# metadata is invalid
|
|
|
|
|
md.signed.version += 1
|
2021-09-02 12:24:16 +00:00
|
|
|
with self.assertRaises(exceptions.UnsignedMetadataError):
|
2021-07-16 13:18:32 +00:00
|
|
|
update_func(md.to_bytes())
|
2021-05-21 10:51:49 +00:00
|
|
|
|
|
|
|
|
# metadata is of wrong type
|
|
|
|
|
with self.assertRaises(exceptions.RepositoryError):
|
2021-11-17 12:24:03 +00:00
|
|
|
update_func(self.metadata[Root.type])
|
2021-05-21 10:51:49 +00:00
|
|
|
|
|
|
|
|
update_func(metadata)
|
|
|
|
|
|
2021-11-18 16:58:16 +00:00
|
|
|
def test_update_root_new_root(self) -> None:
|
2021-08-17 10:54:18 +00:00
|
|
|
# test that root can be updated with a new valid version
|
|
|
|
|
def root_new_version_modifier(root: Root) -> None:
|
|
|
|
|
root.version += 1
|
|
|
|
|
|
2021-11-17 12:24:03 +00:00
|
|
|
root = self.modify_metadata(Root.type, root_new_version_modifier)
|
2021-08-17 10:54:18 +00:00
|
|
|
self.trusted_set.update_root(root)
|
2021-05-21 10:51:49 +00:00
|
|
|
|
2021-11-18 16:58:16 +00:00
|
|
|
def test_update_root_new_root_fail_threshold_verification(self) -> None:
|
2022-04-01 07:10:04 +00:00
|
|
|
# Increase threshold in new root, do not add enough keys
|
|
|
|
|
def root_threshold_bump(root: Root) -> None:
|
|
|
|
|
root.version += 1
|
|
|
|
|
root.roles[Root.type].threshold += 1
|
|
|
|
|
|
|
|
|
|
root = self.modify_metadata(Root.type, root_threshold_bump)
|
2021-07-01 17:14:13 +00:00
|
|
|
with self.assertRaises(exceptions.UnsignedMetadataError):
|
2022-04-01 07:10:04 +00:00
|
|
|
self.trusted_set.update_root(root)
|
2021-07-01 17:14:13 +00:00
|
|
|
|
2021-11-18 16:58:16 +00:00
|
|
|
def test_update_root_new_root_ver_same_as_trusted_root_ver(self) -> None:
|
2021-12-16 17:58:39 +00:00
|
|
|
with self.assertRaises(exceptions.BadVersionNumberError):
|
2021-11-17 12:24:03 +00:00
|
|
|
self.trusted_set.update_root(self.metadata[Root.type])
|
2021-07-01 17:14:13 +00:00
|
|
|
|
2021-11-18 16:58:16 +00:00
|
|
|
def test_root_expired_final_root(self) -> None:
|
2021-08-16 15:13:01 +00:00
|
|
|
def root_expired_modifier(root: Root) -> None:
|
2024-02-29 13:45:56 +00:00
|
|
|
root.expires = datetime(1970, 1, 1, tzinfo=timezone.utc)
|
2021-10-12 13:14:24 +00:00
|
|
|
|
2021-08-18 06:43:19 +00:00
|
|
|
# intermediate root can be expired
|
2021-11-17 12:24:03 +00:00
|
|
|
root = self.modify_metadata(Root.type, root_expired_modifier)
|
2023-10-12 09:14:15 +00:00
|
|
|
tmp_trusted_set = TrustedMetadataSet(root, EnvelopeType.METADATA)
|
2021-08-18 06:43:19 +00:00
|
|
|
# update timestamp to trigger final root expiry check
|
2021-07-01 17:14:13 +00:00
|
|
|
with self.assertRaises(exceptions.ExpiredMetadataError):
|
2021-11-17 12:24:03 +00:00
|
|
|
tmp_trusted_set.update_timestamp(self.metadata[Timestamp.type])
|
2021-07-01 17:14:13 +00:00
|
|
|
|
2021-11-18 16:58:16 +00:00
|
|
|
def test_update_timestamp_new_timestamp_ver_below_trusted_ver(self) -> None:
|
2021-07-01 17:14:13 +00:00
|
|
|
# new_timestamp.version < trusted_timestamp.version
|
2021-08-16 15:13:01 +00:00
|
|
|
def version_modifier(timestamp: Timestamp) -> None:
|
|
|
|
|
timestamp.version = 3
|
2021-10-12 13:14:24 +00:00
|
|
|
|
2021-11-17 12:24:03 +00:00
|
|
|
timestamp = self.modify_metadata(Timestamp.type, version_modifier)
|
2021-08-18 12:32:40 +00:00
|
|
|
self.trusted_set.update_timestamp(timestamp)
|
2021-12-16 17:58:39 +00:00
|
|
|
with self.assertRaises(exceptions.BadVersionNumberError):
|
2021-11-17 12:24:03 +00:00
|
|
|
self.trusted_set.update_timestamp(self.metadata[Timestamp.type])
|
2021-07-01 17:14:13 +00:00
|
|
|
|
2022-06-13 19:52:02 +00:00
|
|
|
def test_update_timestamp_with_same_timestamp(self) -> None:
|
|
|
|
|
# Test that timestamp is NOT updated if:
|
|
|
|
|
# new_timestamp.version == trusted_timestamp.version
|
|
|
|
|
self.trusted_set.update_timestamp(self.metadata[Timestamp.type])
|
|
|
|
|
initial_timestamp = self.trusted_set.timestamp
|
|
|
|
|
|
|
|
|
|
# Update timestamp with the same version.
|
|
|
|
|
with self.assertRaises(exceptions.EqualVersionNumberError):
|
2024-03-27 13:29:53 +00:00
|
|
|
self.trusted_set.update_timestamp(self.metadata[Timestamp.type])
|
2022-06-13 19:52:02 +00:00
|
|
|
|
|
|
|
|
# Every object has a unique id() if they are equal, this means timestamp
|
|
|
|
|
# was not updated.
|
|
|
|
|
self.assertEqual(id(initial_timestamp), id(self.trusted_set.timestamp))
|
|
|
|
|
|
2021-11-18 16:58:16 +00:00
|
|
|
def test_update_timestamp_snapshot_ver_below_current(self) -> None:
|
2021-08-17 10:54:18 +00:00
|
|
|
def bump_snapshot_version(timestamp: Timestamp) -> None:
|
2021-06-14 11:13:15 +00:00
|
|
|
timestamp.snapshot_meta.version = 2
|
2022-06-13 19:52:02 +00:00
|
|
|
# The timestamp version must be increased to initiate a update.
|
|
|
|
|
timestamp.version += 1
|
2021-08-17 10:54:18 +00:00
|
|
|
|
|
|
|
|
# set current known snapshot.json version to 2
|
2021-11-17 12:24:03 +00:00
|
|
|
timestamp = self.modify_metadata(Timestamp.type, bump_snapshot_version)
|
2021-08-18 12:32:40 +00:00
|
|
|
self.trusted_set.update_timestamp(timestamp)
|
2021-07-21 14:34:23 +00:00
|
|
|
|
2021-06-14 11:13:15 +00:00
|
|
|
# newtimestamp.meta.version < trusted_timestamp.meta.version
|
2021-12-16 17:58:39 +00:00
|
|
|
with self.assertRaises(exceptions.BadVersionNumberError):
|
2021-11-17 12:24:03 +00:00
|
|
|
self.trusted_set.update_timestamp(self.metadata[Timestamp.type])
|
2021-07-01 17:14:13 +00:00
|
|
|
|
2021-11-18 16:58:16 +00:00
|
|
|
def test_update_timestamp_expired(self) -> None:
|
2021-07-01 17:14:13 +00:00
|
|
|
# new_timestamp has expired
|
2021-08-16 15:13:01 +00:00
|
|
|
def timestamp_expired_modifier(timestamp: Timestamp) -> None:
|
2024-02-29 13:45:56 +00:00
|
|
|
timestamp.expires = datetime(1970, 1, 1, tzinfo=timezone.utc)
|
2021-08-16 15:13:01 +00:00
|
|
|
|
2021-09-02 15:24:50 +00:00
|
|
|
# expired intermediate timestamp is loaded but raises
|
2021-10-12 13:14:24 +00:00
|
|
|
timestamp = self.modify_metadata(
|
2021-11-17 12:24:03 +00:00
|
|
|
Timestamp.type, timestamp_expired_modifier
|
2021-10-12 13:14:24 +00:00
|
|
|
)
|
2021-09-02 15:24:50 +00:00
|
|
|
with self.assertRaises(exceptions.ExpiredMetadataError):
|
|
|
|
|
self.trusted_set.update_timestamp(timestamp)
|
2021-08-18 07:02:33 +00:00
|
|
|
|
2021-09-02 15:24:50 +00:00
|
|
|
# snapshot update does start but fails because timestamp is expired
|
2021-07-01 17:14:13 +00:00
|
|
|
with self.assertRaises(exceptions.ExpiredMetadataError):
|
2021-11-17 12:24:03 +00:00
|
|
|
self.trusted_set.update_snapshot(self.metadata[Snapshot.type])
|
2021-07-01 17:14:13 +00:00
|
|
|
|
2021-11-18 16:58:16 +00:00
|
|
|
def test_update_snapshot_length_or_hash_mismatch(self) -> None:
|
2021-08-17 12:40:13 +00:00
|
|
|
def modify_snapshot_length(timestamp: Timestamp) -> None:
|
2021-06-14 11:13:15 +00:00
|
|
|
timestamp.snapshot_meta.length = 1
|
2021-08-17 12:40:13 +00:00
|
|
|
|
|
|
|
|
# set known snapshot.json length to 1
|
2021-11-17 12:24:03 +00:00
|
|
|
timestamp = self.modify_metadata(Timestamp.type, modify_snapshot_length)
|
2021-08-18 12:32:40 +00:00
|
|
|
self.trusted_set.update_timestamp(timestamp)
|
2021-08-17 12:40:13 +00:00
|
|
|
|
|
|
|
|
with self.assertRaises(exceptions.RepositoryError):
|
2021-11-17 12:24:03 +00:00
|
|
|
self.trusted_set.update_snapshot(self.metadata[Snapshot.type])
|
2021-07-01 17:14:13 +00:00
|
|
|
|
2021-11-18 16:58:16 +00:00
|
|
|
def test_update_snapshot_fail_threshold_verification(self) -> None:
|
2021-11-17 12:24:03 +00:00
|
|
|
self.trusted_set.update_timestamp(self.metadata[Timestamp.type])
|
|
|
|
|
snapshot = Metadata.from_bytes(self.metadata[Snapshot.type])
|
2021-07-19 11:49:03 +00:00
|
|
|
snapshot.signatures.clear()
|
2021-07-01 17:14:13 +00:00
|
|
|
with self.assertRaises(exceptions.UnsignedMetadataError):
|
2021-07-19 11:49:03 +00:00
|
|
|
self.trusted_set.update_snapshot(snapshot.to_bytes())
|
2021-07-01 17:14:13 +00:00
|
|
|
|
2021-11-18 16:58:16 +00:00
|
|
|
def test_update_snapshot_version_diverge_timestamp_snapshot_version(
|
|
|
|
|
self,
|
|
|
|
|
) -> None:
|
2021-08-16 15:13:01 +00:00
|
|
|
def timestamp_version_modifier(timestamp: Timestamp) -> None:
|
2021-06-14 11:13:15 +00:00
|
|
|
timestamp.snapshot_meta.version = 2
|
2021-07-21 14:34:23 +00:00
|
|
|
|
2021-10-12 13:14:24 +00:00
|
|
|
timestamp = self.modify_metadata(
|
2021-11-17 12:24:03 +00:00
|
|
|
Timestamp.type, timestamp_version_modifier
|
2021-10-12 13:14:24 +00:00
|
|
|
)
|
2021-08-18 12:32:40 +00:00
|
|
|
self.trusted_set.update_timestamp(timestamp)
|
2021-08-16 15:13:01 +00:00
|
|
|
|
2021-09-02 15:24:50 +00:00
|
|
|
# if intermediate snapshot version is incorrect, load it but also raise
|
|
|
|
|
with self.assertRaises(exceptions.BadVersionNumberError):
|
2021-11-17 12:24:03 +00:00
|
|
|
self.trusted_set.update_snapshot(self.metadata[Snapshot.type])
|
2021-08-18 07:54:52 +00:00
|
|
|
|
2021-09-02 15:24:50 +00:00
|
|
|
# targets update starts but fails if snapshot version does not match
|
2021-07-01 17:14:13 +00:00
|
|
|
with self.assertRaises(exceptions.BadVersionNumberError):
|
2021-11-17 12:24:03 +00:00
|
|
|
self.trusted_set.update_targets(self.metadata[Targets.type])
|
2021-08-18 07:54:52 +00:00
|
|
|
|
2021-11-18 16:58:16 +00:00
|
|
|
def test_update_snapshot_file_removed_from_meta(self) -> None:
|
2021-11-17 12:24:03 +00:00
|
|
|
self._update_all_besides_targets(self.metadata[Timestamp.type])
|
2021-10-12 13:14:24 +00:00
|
|
|
|
2021-08-18 07:54:52 +00:00
|
|
|
def remove_file_from_meta(snapshot: Snapshot) -> None:
|
|
|
|
|
del snapshot.meta["targets.json"]
|
2021-08-16 15:13:01 +00:00
|
|
|
|
2021-08-18 07:54:52 +00:00
|
|
|
# Test removing a meta_file in new_snapshot compared to the old snapshot
|
2021-11-17 12:24:03 +00:00
|
|
|
snapshot = self.modify_metadata(Snapshot.type, remove_file_from_meta)
|
2021-07-01 17:14:13 +00:00
|
|
|
with self.assertRaises(exceptions.RepositoryError):
|
2021-08-16 15:13:01 +00:00
|
|
|
self.trusted_set.update_snapshot(snapshot)
|
2021-07-01 17:14:13 +00:00
|
|
|
|
2021-11-18 16:58:16 +00:00
|
|
|
def test_update_snapshot_meta_version_decreases(self) -> None:
|
2021-11-17 12:24:03 +00:00
|
|
|
self.trusted_set.update_timestamp(self.metadata[Timestamp.type])
|
2021-08-18 07:54:52 +00:00
|
|
|
|
2021-08-16 15:13:01 +00:00
|
|
|
def version_meta_modifier(snapshot: Snapshot) -> None:
|
2021-08-18 07:54:52 +00:00
|
|
|
snapshot.meta["targets.json"].version += 1
|
2021-08-16 15:13:01 +00:00
|
|
|
|
2021-11-17 12:24:03 +00:00
|
|
|
snapshot = self.modify_metadata(Snapshot.type, version_meta_modifier)
|
2021-08-16 15:13:01 +00:00
|
|
|
self.trusted_set.update_snapshot(snapshot)
|
2021-08-18 07:54:52 +00:00
|
|
|
|
2021-07-01 17:14:13 +00:00
|
|
|
with self.assertRaises(exceptions.BadVersionNumberError):
|
2021-11-17 12:24:03 +00:00
|
|
|
self.trusted_set.update_snapshot(self.metadata[Snapshot.type])
|
2021-07-01 17:14:13 +00:00
|
|
|
|
2021-11-18 16:58:16 +00:00
|
|
|
def test_update_snapshot_expired_new_snapshot(self) -> None:
|
2021-11-17 12:24:03 +00:00
|
|
|
self.trusted_set.update_timestamp(self.metadata[Timestamp.type])
|
2021-10-12 13:14:24 +00:00
|
|
|
|
2021-08-16 15:13:01 +00:00
|
|
|
def snapshot_expired_modifier(snapshot: Snapshot) -> None:
|
2024-02-29 13:45:56 +00:00
|
|
|
snapshot.expires = datetime(1970, 1, 1, tzinfo=timezone.utc)
|
2021-08-16 15:13:01 +00:00
|
|
|
|
2021-09-02 15:24:50 +00:00
|
|
|
# expired intermediate snapshot is loaded but will raise
|
2021-12-03 16:11:40 +00:00
|
|
|
snapshot = self.modify_metadata(
|
|
|
|
|
Snapshot.type, snapshot_expired_modifier
|
|
|
|
|
)
|
2021-09-02 15:24:50 +00:00
|
|
|
with self.assertRaises(exceptions.ExpiredMetadataError):
|
|
|
|
|
self.trusted_set.update_snapshot(snapshot)
|
2021-08-18 07:02:33 +00:00
|
|
|
|
2021-09-02 15:24:50 +00:00
|
|
|
# targets update does start but fails because snapshot is expired
|
2021-07-01 17:14:13 +00:00
|
|
|
with self.assertRaises(exceptions.ExpiredMetadataError):
|
2021-11-17 12:24:03 +00:00
|
|
|
self.trusted_set.update_targets(self.metadata[Targets.type])
|
2021-07-01 17:14:13 +00:00
|
|
|
|
2021-11-18 16:58:16 +00:00
|
|
|
def test_update_snapshot_successful_rollback_checks(self) -> None:
|
2021-08-18 07:54:52 +00:00
|
|
|
def meta_version_bump(timestamp: Timestamp) -> None:
|
2021-06-14 11:13:15 +00:00
|
|
|
timestamp.snapshot_meta.version += 1
|
2022-06-13 19:52:02 +00:00
|
|
|
# The timestamp version must be increased to initiate a update.
|
|
|
|
|
timestamp.version += 1
|
2021-08-18 07:54:52 +00:00
|
|
|
|
|
|
|
|
def version_bump(snapshot: Snapshot) -> None:
|
|
|
|
|
snapshot.version += 1
|
|
|
|
|
|
|
|
|
|
# load a "local" timestamp, then update to newer one:
|
2021-11-17 12:24:03 +00:00
|
|
|
self.trusted_set.update_timestamp(self.metadata[Timestamp.type])
|
|
|
|
|
new_timestamp = self.modify_metadata(Timestamp.type, meta_version_bump)
|
2021-08-18 07:54:52 +00:00
|
|
|
self.trusted_set.update_timestamp(new_timestamp)
|
|
|
|
|
|
2021-09-02 15:24:50 +00:00
|
|
|
# load a "local" snapshot with mismatching version (loading happens but
|
|
|
|
|
# BadVersionNumberError is raised), then update to newer one:
|
|
|
|
|
with self.assertRaises(exceptions.BadVersionNumberError):
|
2021-11-17 12:24:03 +00:00
|
|
|
self.trusted_set.update_snapshot(self.metadata[Snapshot.type])
|
|
|
|
|
new_snapshot = self.modify_metadata(Snapshot.type, version_bump)
|
2021-08-18 07:54:52 +00:00
|
|
|
self.trusted_set.update_snapshot(new_snapshot)
|
|
|
|
|
|
|
|
|
|
# update targets to trigger final snapshot meta version check
|
2021-11-17 12:24:03 +00:00
|
|
|
self.trusted_set.update_targets(self.metadata[Targets.type])
|
2021-08-18 07:54:52 +00:00
|
|
|
|
2021-11-18 16:58:16 +00:00
|
|
|
def test_update_targets_no_meta_in_snapshot(self) -> None:
|
2021-08-16 15:13:01 +00:00
|
|
|
def no_meta_modifier(snapshot: Snapshot) -> None:
|
2021-07-21 14:34:23 +00:00
|
|
|
snapshot.meta = {}
|
|
|
|
|
|
2021-11-17 12:24:03 +00:00
|
|
|
snapshot = self.modify_metadata(Snapshot.type, no_meta_modifier)
|
2021-12-03 16:11:40 +00:00
|
|
|
self._update_all_besides_targets(
|
|
|
|
|
self.metadata[Timestamp.type], snapshot
|
|
|
|
|
)
|
2021-07-01 17:14:13 +00:00
|
|
|
# remove meta information with information about targets from snapshot
|
|
|
|
|
with self.assertRaises(exceptions.RepositoryError):
|
2021-11-17 12:24:03 +00:00
|
|
|
self.trusted_set.update_targets(self.metadata[Targets.type])
|
2021-07-01 17:14:13 +00:00
|
|
|
|
2021-11-18 16:58:16 +00:00
|
|
|
def test_update_targets_hash_diverge_from_snapshot_meta_hash(self) -> None:
|
2021-08-16 15:13:01 +00:00
|
|
|
def meta_length_modifier(snapshot: Snapshot) -> None:
|
2021-07-21 14:34:23 +00:00
|
|
|
for metafile_path in snapshot.meta:
|
|
|
|
|
snapshot.meta[metafile_path] = MetaFile(version=1, length=1)
|
|
|
|
|
|
2021-11-17 12:24:03 +00:00
|
|
|
snapshot = self.modify_metadata(Snapshot.type, meta_length_modifier)
|
2021-12-03 16:11:40 +00:00
|
|
|
self._update_all_besides_targets(
|
|
|
|
|
self.metadata[Timestamp.type], snapshot
|
|
|
|
|
)
|
2021-07-01 17:14:13 +00:00
|
|
|
# observed_hash != stored hash in snapshot meta for targets
|
|
|
|
|
with self.assertRaises(exceptions.RepositoryError):
|
2021-11-17 12:24:03 +00:00
|
|
|
self.trusted_set.update_targets(self.metadata[Targets.type])
|
2021-07-01 17:14:13 +00:00
|
|
|
|
2021-11-18 16:58:16 +00:00
|
|
|
def test_update_targets_version_diverge_snapshot_meta_version(self) -> None:
|
2021-08-16 15:13:01 +00:00
|
|
|
def meta_modifier(snapshot: Snapshot) -> None:
|
2021-07-21 14:34:23 +00:00
|
|
|
for metafile_path in snapshot.meta:
|
|
|
|
|
snapshot.meta[metafile_path] = MetaFile(version=2)
|
|
|
|
|
|
2021-11-17 12:24:03 +00:00
|
|
|
snapshot = self.modify_metadata(Snapshot.type, meta_modifier)
|
2021-12-03 16:11:40 +00:00
|
|
|
self._update_all_besides_targets(
|
|
|
|
|
self.metadata[Timestamp.type], snapshot
|
|
|
|
|
)
|
2021-07-01 17:14:13 +00:00
|
|
|
# new_delegate.signed.version != meta.version stored in snapshot
|
|
|
|
|
with self.assertRaises(exceptions.BadVersionNumberError):
|
2021-11-17 12:24:03 +00:00
|
|
|
self.trusted_set.update_targets(self.metadata[Targets.type])
|
2021-07-01 17:14:13 +00:00
|
|
|
|
2021-11-18 16:58:16 +00:00
|
|
|
def test_update_targets_expired_new_target(self) -> None:
|
2021-07-01 17:14:13 +00:00
|
|
|
self._update_all_besides_targets()
|
2023-02-01 14:33:03 +00:00
|
|
|
|
2021-07-01 17:14:13 +00:00
|
|
|
# new_delegated_target has expired
|
2021-08-16 15:13:01 +00:00
|
|
|
def target_expired_modifier(target: Targets) -> None:
|
2024-02-29 13:45:56 +00:00
|
|
|
target.expires = datetime(1970, 1, 1, tzinfo=timezone.utc)
|
2021-08-16 15:13:01 +00:00
|
|
|
|
2021-11-17 12:24:03 +00:00
|
|
|
targets = self.modify_metadata(Targets.type, target_expired_modifier)
|
2021-07-01 17:14:13 +00:00
|
|
|
with self.assertRaises(exceptions.ExpiredMetadataError):
|
2021-08-16 15:13:01 +00:00
|
|
|
self.trusted_set.update_targets(targets)
|
2021-07-01 17:14:13 +00:00
|
|
|
|
2021-05-21 10:51:49 +00:00
|
|
|
# TODO test updating over initial metadata (new keys, newer timestamp, etc)
|
|
|
|
|
|
2023-10-12 09:55:45 +00:00
|
|
|
def test_load_from_simple_envelope(self) -> None:
|
|
|
|
|
"""Basic unit test for ``_load_from_simple_envelope`` helper.
|
|
|
|
|
|
|
|
|
|
TODO: Test via trusted metadata set tests like for traditional metadata
|
|
|
|
|
"""
|
|
|
|
|
metadata = Metadata.from_bytes(self.metadata[Root.type])
|
|
|
|
|
root = metadata.signed
|
|
|
|
|
envelope = SimpleEnvelope.from_signed(root)
|
|
|
|
|
|
|
|
|
|
# Unwrap unsigned envelope without verification
|
|
|
|
|
envelope_bytes = envelope.to_bytes()
|
|
|
|
|
payload_obj, signed_bytes, signatures = _load_from_simple_envelope(
|
|
|
|
|
Root, envelope_bytes
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self.assertEqual(payload_obj, root)
|
|
|
|
|
self.assertEqual(signed_bytes, envelope.pae())
|
|
|
|
|
self.assertDictEqual(signatures, {})
|
|
|
|
|
|
|
|
|
|
# Unwrap correctly signed envelope (use default role name)
|
|
|
|
|
sig = envelope.sign(self.keystore[Root.type])
|
|
|
|
|
envelope_bytes = envelope.to_bytes()
|
|
|
|
|
_, _, signatures = _load_from_simple_envelope(
|
|
|
|
|
Root, envelope_bytes, root
|
|
|
|
|
)
|
|
|
|
|
self.assertDictEqual(signatures, {sig.keyid: sig})
|
|
|
|
|
|
|
|
|
|
# Load correctly signed envelope (with explicit role name)
|
|
|
|
|
_, _, signatures = _load_from_simple_envelope(
|
|
|
|
|
Root, envelope.to_bytes(), root, Root.type
|
|
|
|
|
)
|
|
|
|
|
self.assertDictEqual(signatures, {sig.keyid: sig})
|
|
|
|
|
|
|
|
|
|
# Fail load envelope with unexpected 'payload_type'
|
|
|
|
|
envelope_bad_type = SimpleEnvelope.from_signed(root)
|
|
|
|
|
envelope_bad_type.payload_type = "foo"
|
|
|
|
|
envelope_bad_type_bytes = envelope_bad_type.to_bytes()
|
|
|
|
|
with self.assertRaises(exceptions.RepositoryError):
|
|
|
|
|
_load_from_simple_envelope(Root, envelope_bad_type_bytes)
|
|
|
|
|
|
|
|
|
|
# Fail load envelope with unexpected payload type
|
|
|
|
|
envelope_bad_signed = SimpleEnvelope.from_signed(root)
|
|
|
|
|
envelope_bad_signed_bytes = envelope_bad_signed.to_bytes()
|
|
|
|
|
with self.assertRaises(exceptions.RepositoryError):
|
|
|
|
|
_load_from_simple_envelope(Targets, envelope_bad_signed_bytes)
|
|
|
|
|
|
2021-05-21 10:51:49 +00:00
|
|
|
|
2021-10-12 13:14:24 +00:00
|
|
|
if __name__ == "__main__":
|
|
|
|
|
utils.configure_test_logging(sys.argv)
|
|
|
|
|
unittest.main()
|