Bind installed releases to signed artifact receipts

This commit is contained in:
2026-07-22 20:42:05 +02:00
parent 4dc0c8d013
commit 9c0650b2ed
13 changed files with 2179 additions and 52 deletions

View File

@@ -37,6 +37,10 @@ from govoplan_assessment.evidence import ( # noqa: E402
review_installed_composition,
validate_payload,
)
from govoplan_assessment.installer_receipt import ( # noqa: E402
issue_installer_receipt,
)
from govoplan_release.artifact_identity import inspect_python_wheel # noqa: E402
from tests.test_capability_fit_review import signed_catalog # noqa: E402
@@ -89,6 +93,202 @@ class CapabilityFitEvidenceTests(unittest.TestCase):
report["proof_scope"]["installed_artifacts"]["evidence_sha256"],
)
def test_signed_catalog_artifact_identities_bind_installed_release_origin(
self,
) -> None:
evidence = matching_installed_evidence(self.assessment)
catalog, keyring = signed_catalog(
self.assessment,
release_artifacts=catalog_artifacts_for(evidence),
)
report = self.review(
catalog=catalog,
keyring=keyring,
installed_evidence=evidence,
)
origin = report["proof_scope"]["installed_release_origin"]
self.assertEqual("current", report["status"])
self.assertTrue(origin["checked"])
self.assertTrue(origin["valid"])
self.assertTrue(origin["release_origin_bound"])
self.assertEqual(len(evidence["artifacts"]), origin["anchored_artifact_digest_count"])
self.assertEqual(0, origin["signed_installer_receipt_count"])
def test_role_scoped_installer_receipt_admits_transformed_artifacts_and_boundary(
self,
) -> None:
evidence = matching_installed_evidence(self.assessment)
artifact_rows = catalog_artifacts_for(evidence, requires_receipt=True)
catalog, keyring = signed_catalog(
self.assessment,
release_artifacts=artifact_rows,
)
receipt, installer_keys = signed_installer_receipt(
assessment=self.assessment,
installed=evidence,
catalog=catalog,
catalog_artifacts=artifact_rows,
)
proof, proof_keys = signed_boundary_evidence(
assessment=self.assessment,
installed=evidence,
claims=[boundary_claim("target_environment", "passed")],
allowed_scopes=["target_environment"],
)
report = self.review(
catalog=catalog,
keyring=keyring,
installed_evidence=evidence,
installer_receipt=receipt,
installer_authority_keyring=installer_keys,
boundary_evidence=proof,
authority_keyring=proof_keys,
installed_evidence_mode="imported_unsigned",
)
origin = report["proof_scope"]["installed_release_origin"]
self.assertTrue(origin["valid"])
self.assertEqual(0, origin["anchored_artifact_digest_count"])
self.assertEqual(len(evidence["artifacts"]), origin["signed_installer_receipt_count"])
self.assertTrue(report["proof_scope"]["target_environment"]["checked"])
self.assertTrue(report["proof_scope"]["target_environment"]["valid"])
self.assertTrue(
report["proof_scope"]["installed_evidence_observation"][
"receipt_authenticated"
]
)
self.assertNotIn(
"installed_evidence_unsigned_import",
{item["code"] for item in report["findings"]},
)
def test_installer_receipt_cannot_reuse_release_key_or_cover_forged_payload(
self,
) -> None:
evidence = matching_installed_evidence(self.assessment)
artifact_rows = catalog_artifacts_for(evidence, requires_receipt=True)
catalog, keyring = signed_catalog(
self.assessment,
release_artifacts=artifact_rows,
)
receipt, installer_keys = signed_installer_receipt(
assessment=self.assessment,
installed=evidence,
catalog=catalog,
catalog_artifacts=artifact_rows,
)
forged = deepcopy(evidence)
forged["artifacts"][0]["record_integrity"]["installed_payload_identity"][
"sha256"
] = "f" * 64
reused = deepcopy(installer_keys)
reused["keys"][0]["public_key"] = keyring["keys"][0]["public_key"]
forged_report = self.review(
catalog=catalog,
keyring=keyring,
installed_evidence=forged,
installer_receipt=receipt,
installer_authority_keyring=installer_keys,
)
reused_report = self.review(
catalog=catalog,
keyring=keyring,
installed_evidence=evidence,
installer_receipt=receipt,
installer_authority_keyring=reused,
)
self.assertIn(
"installer_receipt_evidence_mismatch",
{item["code"] for item in forged_report["findings"]},
)
self.assertIn(
"installer_authority_untrusted",
{item["code"] for item in reused_report["findings"]},
)
self.assertFalse(
forged_report["proof_scope"]["installed_release_origin"]["valid"]
)
self.assertFalse(
reused_report["proof_scope"]["installed_release_origin"]["valid"]
)
def test_receipt_issuer_refuses_file_like_or_unverified_observations(self) -> None:
evidence = matching_installed_evidence(self.assessment)
artifacts = catalog_artifacts_for(evidence, requires_receipt=True)
catalog, _ = signed_catalog(
self.assessment,
release_artifacts=artifacts,
)
private_key = Ed25519PrivateKey.generate()
with self.assertRaisesRegex(ValueError, "same-process"):
issue_installer_receipt(
assessment=self.assessment,
catalog=catalog,
installed_evidence=evidence,
receipt_id="install:test",
key_id="installer:test",
private_key=private_key,
)
evidence["artifacts"][0]["record_integrity"]["status"] = "partial"
evidence["artifacts"][0]["record_integrity"]["unverifiable_file_count"] = 1
with self.assertRaisesRegex(ValueError, "not a verified match"):
issue_installer_receipt(
assessment=self.assessment,
catalog=catalog,
installed_evidence=evidence,
receipt_id="install:test",
key_id="installer:test",
private_key=private_key,
same_process_observation=True,
consumed_artifacts={
str(item["package_name"]): Path("unused.whl")
for item in evidence["artifacts"]
},
)
def test_installer_receipt_and_observation_must_be_current_together(self) -> None:
evidence = matching_installed_evidence(self.assessment)
artifact_rows = catalog_artifacts_for(evidence, requires_receipt=True)
catalog, keyring = signed_catalog(
self.assessment,
release_artifacts=artifact_rows,
)
receipt, installer_keys = signed_installer_receipt(
assessment=self.assessment,
installed=evidence,
catalog=catalog,
catalog_artifacts=artifact_rows,
)
report = self.review(
catalog=catalog,
keyring=keyring,
installed_evidence=evidence,
installer_receipt=receipt,
installer_authority_keyring=installer_keys,
installed_evidence_mode="imported_unsigned",
verification_time=datetime(2026, 7, 23, 12, 5, 1, tzinfo=UTC),
)
self.assertIn(
"installer_receipt_time_invalid",
{item["code"] for item in report["findings"]},
)
self.assertFalse(
report["proof_scope"]["installed_release_origin"]["valid"]
)
self.assertFalse(
report["proof_scope"]["installed_evidence_observation"][
"receipt_authenticated"
]
)
def test_catalog_trust_blocker_invalidates_dependent_evidence_scopes(
self,
) -> None:
@@ -523,6 +723,7 @@ class CapabilityFitEvidenceTests(unittest.TestCase):
text=True,
)
wheel = next(wheels.glob("govoplan_core-*.whl"))
built_identity = inspect_python_wheel(wheel)
subprocess.run(
(
str(build_python),
@@ -568,6 +769,15 @@ class CapabilityFitEvidenceTests(unittest.TestCase):
self.assertGreater(record["permitted_unhashed_file_count"], 0)
self.assertGreater(record["generated_unhashed_file_count"], 0)
self.assertEqual(0, record["unverifiable_file_count"])
self.assertEqual(
built_identity.payload_sha256,
record["artifact_payload_identity"]["sha256"],
)
self.assertEqual(
built_identity.payload_file_count,
record["artifact_payload_identity"]["file_count"],
)
self.assertTrue(built_identity.requires_installer_receipt)
self.assertEqual(
(), validate_payload(payload=evidence, schema=self.installed_schema)
)
@@ -1162,6 +1372,8 @@ class CapabilityFitEvidenceTests(unittest.TestCase):
catalog: dict[str, object],
keyring: dict[str, object],
installed_evidence: dict[str, object] | None = None,
installer_receipt: dict[str, object] | None = None,
installer_authority_keyring: dict[str, object] | None = None,
boundary_evidence: dict[str, object] | None = None,
authority_keyring: dict[str, object] | None = None,
installed_evidence_mode: str = "direct_local",
@@ -1179,6 +1391,16 @@ class CapabilityFitEvidenceTests(unittest.TestCase):
installed_evidence_schema=(installed_schema or self.installed_schema)
if installed_evidence is not None
else None,
installer_receipt=installer_receipt,
installer_receipt_schema=load_json("docs/installer-receipt.schema.json")
if installer_receipt is not None
else None,
installer_authority_keyring=installer_authority_keyring,
installer_authority_keyring_schema=load_json(
"docs/installer-receipt-authority-keyring.schema.json"
)
if installer_receipt is not None
else None,
boundary_evidence=boundary_evidence,
boundary_evidence_schema=self.boundary_schema
if boundary_evidence is not None
@@ -1196,7 +1418,7 @@ class CapabilityFitEvidenceTests(unittest.TestCase):
def matching_installed_evidence(assessment: dict[str, object]) -> dict[str, object]:
return {
"$schema": "./installed-composition-evidence.schema.json",
"schema_version": "0.3.0",
"schema_version": "0.4.0",
"evidence_kind": "govoplan.installed-composition",
"assessment_id": assessment["assessment_id"],
"assessment_release": assessment["release"]["ref"],
@@ -1238,6 +1460,20 @@ def artifact(
"unverifiable_file_count": 0,
"missing_file_count": 0,
"mismatched_file_count": 0,
"artifact_payload_identity": {
"algorithm": "govoplan-wheel-declared-payload-v1",
"sha256": hashlib.sha256(
f"artifact:{package_name}:{version}".encode()
).hexdigest(),
"file_count": 1,
},
"installed_payload_identity": {
"algorithm": "govoplan-installed-record-payload-v1",
"sha256": hashlib.sha256(
f"installed:{package_name}:{version}".encode()
).hexdigest(),
"file_count": 1,
},
},
}
@@ -1323,6 +1559,107 @@ def signed_boundary_evidence(
return proof, keyring
def catalog_artifacts_for(
installed: dict[str, object], *, requires_receipt: bool = False
) -> list[dict[str, object]]:
rows: list[dict[str, object]] = []
for item in installed["artifacts"]:
record = item["record_integrity"]
rows.append(
{
"artifact_kind": "python-wheel",
"package_name": item["package_name"],
"package_version": item["package_version"],
"archive_sha256": hashlib.sha256(
f"wheel:{item['package_name']}:{item['package_version']}".encode()
).hexdigest(),
"archive_size": 1024,
"installed_payload": deepcopy(record["artifact_payload_identity"]),
"requires_installer_receipt": requires_receipt,
}
)
return rows
def signed_installer_receipt(
*,
assessment: dict[str, object],
installed: dict[str, object],
catalog: dict[str, object],
catalog_artifacts: list[dict[str, object]],
) -> tuple[dict[str, object], dict[str, object]]:
private_key = Ed25519PrivateKey.generate()
public_key = base64.b64encode(
private_key.public_key().public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw,
)
).decode("ascii")
catalog_by_package = {
str(item["package_name"]): item for item in catalog_artifacts
}
receipt: dict[str, object] = {
"$schema": "./installer-receipt.schema.json",
"schema_version": "0.1.0",
"evidence_kind": "govoplan.installer-receipt",
"receipt_id": "install:test",
"assessment_id": assessment["assessment_id"],
"assessment_release": assessment["release"]["ref"],
"installed_evidence_sha256": canonical_sha256(installed),
"catalog": {
"channel": catalog["channel"],
"sequence": catalog["sequence"],
"sha256": hashlib.sha256(
json.dumps(
catalog,
sort_keys=True,
separators=(",", ":"),
ensure_ascii=True,
).encode("utf-8")
).hexdigest(),
},
"issued_at": "2026-07-23T11:59:00Z",
"artifacts": [
{
"package_name": item["package_name"],
"package_version": item["package_version"],
"catalog_archive_sha256": catalog_by_package[
str(item["package_name"])
]["archive_sha256"],
"installed_payload": deepcopy(
item["record_integrity"]["installed_payload_identity"]
),
}
for item in installed["artifacts"]
],
}
receipt["signatures"] = [
{
"algorithm": "ed25519",
"key_id": "installer:test",
"value": base64.b64encode(
private_key.sign(canonical_bytes(receipt))
).decode("ascii"),
}
]
keyring = {
"$schema": "./installer-receipt-authority-keyring.schema.json",
"schema_version": "0.1.0",
"purpose": "govoplan.installer-receipt-authorities",
"keys": [
{
"key_id": "installer:test",
"status": "active",
"public_key": public_key,
"allowed_scopes": ["installed_release_origin"],
"not_before": "2026-07-01T00:00:00Z",
"not_after": "2026-08-01T00:00:00Z",
}
],
}
return receipt, keyring
def create_distribution(
root: Path,
) -> tuple[metadata.PathDistribution, Path]: