Bind installed releases to signed artifact receipts
This commit is contained in:
@@ -25,6 +25,12 @@ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
|
||||
from jsonschema import Draft202012Validator, FormatChecker
|
||||
from jsonschema.exceptions import SchemaError
|
||||
|
||||
from govoplan_release.artifact_identity import (
|
||||
INSTALLED_PAYLOAD_ALGORITHM,
|
||||
WHEEL_PAYLOAD_ALGORITHM,
|
||||
payload_identity_sha256,
|
||||
)
|
||||
|
||||
|
||||
MAX_GOVOPLAN_DISTRIBUTIONS = 256
|
||||
MAX_DISTRIBUTION_FILES = 10_000
|
||||
@@ -243,7 +249,7 @@ def collect_installed_composition(
|
||||
sorted_issues.sort(key=lambda item: (item["package_name"], item["code"]))
|
||||
return {
|
||||
"$schema": "./installed-composition-evidence.schema.json",
|
||||
"schema_version": "0.3.0",
|
||||
"schema_version": "0.4.0",
|
||||
"evidence_kind": "govoplan.installed-composition",
|
||||
"assessment_id": _safe_opaque_id(
|
||||
assessment.get("assessment_id"), fallback="invalid-assessment"
|
||||
@@ -277,6 +283,15 @@ def review_installed_composition(
|
||||
verification_time: datetime | None = None,
|
||||
verification_mode: str = "current",
|
||||
catalog_trusted: bool = False,
|
||||
catalog_artifacts: object = None,
|
||||
catalog_sha256: str | None = None,
|
||||
catalog_channel: str | None = None,
|
||||
catalog_sequence: int | None = None,
|
||||
installer_receipt: dict[str, Any] | None = None,
|
||||
installer_receipt_schema: dict[str, Any] | None = None,
|
||||
installer_authority_keyring: dict[str, Any] | None = None,
|
||||
installer_authority_keyring_schema: dict[str, Any] | None = None,
|
||||
forbidden_installer_public_keys: frozenset[bytes] = frozenset(),
|
||||
) -> InstalledEvidenceReview:
|
||||
if evidence is None:
|
||||
return InstalledEvidenceReview(
|
||||
@@ -403,14 +418,6 @@ def review_installed_composition(
|
||||
observation_trusted = True
|
||||
elif effective_observation_mode == "imported_unsigned":
|
||||
freshness = "unsigned_import"
|
||||
findings.append(
|
||||
EvidenceFinding(
|
||||
"blocker",
|
||||
"installed_evidence_unsigned_import",
|
||||
"Imported unsigned installed evidence may be compared but cannot establish installed proof.",
|
||||
("assessment.installed_composition",),
|
||||
)
|
||||
)
|
||||
if effective_verification_mode == "invalid":
|
||||
observation_trusted = False
|
||||
if catalog_trusted is not True:
|
||||
@@ -528,7 +535,7 @@ def review_installed_composition(
|
||||
provenance_declared_match = 0
|
||||
provenance_mutable = 0
|
||||
provenance_unanchored = 0
|
||||
artifact_valid = binding_valid and not duplicate_packages
|
||||
payload_consistent = binding_valid and not duplicate_packages
|
||||
|
||||
for package_name, (component, catalog_entry) in sorted(expected_by_package.items()):
|
||||
module_id = str(component["module_id"])
|
||||
@@ -537,7 +544,7 @@ def review_installed_composition(
|
||||
record_expected += 1
|
||||
provenance_expected += 1
|
||||
if package_name in duplicate_packages:
|
||||
artifact_valid = False
|
||||
payload_consistent = False
|
||||
_record_change(
|
||||
findings=findings,
|
||||
changes=changes,
|
||||
@@ -550,7 +557,7 @@ def review_installed_composition(
|
||||
)
|
||||
continue
|
||||
if artifact is None:
|
||||
artifact_valid = False
|
||||
payload_consistent = False
|
||||
_record_change(
|
||||
findings=findings,
|
||||
changes=changes,
|
||||
@@ -567,7 +574,7 @@ def review_installed_composition(
|
||||
package_version = str(artifact.get("package_version") or "")
|
||||
catalog_version = str(catalog_entry.get("version") or "")
|
||||
if package_version != expected_version or package_version != catalog_version:
|
||||
artifact_valid = False
|
||||
payload_consistent = False
|
||||
_record_change(
|
||||
findings=findings,
|
||||
changes=changes,
|
||||
@@ -606,7 +613,7 @@ def review_installed_composition(
|
||||
and str(expected_manifest.get("manifest_version") or "") != expected_version
|
||||
)
|
||||
if manifest_mismatch:
|
||||
artifact_valid = False
|
||||
payload_consistent = False
|
||||
_record_change(
|
||||
findings=findings,
|
||||
changes=changes,
|
||||
@@ -619,7 +626,7 @@ def review_installed_composition(
|
||||
)
|
||||
unexpected_modules = sorted(set(module_rows) - {module_id})
|
||||
if duplicate_module_ids or unexpected_modules:
|
||||
artifact_valid = False
|
||||
payload_consistent = False
|
||||
_record_change(
|
||||
findings=findings,
|
||||
changes=changes,
|
||||
@@ -647,7 +654,7 @@ def review_installed_composition(
|
||||
if record_status == "verified" and record_semantics_valid:
|
||||
record_verified += 1
|
||||
else:
|
||||
artifact_valid = False
|
||||
payload_consistent = False
|
||||
_record_change(
|
||||
findings=findings,
|
||||
changes=changes,
|
||||
@@ -676,7 +683,6 @@ def review_installed_composition(
|
||||
str(provenance.get("basis") or "") if isinstance(provenance, dict) else ""
|
||||
)
|
||||
if provenance_basis != "local-pep610-metadata":
|
||||
artifact_valid = False
|
||||
provenance_unanchored += 1
|
||||
_record_change(
|
||||
findings=findings,
|
||||
@@ -703,7 +709,6 @@ def review_installed_composition(
|
||||
if commit_matches and selected_matches:
|
||||
provenance_declared_match += 1
|
||||
else:
|
||||
artifact_valid = False
|
||||
_record_change(
|
||||
findings=findings,
|
||||
changes=changes,
|
||||
@@ -716,7 +721,6 @@ def review_installed_composition(
|
||||
)
|
||||
elif provenance_kind in {"editable-vcs", "editable-local", "local-directory"}:
|
||||
provenance_mutable += 1
|
||||
artifact_valid = False
|
||||
_record_change(
|
||||
findings=findings,
|
||||
changes=changes,
|
||||
@@ -729,7 +733,6 @@ def review_installed_composition(
|
||||
)
|
||||
else:
|
||||
provenance_unanchored += 1
|
||||
artifact_valid = False
|
||||
_record_change(
|
||||
findings=findings,
|
||||
changes=changes,
|
||||
@@ -743,7 +746,7 @@ def review_installed_composition(
|
||||
|
||||
expected_packages = set(expected_by_package)
|
||||
for package_name in sorted(set(artifacts_by_package) - expected_packages):
|
||||
artifact_valid = False
|
||||
payload_consistent = False
|
||||
matching_module_id = next(
|
||||
(
|
||||
module_id
|
||||
@@ -788,7 +791,7 @@ def review_installed_composition(
|
||||
continue
|
||||
package_name = str(issue.get("package_name") or "")
|
||||
code = str(issue.get("code") or "")
|
||||
artifact_valid = False
|
||||
payload_consistent = False
|
||||
findings.append(
|
||||
EvidenceFinding(
|
||||
"review",
|
||||
@@ -801,18 +804,68 @@ def review_installed_composition(
|
||||
exact_set = (
|
||||
set(artifacts_by_package) == expected_packages and not duplicate_packages
|
||||
)
|
||||
installed_checked = observation_trusted
|
||||
origin_scope, origin_findings = _review_installed_release_origin(
|
||||
expected_by_package=expected_by_package,
|
||||
installed_artifacts=artifacts_by_package,
|
||||
installed_evidence=evidence,
|
||||
installed_evidence_sha256=digest,
|
||||
installed_composition_valid=(
|
||||
binding_valid
|
||||
and exact_set
|
||||
and payload_consistent
|
||||
and record_verified == record_expected
|
||||
and record_expected > 0
|
||||
),
|
||||
observation_trusted=observation_trusted,
|
||||
catalog_artifacts=catalog_artifacts,
|
||||
catalog_sha256=catalog_sha256,
|
||||
catalog_channel=catalog_channel,
|
||||
catalog_sequence=catalog_sequence,
|
||||
catalog_trusted=catalog_trusted,
|
||||
receipt=installer_receipt,
|
||||
receipt_schema=installer_receipt_schema,
|
||||
authority_keyring=installer_authority_keyring,
|
||||
authority_keyring_schema=installer_authority_keyring_schema,
|
||||
verification_time=now,
|
||||
forbidden_public_keys=forbidden_installer_public_keys,
|
||||
)
|
||||
findings.extend(origin_findings)
|
||||
receipt_authenticated = (
|
||||
origin_scope.get("valid") is True
|
||||
and origin_scope.get("signed_installer_receipt_count") == record_expected
|
||||
and record_expected > 0
|
||||
)
|
||||
effective_observation_trusted = observation_trusted or receipt_authenticated
|
||||
if (
|
||||
effective_observation_mode == "imported_unsigned"
|
||||
and not receipt_authenticated
|
||||
):
|
||||
findings.append(
|
||||
EvidenceFinding(
|
||||
"blocker",
|
||||
"installed_evidence_unsigned_import",
|
||||
"Imported installed evidence requires a valid independent installer receipt before it can establish installed proof.",
|
||||
("assessment.installed_composition",),
|
||||
)
|
||||
)
|
||||
installed_checked = effective_observation_trusted
|
||||
record_checked = (
|
||||
observation_trusted and binding_valid and exact_set and record_expected > 0
|
||||
effective_observation_trusted
|
||||
and binding_valid
|
||||
and exact_set
|
||||
and record_expected > 0
|
||||
)
|
||||
provenance_checked = (
|
||||
observation_trusted and binding_valid and exact_set and provenance_expected > 0
|
||||
effective_observation_trusted
|
||||
and binding_valid
|
||||
and exact_set
|
||||
and provenance_expected > 0
|
||||
)
|
||||
proof_scope = {
|
||||
"installed_artifacts": {
|
||||
"checked": installed_checked,
|
||||
"valid": artifact_valid if installed_checked else None,
|
||||
"comparison_valid": artifact_valid,
|
||||
"valid": payload_consistent if installed_checked else None,
|
||||
"comparison_valid": payload_consistent,
|
||||
"evidence_sha256": digest,
|
||||
"expected_distribution_count": len(expected_packages),
|
||||
"observed_distribution_count": len(artifact_rows),
|
||||
@@ -836,16 +889,7 @@ def review_installed_composition(
|
||||
"expected_distribution_count": provenance_expected,
|
||||
"release_origin_bound": False,
|
||||
},
|
||||
"installed_release_origin": {
|
||||
"checked": False,
|
||||
"valid": None,
|
||||
"release_origin_bound": False,
|
||||
"anchored_artifact_digest_count": 0,
|
||||
"expected_distribution_count": provenance_expected,
|
||||
"required_evidence": (
|
||||
"An independently anchored artifact digest or signed installer receipt that binds each installed payload to the signed release catalog.",
|
||||
),
|
||||
},
|
||||
"installed_release_origin": origin_scope,
|
||||
"runtime_activation": {
|
||||
"checked": False,
|
||||
"valid": None,
|
||||
@@ -861,6 +905,7 @@ def review_installed_composition(
|
||||
"evaluated_at": _iso_datetime(now),
|
||||
"verification_mode": effective_verification_mode,
|
||||
"catalog_trusted": catalog_trusted is True,
|
||||
"receipt_authenticated": receipt_authenticated,
|
||||
"freshness_window_seconds": int(MAX_LOCAL_OBSERVATION_AGE.total_seconds()),
|
||||
},
|
||||
}
|
||||
@@ -874,6 +919,489 @@ def review_installed_composition(
|
||||
)
|
||||
|
||||
|
||||
def _review_installed_release_origin(
|
||||
*,
|
||||
expected_by_package: Mapping[str, tuple[dict[str, Any], Mapping[str, Any]]],
|
||||
installed_artifacts: Mapping[str, dict[str, Any]],
|
||||
installed_evidence: dict[str, Any],
|
||||
installed_evidence_sha256: str,
|
||||
installed_composition_valid: bool,
|
||||
observation_trusted: bool,
|
||||
catalog_artifacts: object,
|
||||
catalog_sha256: str | None,
|
||||
catalog_channel: str | None,
|
||||
catalog_sequence: int | None,
|
||||
catalog_trusted: bool,
|
||||
receipt: dict[str, Any] | None,
|
||||
receipt_schema: dict[str, Any] | None,
|
||||
authority_keyring: dict[str, Any] | None,
|
||||
authority_keyring_schema: dict[str, Any] | None,
|
||||
verification_time: datetime,
|
||||
forbidden_public_keys: frozenset[bytes],
|
||||
) -> tuple[dict[str, Any], tuple[EvidenceFinding, ...]]:
|
||||
findings: list[EvidenceFinding] = []
|
||||
expected_count = len(expected_by_package)
|
||||
scope: dict[str, Any] = {
|
||||
"checked": False,
|
||||
"valid": None,
|
||||
"release_origin_bound": False,
|
||||
"anchored_artifact_digest_count": 0,
|
||||
"signed_installer_receipt_count": 0,
|
||||
"expected_distribution_count": expected_count,
|
||||
"catalog_sha256": catalog_sha256,
|
||||
"installer_receipt_sha256": None,
|
||||
"required_evidence": (
|
||||
"A signed catalog identity computed from each built immutable artifact, or a role-scoped installer receipt signed by an independently provisioned authority.",
|
||||
),
|
||||
}
|
||||
manifest_supplied = catalog_artifacts is not None
|
||||
receipt_supplied = receipt is not None
|
||||
if not manifest_supplied and not receipt_supplied:
|
||||
return scope, ()
|
||||
scope["checked"] = True
|
||||
scope["valid"] = False
|
||||
if not installed_composition_valid or catalog_trusted is not True:
|
||||
findings.append(
|
||||
EvidenceFinding(
|
||||
"blocker",
|
||||
"installed_origin_dependency_untrusted",
|
||||
"Installed release-origin proof requires a trusted catalog and valid fresh installed-composition evidence.",
|
||||
("assessment.release", "assessment.installed_composition"),
|
||||
)
|
||||
)
|
||||
return scope, tuple(findings)
|
||||
|
||||
catalog_by_package, catalog_findings = _catalog_artifact_identities(
|
||||
catalog_artifacts
|
||||
)
|
||||
findings.extend(catalog_findings)
|
||||
if catalog_findings:
|
||||
return scope, tuple(findings)
|
||||
|
||||
direct_bound: set[str] = set()
|
||||
receipt_required: set[str] = set()
|
||||
for package_name, (component, catalog_entry) in sorted(expected_by_package.items()):
|
||||
expected_version = str(catalog_entry.get("version") or "")
|
||||
identity = catalog_by_package.get(package_name)
|
||||
if identity is None or identity.get("package_version") != expected_version:
|
||||
findings.append(
|
||||
EvidenceFinding(
|
||||
"review",
|
||||
"installed_origin_catalog_artifact_missing",
|
||||
f"The signed catalog has no built-artifact identity for {package_name!r} at the selected version.",
|
||||
(f"composition.{component['module_id']}",),
|
||||
)
|
||||
)
|
||||
continue
|
||||
if identity.get("requires_installer_receipt") is True:
|
||||
receipt_required.add(package_name)
|
||||
continue
|
||||
if not observation_trusted:
|
||||
continue
|
||||
installed = installed_artifacts.get(package_name)
|
||||
record = installed.get("record_integrity") if isinstance(installed, dict) else None
|
||||
observed = (
|
||||
record.get("artifact_payload_identity") if isinstance(record, dict) else None
|
||||
)
|
||||
expected = identity.get("installed_payload")
|
||||
if observed == expected:
|
||||
direct_bound.add(package_name)
|
||||
else:
|
||||
findings.append(
|
||||
EvidenceFinding(
|
||||
"blocker",
|
||||
"installed_origin_artifact_payload_mismatch",
|
||||
f"Installed payload for {package_name!r} does not match the identity independently computed from the signed catalog artifact.",
|
||||
(f"composition.{component['module_id']}",),
|
||||
)
|
||||
)
|
||||
|
||||
receipt_bound: set[str] = set()
|
||||
if receipt_supplied:
|
||||
receipt_bound, receipt_findings, receipt_digest = _verify_installer_receipt(
|
||||
expected_by_package=expected_by_package,
|
||||
installed_artifacts=installed_artifacts,
|
||||
installed_evidence=installed_evidence,
|
||||
installed_evidence_sha256=installed_evidence_sha256,
|
||||
catalog_by_package=catalog_by_package,
|
||||
catalog_sha256=catalog_sha256,
|
||||
catalog_channel=catalog_channel,
|
||||
catalog_sequence=catalog_sequence,
|
||||
receipt=receipt,
|
||||
receipt_schema=receipt_schema,
|
||||
authority_keyring=authority_keyring,
|
||||
authority_keyring_schema=authority_keyring_schema,
|
||||
verification_time=verification_time,
|
||||
forbidden_public_keys=forbidden_public_keys,
|
||||
)
|
||||
findings.extend(receipt_findings)
|
||||
scope["installer_receipt_sha256"] = receipt_digest
|
||||
elif receipt_required:
|
||||
findings.append(
|
||||
EvidenceFinding(
|
||||
"review",
|
||||
"installed_origin_receipt_required",
|
||||
"At least one selected wheel produces installer-transformed files, so its signed installer receipt is required for complete origin proof.",
|
||||
("assessment.installed_composition",),
|
||||
)
|
||||
)
|
||||
|
||||
bound = direct_bound | receipt_bound
|
||||
scope["anchored_artifact_digest_count"] = len(direct_bound)
|
||||
scope["signed_installer_receipt_count"] = len(receipt_bound)
|
||||
valid = len(bound) == expected_count and not any(
|
||||
finding.severity == "blocker" for finding in findings
|
||||
)
|
||||
scope["valid"] = valid
|
||||
scope["release_origin_bound"] = valid
|
||||
return scope, tuple(findings)
|
||||
|
||||
|
||||
def _catalog_artifact_identities(
|
||||
value: object,
|
||||
) -> tuple[dict[str, dict[str, Any]], tuple[EvidenceFinding, ...]]:
|
||||
if value is None:
|
||||
return {}, ()
|
||||
if not isinstance(value, list) or len(value) > MAX_GOVOPLAN_DISTRIBUTIONS:
|
||||
return {}, (
|
||||
EvidenceFinding(
|
||||
"blocker",
|
||||
"catalog_artifact_manifest_invalid",
|
||||
"Signed catalog release artifacts must be a bounded list.",
|
||||
("assessment.release",),
|
||||
),
|
||||
)
|
||||
result: dict[str, dict[str, Any]] = {}
|
||||
invalid = False
|
||||
for item in value:
|
||||
if not isinstance(item, dict) or set(item) != {
|
||||
"artifact_kind",
|
||||
"package_name",
|
||||
"package_version",
|
||||
"archive_sha256",
|
||||
"archive_size",
|
||||
"installed_payload",
|
||||
"requires_installer_receipt",
|
||||
}:
|
||||
invalid = True
|
||||
continue
|
||||
package_name = item.get("package_name")
|
||||
version = item.get("package_version")
|
||||
archive_size = item.get("archive_size")
|
||||
payload = item.get("installed_payload")
|
||||
if (
|
||||
item.get("artifact_kind") != "python-wheel"
|
||||
or not isinstance(package_name, str)
|
||||
or PACKAGE_PATTERN.fullmatch(package_name) is None
|
||||
or not isinstance(version, str)
|
||||
or VERSION_PATTERN.fullmatch(version) is None
|
||||
or not isinstance(item.get("archive_sha256"), str)
|
||||
or SHA256_PATTERN.fullmatch(str(item["archive_sha256"])) is None
|
||||
or not isinstance(archive_size, int)
|
||||
or isinstance(archive_size, bool)
|
||||
or not 0 < archive_size <= MAX_HASHED_DISTRIBUTION_BYTES
|
||||
or not isinstance(item.get("requires_installer_receipt"), bool)
|
||||
or not _payload_identity_semantics_valid(
|
||||
payload,
|
||||
algorithm=WHEEL_PAYLOAD_ALGORITHM,
|
||||
maximum_files=MAX_DISTRIBUTION_FILES,
|
||||
)
|
||||
or package_name in result
|
||||
):
|
||||
invalid = True
|
||||
continue
|
||||
result[package_name] = item
|
||||
if invalid:
|
||||
return {}, (
|
||||
EvidenceFinding(
|
||||
"blocker",
|
||||
"catalog_artifact_manifest_invalid",
|
||||
"Signed catalog release artifacts contain malformed or duplicate package identities.",
|
||||
("assessment.release",),
|
||||
),
|
||||
)
|
||||
return result, ()
|
||||
|
||||
|
||||
def _verify_installer_receipt(
|
||||
*,
|
||||
expected_by_package: Mapping[str, tuple[dict[str, Any], Mapping[str, Any]]],
|
||||
installed_artifacts: Mapping[str, dict[str, Any]],
|
||||
installed_evidence: dict[str, Any],
|
||||
installed_evidence_sha256: str,
|
||||
catalog_by_package: Mapping[str, dict[str, Any]],
|
||||
catalog_sha256: str | None,
|
||||
catalog_channel: str | None,
|
||||
catalog_sequence: int | None,
|
||||
receipt: dict[str, Any],
|
||||
receipt_schema: dict[str, Any] | None,
|
||||
authority_keyring: dict[str, Any] | None,
|
||||
authority_keyring_schema: dict[str, Any] | None,
|
||||
verification_time: datetime,
|
||||
forbidden_public_keys: frozenset[bytes],
|
||||
) -> tuple[set[str], tuple[EvidenceFinding, ...], str]:
|
||||
receipt_digest = canonical_sha256(receipt)
|
||||
findings: list[EvidenceFinding] = []
|
||||
if receipt_schema is None or authority_keyring_schema is None:
|
||||
findings.append(
|
||||
EvidenceFinding(
|
||||
"blocker",
|
||||
"installer_receipt_schema_missing",
|
||||
"Installer receipt proof requires both bounded validation schemas.",
|
||||
("assessment.installed_composition",),
|
||||
)
|
||||
)
|
||||
return set(), tuple(findings), receipt_digest
|
||||
receipt_errors = validate_payload(payload=receipt, schema=receipt_schema)
|
||||
keyring_errors = (
|
||||
validate_payload(payload=authority_keyring, schema=authority_keyring_schema)
|
||||
if isinstance(authority_keyring, dict)
|
||||
else ("$: an independently provisioned installer authority keyring is required",)
|
||||
)
|
||||
findings.extend(
|
||||
EvidenceFinding(
|
||||
"blocker",
|
||||
"installer_receipt_schema",
|
||||
message,
|
||||
("assessment.installed_composition",),
|
||||
)
|
||||
for message in receipt_errors
|
||||
)
|
||||
findings.extend(
|
||||
EvidenceFinding(
|
||||
"blocker",
|
||||
"installer_authority_keyring_schema",
|
||||
message,
|
||||
("assessment.installed_composition",),
|
||||
)
|
||||
for message in keyring_errors
|
||||
)
|
||||
if receipt_errors or keyring_errors or not isinstance(authority_keyring, dict):
|
||||
return set(), tuple(findings), receipt_digest
|
||||
|
||||
expected_release = installed_evidence.get("assessment_release")
|
||||
expected_assessment = installed_evidence.get("assessment_id")
|
||||
catalog_binding = receipt.get("catalog")
|
||||
binding_valid = True
|
||||
if (
|
||||
receipt.get("assessment_id") != expected_assessment
|
||||
or receipt.get("assessment_release") != expected_release
|
||||
or receipt.get("installed_evidence_sha256") != installed_evidence_sha256
|
||||
):
|
||||
binding_valid = False
|
||||
findings.append(
|
||||
EvidenceFinding(
|
||||
"blocker",
|
||||
"installer_receipt_evidence_mismatch",
|
||||
"Installer receipt is not bound to the supplied assessment and installed-evidence digest.",
|
||||
("assessment.installed_composition",),
|
||||
)
|
||||
)
|
||||
if not isinstance(catalog_binding, dict) or (
|
||||
catalog_binding.get("sha256") != catalog_sha256
|
||||
or catalog_binding.get("channel") != catalog_channel
|
||||
or catalog_binding.get("sequence") != catalog_sequence
|
||||
):
|
||||
binding_valid = False
|
||||
findings.append(
|
||||
EvidenceFinding(
|
||||
"blocker",
|
||||
"installer_receipt_catalog_mismatch",
|
||||
"Installer receipt is not bound to the supplied signed catalog identity.",
|
||||
("assessment.release", "assessment.installed_composition"),
|
||||
)
|
||||
)
|
||||
|
||||
issued_at = _datetime(receipt.get("issued_at"))
|
||||
collected_at = _datetime(installed_evidence.get("collected_at"))
|
||||
if (
|
||||
issued_at is None
|
||||
or collected_at is None
|
||||
or issued_at < collected_at
|
||||
or issued_at - collected_at > MAX_LOCAL_OBSERVATION_AGE
|
||||
or issued_at > verification_time + MAX_LOCAL_OBSERVATION_FUTURE_SKEW
|
||||
or verification_time - issued_at > MAX_LOCAL_OBSERVATION_AGE
|
||||
):
|
||||
binding_valid = False
|
||||
findings.append(
|
||||
EvidenceFinding(
|
||||
"blocker",
|
||||
"installer_receipt_time_invalid",
|
||||
"Installer receipt issuance must follow the observation within five minutes and remain fresh at the selected verification time.",
|
||||
("assessment.installed_composition",),
|
||||
)
|
||||
)
|
||||
|
||||
receipt_rows = [
|
||||
item for item in receipt.get("artifacts", []) if isinstance(item, dict)
|
||||
]
|
||||
rows_by_package: dict[str, dict[str, Any]] = {}
|
||||
duplicate_packages: set[str] = set()
|
||||
for row in receipt_rows:
|
||||
package_name = str(row.get("package_name") or "")
|
||||
if package_name in rows_by_package:
|
||||
duplicate_packages.add(package_name)
|
||||
else:
|
||||
rows_by_package[package_name] = row
|
||||
expected_packages = set(expected_by_package)
|
||||
if duplicate_packages or set(rows_by_package) != expected_packages:
|
||||
binding_valid = False
|
||||
findings.append(
|
||||
EvidenceFinding(
|
||||
"blocker",
|
||||
"installer_receipt_artifact_set_mismatch",
|
||||
"Installer receipt must bind exactly one row for every expected installed distribution.",
|
||||
("assessment.installed_composition",),
|
||||
)
|
||||
)
|
||||
bound: set[str] = set()
|
||||
for package_name, (_, catalog_entry) in sorted(expected_by_package.items()):
|
||||
row = rows_by_package.get(package_name)
|
||||
installed = installed_artifacts.get(package_name)
|
||||
record = installed.get("record_integrity") if isinstance(installed, dict) else None
|
||||
observed_payload = (
|
||||
record.get("installed_payload_identity") if isinstance(record, dict) else None
|
||||
)
|
||||
catalog_identity = catalog_by_package.get(package_name)
|
||||
if (
|
||||
row is None
|
||||
or catalog_identity is None
|
||||
or row.get("package_version") != catalog_entry.get("version")
|
||||
or row.get("catalog_archive_sha256")
|
||||
!= catalog_identity.get("archive_sha256")
|
||||
or row.get("installed_payload") != observed_payload
|
||||
):
|
||||
binding_valid = False
|
||||
findings.append(
|
||||
EvidenceFinding(
|
||||
"blocker",
|
||||
"installer_receipt_artifact_mismatch",
|
||||
f"Installer receipt does not bind the observed {package_name!r} payload to its signed catalog artifact.",
|
||||
("assessment.installed_composition",),
|
||||
)
|
||||
)
|
||||
else:
|
||||
bound.add(package_name)
|
||||
|
||||
keys, key_findings = _installer_receipt_keys(
|
||||
authority_keyring=authority_keyring,
|
||||
issued_at=issued_at,
|
||||
forbidden_public_keys=forbidden_public_keys,
|
||||
)
|
||||
findings.extend(key_findings)
|
||||
signature_payload = dict(receipt)
|
||||
signature_payload.pop("signatures", None)
|
||||
signature_bytes = canonical_bytes(signature_payload)
|
||||
signature_valid = False
|
||||
seen_ids: set[str] = set()
|
||||
duplicate_ids = False
|
||||
for signature in receipt.get("signatures", []):
|
||||
if not isinstance(signature, dict):
|
||||
continue
|
||||
key_id = str(signature.get("key_id") or "")
|
||||
if key_id in seen_ids:
|
||||
duplicate_ids = True
|
||||
continue
|
||||
seen_ids.add(key_id)
|
||||
public_key = keys.get(key_id)
|
||||
if public_key is None or signature.get("algorithm") != "ed25519":
|
||||
continue
|
||||
try:
|
||||
encoded = base64.b64decode(str(signature.get("value") or ""), validate=True)
|
||||
Ed25519PublicKey.from_public_bytes(public_key).verify(
|
||||
encoded, signature_bytes
|
||||
)
|
||||
signature_valid = True
|
||||
except (ValueError, InvalidSignature):
|
||||
continue
|
||||
if duplicate_ids or not signature_valid:
|
||||
findings.append(
|
||||
EvidenceFinding(
|
||||
"blocker",
|
||||
"installer_receipt_signature_untrusted",
|
||||
"Installer receipt has no unique valid signature from an authorized independent installer key.",
|
||||
("assessment.installed_composition",),
|
||||
)
|
||||
)
|
||||
if not binding_valid or not signature_valid or key_findings:
|
||||
return set(), tuple(findings), receipt_digest
|
||||
return bound, tuple(findings), receipt_digest
|
||||
|
||||
|
||||
def _installer_receipt_keys(
|
||||
*,
|
||||
authority_keyring: dict[str, Any],
|
||||
issued_at: datetime | None,
|
||||
forbidden_public_keys: frozenset[bytes],
|
||||
) -> tuple[dict[str, bytes], tuple[EvidenceFinding, ...]]:
|
||||
findings: list[EvidenceFinding] = []
|
||||
result: dict[str, bytes] = {}
|
||||
raw_keys = authority_keyring.get("keys")
|
||||
if not isinstance(raw_keys, list) or issued_at is None:
|
||||
return {}, (
|
||||
EvidenceFinding(
|
||||
"blocker",
|
||||
"installer_authority_untrusted",
|
||||
"Installer receipt authority keyring or issuance time is invalid.",
|
||||
("assessment.installed_composition",),
|
||||
),
|
||||
)
|
||||
seen_ids: set[str] = set()
|
||||
seen_material: set[bytes] = set()
|
||||
malformed = False
|
||||
for item in raw_keys:
|
||||
if not isinstance(item, dict):
|
||||
malformed = True
|
||||
continue
|
||||
key_id = str(item.get("key_id") or "")
|
||||
try:
|
||||
public_key = base64.b64decode(str(item.get("public_key") or ""), validate=True)
|
||||
Ed25519PublicKey.from_public_bytes(public_key)
|
||||
except ValueError:
|
||||
malformed = True
|
||||
continue
|
||||
not_before = _datetime(item.get("not_before"))
|
||||
not_after = _datetime(item.get("not_after"))
|
||||
interval_valid = not (
|
||||
not_before is not None
|
||||
and not_after is not None
|
||||
and not_before >= not_after
|
||||
)
|
||||
if (
|
||||
OPAQUE_ID_PATTERN.fullmatch(key_id) is None
|
||||
or key_id in seen_ids
|
||||
or public_key in seen_material
|
||||
or public_key in forbidden_public_keys
|
||||
or item.get("allowed_scopes") != ["installed_release_origin"]
|
||||
or not interval_valid
|
||||
):
|
||||
malformed = True
|
||||
continue
|
||||
seen_ids.add(key_id)
|
||||
seen_material.add(public_key)
|
||||
status = item.get("status")
|
||||
active_at_issue = (
|
||||
(not_before is None or issued_at >= not_before)
|
||||
and (not_after is None or issued_at < not_after)
|
||||
)
|
||||
historically_retired = status == "retired" and not_after is not None
|
||||
if active_at_issue and (
|
||||
status in {"active", "next"} or historically_retired
|
||||
):
|
||||
result[key_id] = public_key
|
||||
if malformed or not result:
|
||||
findings.append(
|
||||
EvidenceFinding(
|
||||
"blocker",
|
||||
"installer_authority_untrusted",
|
||||
"Installer authority keyring is malformed, reuses another trust-domain key, or has no role-scoped key valid at receipt issuance.",
|
||||
("assessment.installed_composition",),
|
||||
)
|
||||
)
|
||||
return {}, tuple(findings)
|
||||
return result, ()
|
||||
|
||||
|
||||
def review_boundary_evidence(
|
||||
*,
|
||||
assessment: dict[str, Any],
|
||||
@@ -1606,13 +2134,17 @@ def _record_integrity(
|
||||
"missing_file_count": 0,
|
||||
"mismatched_file_count": 0,
|
||||
}
|
||||
identities = {
|
||||
"artifact_payload_identity": None,
|
||||
"installed_payload_identity": None,
|
||||
}
|
||||
try:
|
||||
distribution_root = Path(distribution.locate_file("")).resolve()
|
||||
installation_root = Path(sys.prefix).resolve()
|
||||
except Exception:
|
||||
return {"status": "unavailable", **counts}
|
||||
return {"status": "unavailable", **counts, **identities}
|
||||
if not _trusted_distribution_root(distribution_root):
|
||||
return {"status": "unavailable", **counts}
|
||||
return {"status": "unavailable", **counts, **identities}
|
||||
entries, malformed_rows, record_status = _read_record_entries(
|
||||
distribution=distribution,
|
||||
distribution_root=distribution_root,
|
||||
@@ -1620,13 +2152,13 @@ def _record_integrity(
|
||||
)
|
||||
if record_status == "limit-exceeded":
|
||||
budget.remaining_files = 0
|
||||
return {"status": "limit-exceeded", **counts}
|
||||
return {"status": "limit-exceeded", **counts, **identities}
|
||||
if record_status != "ok":
|
||||
counts["unverifiable_file_count"] = malformed_rows
|
||||
return {"status": "unavailable", **counts}
|
||||
return {"status": "unavailable", **counts, **identities}
|
||||
record_file_count = len(entries) + malformed_rows
|
||||
if record_file_count == 0:
|
||||
return {"status": "unavailable", **counts}
|
||||
return {"status": "unavailable", **counts, **identities}
|
||||
budget.remaining_files -= record_file_count
|
||||
counts["unverifiable_file_count"] = malformed_rows
|
||||
declared_scripts = _declared_distribution_scripts(distribution)
|
||||
@@ -1666,6 +2198,8 @@ def _record_integrity(
|
||||
}
|
||||
total_hashed_bytes = 0
|
||||
limit_exceeded = False
|
||||
artifact_payload_rows: list[dict[str, object]] = []
|
||||
installed_payload_rows: list[dict[str, object]] = []
|
||||
for entry, resolved_path in resolved_entries:
|
||||
if resolved_path is None:
|
||||
counts["unverifiable_file_count"] += 1
|
||||
@@ -1731,6 +2265,31 @@ def _record_integrity(
|
||||
continue
|
||||
if hmac.compare_digest(actual, expected):
|
||||
counts["hashed_file_count"] += 1
|
||||
installed_path = _installed_payload_path(
|
||||
resolved_path,
|
||||
distribution_root=distribution_root,
|
||||
installation_root=installation_root,
|
||||
declared_scripts=declared_scripts,
|
||||
)
|
||||
if installed_path is None:
|
||||
counts["unverifiable_file_count"] += 1
|
||||
continue
|
||||
row = {
|
||||
"path": installed_path,
|
||||
"sha256": actual.hex(),
|
||||
"size": file_stat.st_size,
|
||||
}
|
||||
installed_payload_rows.append(row)
|
||||
artifact_path = _artifact_payload_path(
|
||||
resolved_path,
|
||||
distribution_root=distribution_root,
|
||||
installation_root=installation_root,
|
||||
declared_scripts=declared_scripts,
|
||||
)
|
||||
if artifact_path is not None and not _installer_generated_metadata(
|
||||
resolved_path, distribution_root=distribution_root
|
||||
):
|
||||
artifact_payload_rows.append({**row, "path": artifact_path})
|
||||
else:
|
||||
counts["mismatched_file_count"] += 1
|
||||
if limit_exceeded:
|
||||
@@ -1743,7 +2302,85 @@ def _record_integrity(
|
||||
status = "partial"
|
||||
else:
|
||||
status = "verified"
|
||||
return {"status": status, **counts}
|
||||
if status == "verified":
|
||||
installed_identity = _payload_identity(
|
||||
algorithm=INSTALLED_PAYLOAD_ALGORITHM,
|
||||
rows=installed_payload_rows,
|
||||
)
|
||||
artifact_identity = _payload_identity(
|
||||
algorithm=WHEEL_PAYLOAD_ALGORITHM,
|
||||
rows=artifact_payload_rows,
|
||||
)
|
||||
if installed_identity is None or artifact_identity is None:
|
||||
status = "partial"
|
||||
counts["unverifiable_file_count"] += 1
|
||||
else:
|
||||
identities["installed_payload_identity"] = installed_identity
|
||||
identities["artifact_payload_identity"] = artifact_identity
|
||||
return {"status": status, **counts, **identities}
|
||||
|
||||
|
||||
def _payload_identity(
|
||||
*, algorithm: str, rows: list[dict[str, object]]
|
||||
) -> dict[str, object] | None:
|
||||
ordered = sorted(rows, key=lambda item: str(item["path"]))
|
||||
paths = [str(item["path"]) for item in ordered]
|
||||
if not ordered or len(set(paths)) != len(paths):
|
||||
return None
|
||||
return {
|
||||
"algorithm": algorithm,
|
||||
"sha256": payload_identity_sha256(algorithm=algorithm, rows=ordered),
|
||||
"file_count": len(ordered),
|
||||
}
|
||||
|
||||
|
||||
def _installed_payload_path(
|
||||
path: Path,
|
||||
*,
|
||||
distribution_root: Path,
|
||||
installation_root: Path,
|
||||
declared_scripts: frozenset[str],
|
||||
) -> str | None:
|
||||
if _is_relative_to(path, distribution_root):
|
||||
return path.relative_to(distribution_root).as_posix()
|
||||
for script_root in (
|
||||
(installation_root / "bin").resolve(),
|
||||
(installation_root / "Scripts").resolve(),
|
||||
):
|
||||
if path.parent == script_root and path.name in declared_scripts:
|
||||
return f"@scripts/{path.name}"
|
||||
if _is_relative_to(path, installation_root):
|
||||
return f"@data/{path.relative_to(installation_root).as_posix()}"
|
||||
return None
|
||||
|
||||
|
||||
def _artifact_payload_path(
|
||||
path: Path,
|
||||
*,
|
||||
distribution_root: Path,
|
||||
installation_root: Path,
|
||||
declared_scripts: frozenset[str],
|
||||
) -> str | None:
|
||||
installed = _installed_payload_path(
|
||||
path,
|
||||
distribution_root=distribution_root,
|
||||
installation_root=installation_root,
|
||||
declared_scripts=declared_scripts,
|
||||
)
|
||||
return None if installed is None or installed.startswith("@scripts/") else installed
|
||||
|
||||
|
||||
def _installer_generated_metadata(
|
||||
path: Path, *, distribution_root: Path
|
||||
) -> bool:
|
||||
if not _is_relative_to(path, distribution_root):
|
||||
return False
|
||||
relative = path.relative_to(distribution_root)
|
||||
return (
|
||||
len(relative.parts) >= 2
|
||||
and relative.parent.name.endswith(".dist-info")
|
||||
and relative.name in {"direct_url.json", "INSTALLER", "REQUESTED"}
|
||||
)
|
||||
|
||||
|
||||
def _resolve_record_path(
|
||||
@@ -1867,12 +2504,25 @@ def _record_integrity_semantics_valid(record: Mapping[str, Any]) -> bool:
|
||||
missing = counts["missing_file_count"]
|
||||
mismatched = counts["mismatched_file_count"]
|
||||
if status == "verified":
|
||||
artifact_identity = record.get("artifact_payload_identity")
|
||||
installed_identity = record.get("installed_payload_identity")
|
||||
return (
|
||||
hashed > 0
|
||||
and counts["permitted_unhashed_file_count"] > 0
|
||||
and unverifiable == 0
|
||||
and missing == 0
|
||||
and mismatched == 0
|
||||
and _payload_identity_semantics_valid(
|
||||
artifact_identity,
|
||||
algorithm=WHEEL_PAYLOAD_ALGORITHM,
|
||||
maximum_files=hashed,
|
||||
)
|
||||
and _payload_identity_semantics_valid(
|
||||
installed_identity,
|
||||
algorithm=INSTALLED_PAYLOAD_ALGORITHM,
|
||||
maximum_files=hashed,
|
||||
exact_files=hashed,
|
||||
)
|
||||
)
|
||||
if status == "partial":
|
||||
return hashed > 0 and unverifiable > 0 and missing == 0 and mismatched == 0
|
||||
@@ -1885,6 +2535,31 @@ def _record_integrity_semantics_valid(record: Mapping[str, Any]) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _payload_identity_semantics_valid(
|
||||
value: object,
|
||||
*,
|
||||
algorithm: str,
|
||||
maximum_files: int,
|
||||
exact_files: int | None = None,
|
||||
) -> bool:
|
||||
if not isinstance(value, Mapping) or set(value) != {
|
||||
"algorithm",
|
||||
"sha256",
|
||||
"file_count",
|
||||
}:
|
||||
return False
|
||||
file_count = value.get("file_count")
|
||||
return (
|
||||
value.get("algorithm") == algorithm
|
||||
and isinstance(value.get("sha256"), str)
|
||||
and SHA256_PATTERN.fullmatch(str(value["sha256"])) is not None
|
||||
and isinstance(file_count, int)
|
||||
and not isinstance(file_count, bool)
|
||||
and 0 < file_count <= maximum_files
|
||||
and (exact_files is None or file_count == exact_files)
|
||||
)
|
||||
|
||||
|
||||
def _permitted_unhashed_record_path(
|
||||
path: Path,
|
||||
*,
|
||||
|
||||
Reference in New Issue
Block a user