#!/usr/bin/env python3 import base64 import subprocess import tempfile import unittest from pathlib import Path from domain_manifest_signing import ( SignerConfigurationError, canonical_manifest_payload, load_signer_config_from_environment, sign_domain_manifest, ) class DomainManifestSigningTests(unittest.TestCase): def setUp(self): self.temp = tempfile.TemporaryDirectory() self.root = Path(self.temp.name) self.private_key = self.root / "signer.pem" self.public_key = self.root / "signer-public.pem" subprocess.run( ["openssl", "genpkey", "-algorithm", "ED25519", "-out", self.private_key], check=True, capture_output=True, ) self.private_key.chmod(0o600) subprocess.run( ["openssl", "pkey", "-in", self.private_key, "-pubout", "-out", self.public_key], check=True, capture_output=True, ) def tearDown(self): self.temp.cleanup() def test_canonical_payload_matches_the_hololake_client_contract(self): encoded = canonical_manifest_payload( domain_id="DOM-FIFTH-0001", repository_id="REPO-012", signer_id="GH-LIGHTHOUSE-001", source_commit="b" * 40, ) self.assertEqual( encoded, b'{"domainId":"DOM-FIFTH-0001","repositoryId":"REPO-012",' b'"schema":"gh-aios.domain-manifest/v1","signerId":"GH-LIGHTHOUSE-001",' b'"sourceCommit":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"}', ) def test_signs_a_digest_bound_ed25519_envelope(self): envelope = sign_domain_manifest( private_key_path=self.private_key, domain_id="DOM-FIFTH-0001", repository_id="REPO-012", signer_id="GH-LIGHTHOUSE-001", source_commit="b" * 40, ) payload = canonical_manifest_payload( domain_id=envelope["domainId"], repository_id=envelope["repositoryId"], signer_id=envelope["signerId"], source_commit=envelope["sourceCommit"], ) signature_path = self.root / "signature.bin" payload_path = self.root / "payload.json" payload_path.write_bytes(payload) signature_path.write_bytes(base64.b64decode(envelope["signature"], validate=True)) verified = subprocess.run( [ "openssl", "pkeyutl", "-verify", "-rawin", "-pubin", "-inkey", self.public_key, "-sigfile", signature_path, "-in", payload_path, ], capture_output=True, ) self.assertEqual(verified.returncode, 0, verified.stderr.decode()) self.assertEqual(len(envelope["digest"]), 64) self.assertEqual(set(envelope), { "digest", "domainId", "repositoryId", "schema", "signature", "signerId", "sourceCommit", }) def test_payload_tampering_does_not_verify(self): envelope = sign_domain_manifest( private_key_path=self.private_key, domain_id="DOM-FIFTH-0001", repository_id="REPO-012", signer_id="GH-LIGHTHOUSE-001", source_commit="b" * 40, ) signature_path = self.root / "signature.bin" tampered_path = self.root / "tampered.json" signature_path.write_bytes(base64.b64decode(envelope["signature"], validate=True)) tampered = canonical_manifest_payload( domain_id="DOM-FIFTH-0001", repository_id="REPO-012", signer_id="GH-LIGHTHOUSE-001", source_commit="c" * 40, ) tampered_path.write_bytes(tampered) verified = subprocess.run( [ "openssl", "pkeyutl", "-verify", "-rawin", "-pubin", "-inkey", self.public_key, "-sigfile", signature_path, "-in", tampered_path, ], capture_output=True, ) self.assertNotEqual(verified.returncode, 0) def test_signing_is_unavailable_without_complete_environment_configuration(self): self.assertIsNone(load_signer_config_from_environment({})) with self.assertRaises(SignerConfigurationError): load_signer_config_from_environment({"LIGHTHOUSE_DOMAIN_SIGNING_KEY_PATH": str(self.private_key)}) def test_rejects_relative_or_permissive_private_key_files(self): with self.assertRaises(SignerConfigurationError): sign_domain_manifest( private_key_path=Path("relative.pem"), domain_id="DOM-FIFTH-0001", repository_id="REPO-012", signer_id="GH-LIGHTHOUSE-001", source_commit="b" * 40, ) self.private_key.chmod(0o644) with self.assertRaises(SignerConfigurationError): sign_domain_manifest( private_key_path=self.private_key, domain_id="DOM-FIFTH-0001", repository_id="REPO-012", signer_id="GH-LIGHTHOUSE-001", source_commit="b" * 40, ) def test_rejects_unbounded_identifiers_and_invalid_source_commits(self): for field, value in (("domain_id", "../../escape"), ("source_commit", "not-a-commit")): arguments = { "private_key_path": self.private_key, "domain_id": "DOM-FIFTH-0001", "repository_id": "REPO-012", "signer_id": "GH-LIGHTHOUSE-001", "source_commit": "b" * 40, } arguments[field] = value with self.assertRaises(SignerConfigurationError): sign_domain_manifest(**arguments) if __name__ == "__main__": unittest.main()