diff --git a/tests/test_api.py b/tests/test_api.py index 1ebece61..c1076353 100755 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -15,6 +15,7 @@ import unittest from copy import copy, deepcopy from datetime import datetime, timedelta +from pathlib import Path from typing import Any, ClassVar, Dict, Optional from securesystemslib import exceptions as sslib_exceptions @@ -33,6 +34,7 @@ from tests import utils from tuf.api import exceptions +from tuf.api.dsse import SimpleEnvelope from tuf.api.metadata import ( TOP_LEVEL_ROLE_NAMES, DelegatedRole, @@ -1144,6 +1146,95 @@ def test_delegations_get_delegated_role(self) -> None: ) +class TestSimpleEnvelope(unittest.TestCase): + """Tests for public API in 'tuf/api/dsse.py'.""" + + @classmethod + def setUpClass(cls) -> None: + repo_data_dir = Path(utils.TESTS_DIR) / "repository_data" + cls.metadata_dir = repo_data_dir / "repository" / "metadata" + cls.signer_store = {} + for role in [Snapshot, Targets, Timestamp]: + key_path = repo_data_dir / "keystore" / f"{role.type}_key" + key = import_ed25519_privatekey_from_file( + str(key_path), + password="password", + ) + cls.signer_store[role.type] = SSlibSigner(key) + + def test_serialization(self) -> None: + """Basic de/serialization test. + + 1. Load test metadata for each role + 2. Wrap metadata payloads in envelope serializing the payload + 3. Serialize envelope + 4. De-serialize envelope + 5. De-serialize payload + + """ + for role in [Root, Timestamp, Snapshot, Targets]: + metadata_path = self.metadata_dir / f"{role.type}.json" + metadata = Metadata.from_file(str(metadata_path)) + self.assertIsInstance(metadata.signed, role) + + envelope = SimpleEnvelope.from_signed(metadata.signed) + envelope_bytes = envelope.to_bytes() + + envelope2 = SimpleEnvelope.from_bytes(envelope_bytes) + payload = envelope2.get_signed() + self.assertEqual(metadata.signed, payload) + + def test_fail_envelope_serialization(self) -> None: + envelope = SimpleEnvelope(b"foo", "bar", ["baz"]) + with self.assertRaises(SerializationError): + envelope.to_bytes() + + def test_fail_envelope_deserialization(self) -> None: + with self.assertRaises(DeserializationError): + SimpleEnvelope.from_bytes(b"[") + + def test_fail_payload_serialization(self) -> None: + with self.assertRaises(SerializationError): + SimpleEnvelope.from_signed("foo") # type: ignore + + def test_fail_payload_deserialization(self) -> None: + payloads = [b"[", b'{"_type": "foo"}'] + for payload in payloads: + envelope = SimpleEnvelope(payload, "bar", []) + with self.assertRaises(DeserializationError): + envelope.get_signed() + + def test_verify_delegate(self) -> None: + """Basic verification test. + + 1. Load test metadata for each role + 2. Wrap non-root payloads in envelope serializing the payload + 3. Sign with correct delegated key + 4. Verify delegate with root + + """ + root_path = self.metadata_dir / "root.json" + root = Metadata[Root].from_file(str(root_path)).signed + + for role in [Timestamp, Snapshot, Targets]: + metadata_path = self.metadata_dir / f"{role.type}.json" + metadata = Metadata.from_file(str(metadata_path)) + self.assertIsInstance(metadata.signed, role) + + signer = self.signer_store[role.type] + self.assertIn( + signer.key_dict["keyid"], root.roles[role.type].keyids + ) + + envelope = SimpleEnvelope.from_signed(metadata.signed) + envelope.sign(signer) + self.assertTrue(len(envelope.signatures) == 1) + + root.verify_delegate( + role.type, envelope.pae(), envelope.signatures_dict + ) + + # Run unit test. if __name__ == "__main__": utils.configure_test_logging(sys.argv) diff --git a/tests/test_trusted_metadata_set.py b/tests/test_trusted_metadata_set.py index 71d9bf16..b5ab042d 100644 --- a/tests/test_trusted_metadata_set.py +++ b/tests/test_trusted_metadata_set.py @@ -15,6 +15,7 @@ from tests import utils from tuf.api import exceptions +from tuf.api.dsse import SimpleEnvelope from tuf.api.metadata import ( Metadata, MetaFile, @@ -25,7 +26,10 @@ Timestamp, ) from tuf.api.serialization.json import JSONSerializer -from tuf.ngclient._internal.trusted_metadata_set import TrustedMetadataSet +from tuf.ngclient._internal.trusted_metadata_set import ( + TrustedMetadataSet, + _load_from_simple_envelope, +) from tuf.ngclient.config import EnvelopeType logger = logging.getLogger(__name__) @@ -490,6 +494,52 @@ def target_expired_modifier(target: Targets) -> None: # TODO test updating over initial metadata (new keys, newer timestamp, etc) + 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) + if __name__ == "__main__": utils.configure_test_logging(sys.argv)