Files
govoplan/tools/assessments/govoplan_assessment/evidence.py

1323 lines
49 KiB
Python

"""Bounded installed-composition and externally authorized fit evidence."""
from __future__ import annotations
import base64
from dataclasses import dataclass
from datetime import UTC, datetime
import hashlib
import hmac
from importlib import metadata
import json
from pathlib import Path
import re
import sys
from typing import Any, Iterable, Mapping, Sequence
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
from jsonschema import Draft202012Validator, FormatChecker
from jsonschema.exceptions import SchemaError
MAX_GOVOPLAN_DISTRIBUTIONS = 256
MAX_DISTRIBUTION_FILES = 10_000
MAX_COLLECTION_RECORD_FILES = 50_000
MAX_HASHED_FILE_BYTES = 64 * 1024 * 1024
MAX_HASHED_DISTRIBUTION_BYTES = 512 * 1024 * 1024
MAX_HASHED_COLLECTION_BYTES = 2 * 1024 * 1024 * 1024
MAX_DIRECT_URL_BYTES = 64 * 1024
PACKAGE_PATTERN = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
OPAQUE_ID_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]*$")
VERSION_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._+!-]*$")
GIT_OBJECT_PATTERN = re.compile(r"^[0-9a-f]{40,64}$")
SHA256_PATTERN = re.compile(r"^[0-9a-f]{64}$")
POSITIVE_RESULTS = {
"target_environment": "passed",
"external_providers": "passed",
"production_approval": "approved",
}
NEGATIVE_RESULTS = {
"target_environment": "failed",
"external_providers": "failed",
"production_approval": "rejected",
}
PROOF_REQUIREMENTS = {
"target_environment": (
"A target-run result bound to this assessment release and installed-evidence digest.",
"An independently trusted authority key permitted for target_environment proof.",
"Opaque control IDs and content hashes for the target acceptance artifacts.",
),
"external_providers": (
"Provider acceptance results bound to this assessment release and installed-evidence digest.",
"An independently trusted authority key permitted for external_providers proof.",
"Opaque control IDs and content hashes; credentials and endpoint identifiers stay outside the report.",
),
"production_approval": (
"An explicit approval bound to this assessment release and installed-evidence digest.",
"An independently provisioned authority key permitted for production_approval.",
"An unexpired signed proof bundle; an assessment or operator cannot approve itself.",
),
}
@dataclass(frozen=True, slots=True)
class EvidenceFinding:
severity: str
code: str
message: str
assessment_ids: tuple[str, ...] = ()
@dataclass(frozen=True, slots=True)
class InstalledEvidenceReview:
findings: tuple[EvidenceFinding, ...]
changes: tuple[dict[str, Any], ...]
changed_repositories: frozenset[str]
affected_module_ids: frozenset[str]
proof_scope: Mapping[str, Any]
evidence_sha256: str | None
@dataclass(frozen=True, slots=True)
class BoundaryEvidenceReview:
findings: tuple[EvidenceFinding, ...]
proof_scope: Mapping[str, Any]
@dataclass(slots=True)
class CollectionHashBudget:
remaining_files: int = MAX_COLLECTION_RECORD_FILES
remaining_bytes: int = MAX_HASHED_COLLECTION_BYTES
def validate_payload(
*, payload: dict[str, Any], schema: dict[str, Any]
) -> tuple[str, ...]:
try:
Draft202012Validator.check_schema(schema)
except SchemaError as exc:
return (f"$schema: {exc.message}",)
validator = Draft202012Validator(schema, format_checker=FormatChecker())
return tuple(
f"{_json_path(error.absolute_path)}: failed {error.validator!r} validation"
for error in sorted(
validator.iter_errors(payload),
key=lambda item: (
tuple(str(part) for part in item.absolute_path),
str(item.validator),
item.message,
),
)
)
def collect_installed_composition(
*,
assessment: dict[str, Any],
collected_at: datetime | None = None,
distributions: Iterable[metadata.Distribution] | None = None,
) -> dict[str, Any]:
"""Collect only bounded, non-identifying GovOPlaN distribution evidence.
Loading ``govoplan.modules`` entry points executes installed module code. The
collector catches failures and records only a stable error code; it never
serializes exception text, URLs, local paths, hostnames, or usernames.
"""
artifacts: list[dict[str, Any]] = []
issues: list[dict[str, str]] = []
hash_budget = CollectionHashBudget()
candidates: list[tuple[str, metadata.Distribution]] = []
source = metadata.distributions() if distributions is None else distributions
for distribution in source:
try:
package_name = normalize_package_name(str(distribution.metadata["Name"]))
except (Exception, SystemExit):
continue
if package_name.startswith("govoplan-"):
candidates.append((package_name, distribution))
candidates.sort(key=lambda item: (item[0], _distribution_version(item[1])))
if len(candidates) > MAX_GOVOPLAN_DISTRIBUTIONS:
candidates = candidates[:MAX_GOVOPLAN_DISTRIBUTIONS]
issues.append(
{
"code": "collection-limit-exceeded",
"package_name": "govoplan-collector",
}
)
name_counts: dict[str, int] = {}
for package_name, _ in candidates:
name_counts[package_name] = name_counts.get(package_name, 0) + 1
for package_name, count in sorted(name_counts.items()):
if count > 1:
issues.append(
{
"code": "duplicate-distribution",
"package_name": package_name,
}
)
for package_name, distribution in candidates:
version = _distribution_version(distribution)
if not version or PACKAGE_PATTERN.fullmatch(package_name) is None:
issues.append(
{
"code": "distribution-metadata-invalid",
"package_name": package_name
if PACKAGE_PATTERN.fullmatch(package_name)
else "govoplan-invalid",
}
)
continue
modules, module_issues = _distribution_modules(
package_name=package_name,
distribution=distribution,
)
issues.extend(module_issues)
artifacts.append(
{
"package_name": package_name,
"package_version": version,
"modules": modules,
"source_provenance": _source_provenance(distribution),
"record_integrity": _record_integrity(
distribution,
budget=hash_budget,
),
}
)
observed_at = collected_at or datetime.now(tz=UTC)
if observed_at.tzinfo is None:
observed_at = observed_at.replace(tzinfo=UTC)
release = assessment.get("release")
assessment_release = release.get("ref") if isinstance(release, dict) else None
sorted_issues = sorted(
_unique_dicts(issues),
key=lambda item: (item["package_name"], item["code"]),
)
if len(sorted_issues) > 256:
limit_issue = {
"code": "collection-limit-exceeded",
"package_name": "govoplan-collector",
}
sorted_issues = sorted_issues[:255]
if limit_issue not in sorted_issues:
sorted_issues.append(limit_issue)
sorted_issues.sort(key=lambda item: (item["package_name"], item["code"]))
return {
"$schema": "./installed-composition-evidence.schema.json",
"schema_version": "0.1.0",
"evidence_kind": "govoplan.installed-composition",
"assessment_id": str(assessment.get("assessment_id") or "invalid-assessment"),
"assessment_release": str(assessment_release or "invalid-release"),
"collected_at": observed_at.astimezone(UTC).isoformat().replace("+00:00", "Z"),
"scope": "current-python-environment.govoplan-distributions",
"artifacts": sorted(
artifacts,
key=lambda item: (
item["package_name"],
item["package_version"],
canonical_bytes(item),
),
),
"collection_issues": sorted_issues,
}
def review_installed_composition(
*,
assessment: dict[str, Any],
catalog_entries: Mapping[str, Mapping[str, Any]],
selected_versions: Mapping[str, str],
selected_commits: Mapping[str, str],
evidence: dict[str, Any] | None,
schema: dict[str, Any] | None,
) -> InstalledEvidenceReview:
if evidence is None:
return InstalledEvidenceReview(
findings=(),
changes=(),
changed_repositories=frozenset(),
affected_module_ids=frozenset(),
proof_scope=_unchecked_installed_scope(),
evidence_sha256=None,
)
digest = canonical_sha256(evidence)
if schema is None:
finding = EvidenceFinding(
"blocker",
"installed_evidence_schema_missing",
"Installed evidence was supplied without its validation schema.",
("assessment.installed_composition",),
)
return InstalledEvidenceReview(
findings=(finding,),
changes=(),
changed_repositories=frozenset(),
affected_module_ids=frozenset(),
proof_scope=_invalid_installed_scope(digest=digest),
evidence_sha256=digest,
)
schema_errors = validate_payload(payload=evidence, schema=schema)
if schema_errors:
findings = tuple(
EvidenceFinding(
"blocker",
"installed_evidence_schema",
message,
("assessment.installed_composition",),
)
for message in schema_errors
)
return InstalledEvidenceReview(
findings=findings,
changes=(),
changed_repositories=frozenset(),
affected_module_ids=frozenset(),
proof_scope=_invalid_installed_scope(digest=digest),
evidence_sha256=digest,
)
findings: list[EvidenceFinding] = []
changes: list[dict[str, Any]] = []
changed_repositories: set[str] = set()
affected_module_ids: set[str] = set()
expected_release = (
assessment.get("release", {}).get("ref")
if isinstance(assessment.get("release"), dict)
else None
)
binding_valid = True
if evidence.get("assessment_id") != assessment.get("assessment_id"):
binding_valid = False
findings.append(
EvidenceFinding(
"blocker",
"installed_evidence_assessment_mismatch",
"Installed evidence is bound to a different assessment ID.",
("assessment.installed_composition",),
)
)
if evidence.get("assessment_release") != expected_release:
binding_valid = False
findings.append(
EvidenceFinding(
"blocker",
"installed_evidence_release_mismatch",
"Installed evidence is bound to a different assessment release.",
("assessment.release", "assessment.installed_composition"),
)
)
artifact_rows = [
item for item in evidence.get("artifacts", []) if isinstance(item, dict)
]
artifacts_by_package: dict[str, dict[str, Any]] = {}
duplicate_packages: set[str] = set()
for artifact in artifact_rows:
package_name = normalize_package_name(str(artifact.get("package_name") or ""))
if package_name in artifacts_by_package:
duplicate_packages.add(package_name)
else:
artifacts_by_package[package_name] = artifact
for package_name in sorted(duplicate_packages):
binding_valid = False
findings.append(
EvidenceFinding(
"blocker",
"installed_distribution_duplicate",
f"Installed evidence repeats distribution {package_name!r}.",
("assessment.installed_composition",),
)
)
expected_by_package: dict[str, tuple[dict[str, Any], Mapping[str, Any]]] = {}
assessment_by_module: dict[str, dict[str, Any]] = {}
for component in assessment.get("composition", []):
if not isinstance(component, dict):
continue
module_id = str(component.get("module_id") or "")
assessment_by_module[module_id] = component
if component.get("enabled") is not True:
continue
catalog_entry = catalog_entries.get(module_id)
if catalog_entry is None:
binding_valid = False
findings.append(
EvidenceFinding(
"blocker",
"installed_expected_catalog_entry_missing",
f"Enabled module {module_id!r} cannot be mapped to a catalog package.",
(f"composition.{module_id}",),
)
)
continue
package_name = normalize_package_name(str(catalog_entry.get("package") or ""))
if not package_name:
binding_valid = False
findings.append(
EvidenceFinding(
"blocker",
"installed_expected_package_missing",
f"Enabled module {module_id!r} has no valid catalog package name.",
(f"composition.{module_id}",),
)
)
continue
if package_name in expected_by_package:
binding_valid = False
findings.append(
EvidenceFinding(
"blocker",
"installed_expected_package_ambiguous",
f"Multiple enabled composition entries map to {package_name!r}.",
("assessment.installed_composition",),
)
)
continue
expected_by_package[package_name] = (component, catalog_entry)
if not expected_by_package:
binding_valid = False
findings.append(
EvidenceFinding(
"blocker",
"installed_expected_composition_empty",
"Installed proof requires at least one enabled assessment component mapped to a catalog package.",
("assessment.installed_composition",),
)
)
record_expected = 0
record_verified = 0
provenance_expected = 0
provenance_anchored = 0
provenance_mutable = 0
provenance_unanchored = 0
artifact_valid = binding_valid and not duplicate_packages
for package_name, (component, catalog_entry) in sorted(expected_by_package.items()):
module_id = str(component["module_id"])
repository = str(component["repository"])
artifact = artifacts_by_package.get(package_name)
record_expected += 1
provenance_expected += 1
if package_name in duplicate_packages:
artifact_valid = False
_record_change(
findings=findings,
changes=changes,
changed_repositories=changed_repositories,
affected_module_ids=affected_module_ids,
component=component,
kind="installed_distribution_duplicate",
code="installed_distribution_duplicate_ambiguous",
message=f"Installed distribution {package_name!r} is duplicated, so no candidate was selected for integrity or provenance conclusions.",
)
continue
if artifact is None:
artifact_valid = False
_record_change(
findings=findings,
changes=changes,
changed_repositories=changed_repositories,
affected_module_ids=affected_module_ids,
component=component,
kind="installed_distribution_missing",
code="installed_distribution_missing",
message=f"Enabled module {module_id!r} has no installed distribution {package_name!r} in the evidence.",
)
continue
expected_version = str(component["manifest_version"])
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
_record_change(
findings=findings,
changes=changes,
changed_repositories=changed_repositories,
affected_module_ids=affected_module_ids,
component=component,
kind="installed_version_mismatch",
code="installed_distribution_version_mismatch",
message=(
f"Installed {package_name!r} version {package_version!r} does not match "
f"assessment {expected_version!r} and catalog {catalog_version!r}."
),
extra={
"installed_version": package_version,
"assessed_version": expected_version,
"catalog_version": catalog_version,
},
)
modules = [
item for item in artifact.get("modules", []) if isinstance(item, dict)
]
module_rows: dict[str, dict[str, Any]] = {}
duplicate_module_ids: set[str] = set()
for item in modules:
observed_id = str(item.get("module_id") or "")
if observed_id in module_rows:
duplicate_module_ids.add(observed_id)
else:
module_rows[observed_id] = item
expected_manifest = module_rows.get(module_id)
manifest_mismatch = (
expected_manifest is None if module_id != "core" else False
) or (
expected_manifest is not None
and str(expected_manifest.get("manifest_version") or "") != expected_version
)
if manifest_mismatch:
artifact_valid = False
_record_change(
findings=findings,
changes=changes,
changed_repositories=changed_repositories,
affected_module_ids=affected_module_ids,
component=component,
kind="installed_manifest_mismatch",
code="installed_manifest_version_mismatch",
message=f"Installed distribution {package_name!r} does not expose module {module_id!r} at version {expected_version!r}.",
)
unexpected_modules = sorted(set(module_rows) - {module_id})
if duplicate_module_ids or unexpected_modules:
artifact_valid = False
_record_change(
findings=findings,
changes=changes,
changed_repositories=changed_repositories,
affected_module_ids=affected_module_ids,
component=component,
kind="installed_manifest_set_mismatch",
code="installed_manifest_set_mismatch",
message=f"Installed distribution {package_name!r} exposes an unexpected or duplicate module entry-point set.",
)
record = artifact.get("record_integrity")
record_status = record.get("status") if isinstance(record, dict) else None
if record_status == "verified":
record_verified += 1
else:
artifact_valid = False
_record_change(
findings=findings,
changes=changes,
changed_repositories=changed_repositories,
affected_module_ids=affected_module_ids,
component=component,
kind="installed_record_unverified",
code="installed_record_integrity_unverified",
message=f"Installed distribution {package_name!r} does not have complete, matching RECORD hash coverage.",
extra={"record_status": record_status},
)
provenance = artifact.get("source_provenance")
provenance_kind = (
str(provenance.get("kind") or "") if isinstance(provenance, dict) else ""
)
if provenance_kind == "vcs-commit":
commit = str(provenance.get("commit") or "").lower()
selected_version = selected_versions.get(repository)
selected_commit = selected_commits.get(repository)
assessed_commit = str(component.get("commit") or "").lower()
commit_matches = commit.startswith(assessed_commit)
selected_matches = (
selected_version != expected_version
or selected_commit is None
or commit == selected_commit.lower()
)
if commit_matches and selected_matches:
provenance_anchored += 1
else:
artifact_valid = False
_record_change(
findings=findings,
changes=changes,
changed_repositories=changed_repositories,
affected_module_ids=affected_module_ids,
component=component,
kind="installed_commit_mismatch",
code="installed_source_commit_mismatch",
message=f"Installed immutable VCS provenance for {package_name!r} does not match the assessed or signed selected-unit commit.",
)
elif provenance_kind in {"editable-vcs", "editable-local", "local-directory"}:
provenance_mutable += 1
artifact_valid = False
_record_change(
findings=findings,
changes=changes,
changed_repositories=changed_repositories,
affected_module_ids=affected_module_ids,
component=component,
kind="installed_source_mutable",
code="installed_source_mutable",
message=f"Installed distribution {package_name!r} is editable or directory-backed and is not immutable release evidence.",
)
else:
provenance_unanchored += 1
artifact_valid = False
_record_change(
findings=findings,
changes=changes,
changed_repositories=changed_repositories,
affected_module_ids=affected_module_ids,
component=component,
kind="installed_source_unanchored",
code="installed_source_unanchored",
message=f"Installed distribution {package_name!r} has no source provenance anchored to the assessed release.",
)
expected_packages = set(expected_by_package)
for package_name in sorted(set(artifacts_by_package) - expected_packages):
artifact_valid = False
matching_module_id = next(
(
module_id
for module_id, entry in catalog_entries.items()
if normalize_package_name(str(entry.get("package") or ""))
== package_name
),
None,
)
component = assessment_by_module.get(str(matching_module_id or ""))
repository = (
str(component.get("repository")) if isinstance(component, dict) else None
)
if repository:
changed_repositories.add(repository)
if matching_module_id and component is not None:
affected_module_ids.add(str(matching_module_id))
target_ids = (
(f"composition.{matching_module_id}",)
if matching_module_id and component is not None
else ("assessment.installed_composition",)
)
findings.append(
EvidenceFinding(
"review",
"installed_distribution_extra",
f"Installed GovOPlaN distribution {package_name!r} is outside the enabled assessed composition.",
target_ids,
)
)
changes.append(
{
"module_id": str(matching_module_id or package_name),
"repository": repository,
"kind": "installed_distribution_extra",
"package_name": package_name,
}
)
for issue in evidence.get("collection_issues", []):
if not isinstance(issue, dict):
continue
package_name = str(issue.get("package_name") or "")
code = str(issue.get("code") or "")
artifact_valid = False
findings.append(
EvidenceFinding(
"review",
"installed_collection_incomplete",
f"Installed evidence collection reported {code!r} for {package_name!r}.",
("assessment.installed_composition",),
)
)
exact_set = (
set(artifacts_by_package) == expected_packages and not duplicate_packages
)
installed_checked = True
record_checked = binding_valid and exact_set and record_expected > 0
provenance_checked = binding_valid and exact_set and provenance_expected > 0
proof_scope = {
"installed_artifacts": {
"checked": installed_checked,
"valid": artifact_valid,
"evidence_sha256": digest,
"expected_distribution_count": len(expected_packages),
"observed_distribution_count": len(artifact_rows),
},
"installed_record_integrity": {
"checked": record_checked,
"valid": record_verified == record_expected if record_checked else None,
"verified_distribution_count": record_verified,
"expected_distribution_count": record_expected,
},
"installed_source_provenance": {
"checked": provenance_checked,
"valid": provenance_anchored == provenance_expected
if provenance_checked
else None,
"anchored_distribution_count": provenance_anchored,
"mutable_distribution_count": provenance_mutable,
"unanchored_distribution_count": provenance_unanchored,
"expected_distribution_count": provenance_expected,
},
"runtime_activation": {
"checked": False,
"valid": None,
"required_evidence": (
"A separately collected runtime registry/configuration snapshot bound to the installed evidence digest.",
),
},
}
return InstalledEvidenceReview(
findings=tuple(findings),
changes=tuple(changes),
changed_repositories=frozenset(changed_repositories),
affected_module_ids=frozenset(affected_module_ids),
proof_scope=proof_scope,
evidence_sha256=digest,
)
def review_boundary_evidence(
*,
assessment: dict[str, Any],
installed_review: InstalledEvidenceReview,
evidence: dict[str, Any] | None,
evidence_schema: dict[str, Any] | None,
authority_keyring: dict[str, Any] | None,
authority_keyring_schema: dict[str, Any] | None,
verification_time: datetime | None = None,
) -> BoundaryEvidenceReview:
scopes = _unchecked_boundary_scope()
if evidence is None:
return BoundaryEvidenceReview(findings=(), proof_scope=scopes)
findings: list[EvidenceFinding] = []
if evidence_schema is None or authority_keyring_schema is None:
findings.append(
EvidenceFinding(
"blocker",
"boundary_evidence_schema_missing",
"Boundary evidence was supplied without both validation schemas.",
("assessment.external_proof",),
)
)
return BoundaryEvidenceReview(tuple(findings), scopes)
evidence_errors = validate_payload(payload=evidence, schema=evidence_schema)
keyring_errors = (
validate_payload(payload=authority_keyring, schema=authority_keyring_schema)
if isinstance(authority_keyring, dict)
else ("$: an independently provisioned proof-authority keyring is required",)
)
findings.extend(
EvidenceFinding(
"blocker",
"boundary_evidence_schema",
message,
("assessment.external_proof",),
)
for message in evidence_errors
)
findings.extend(
EvidenceFinding(
"blocker",
"boundary_authority_keyring_schema",
message,
("assessment.external_proof",),
)
for message in keyring_errors
)
if evidence_errors or keyring_errors or not isinstance(authority_keyring, dict):
return BoundaryEvidenceReview(tuple(findings), scopes)
expected_release = (
assessment.get("release", {}).get("ref")
if isinstance(assessment.get("release"), dict)
else None
)
if evidence.get("assessment_id") != assessment.get("assessment_id"):
findings.append(
EvidenceFinding(
"blocker",
"boundary_assessment_mismatch",
"Boundary evidence is bound to a different assessment ID.",
("assessment.external_proof",),
)
)
if evidence.get("assessment_release") != expected_release:
findings.append(
EvidenceFinding(
"blocker",
"boundary_release_mismatch",
"Boundary evidence is bound to a different assessment release.",
("assessment.release", "assessment.external_proof"),
)
)
installed_digest = installed_review.evidence_sha256
installed_scope = installed_review.proof_scope.get("installed_artifacts", {})
if (
installed_digest is None
or evidence.get("installed_evidence_sha256") != installed_digest
or installed_scope.get("valid") is not True
):
findings.append(
EvidenceFinding(
"blocker",
"boundary_installed_evidence_untrusted",
"Boundary evidence must bind to the supplied, valid installed-composition evidence digest.",
("assessment.installed_composition", "assessment.external_proof"),
)
)
now = verification_time or datetime.now(tz=UTC)
if now.tzinfo is None:
now = now.replace(tzinfo=UTC)
issued_at = _datetime(evidence.get("issued_at"))
expires_at = _datetime(evidence.get("expires_at"))
time_valid = True
if issued_at is None or expires_at is None or issued_at >= expires_at:
time_valid = False
findings.append(
EvidenceFinding(
"blocker",
"boundary_evidence_interval_invalid",
"Boundary evidence must have issued_at earlier than expires_at.",
("assessment.external_proof",),
)
)
elif now < issued_at or now >= expires_at:
time_valid = False
findings.append(
EvidenceFinding(
"blocker",
"boundary_evidence_not_current",
"Boundary evidence is not valid at the verification time.",
("assessment.external_proof",),
)
)
claims = [item for item in evidence.get("claims", []) if isinstance(item, dict)]
claims_by_scope: dict[str, dict[str, Any]] = {}
duplicate_scopes: set[str] = set()
semantic_invalid: set[str] = set()
for claim in claims:
scope = str(claim.get("scope") or "")
if scope in claims_by_scope:
duplicate_scopes.add(scope)
else:
claims_by_scope[scope] = claim
allowed_results = {POSITIVE_RESULTS.get(scope), NEGATIVE_RESULTS.get(scope)}
if claim.get("result") not in allowed_results:
semantic_invalid.add(scope)
controls = claim.get("control_ids")
if not isinstance(controls, list) or len(controls) != len(set(controls)):
semantic_invalid.add(scope)
artifacts = claim.get("artifacts")
artifact_ids = [
str(item.get("artifact_id") or "")
for item in artifacts or []
if isinstance(item, dict)
]
if len(artifact_ids) != len(set(artifact_ids)):
semantic_invalid.add(scope)
for scope in sorted(duplicate_scopes):
findings.append(
EvidenceFinding(
"blocker",
"boundary_claim_duplicate",
f"Boundary evidence repeats claim scope {scope!r}.",
("assessment.external_proof",),
)
)
for scope in sorted(semantic_invalid):
findings.append(
EvidenceFinding(
"blocker",
"boundary_claim_semantics",
f"Boundary claim {scope!r} has an invalid result or duplicate control/artifact IDs.",
("assessment.external_proof",),
)
)
authority_keys, key_findings = _active_authority_keys(
authority_keyring=authority_keyring,
verification_time=now,
)
findings.extend(key_findings)
signature_payload = dict(evidence)
signature_payload.pop("signatures", None)
signature_bytes = canonical_bytes(signature_payload)
authorized_scopes: set[str] = set()
seen_signature_key_ids: set[str] = set()
duplicate_signature_key_ids: set[str] = set()
for signature in evidence.get("signatures", []):
if not isinstance(signature, dict):
continue
key_id = str(signature.get("key_id") or "")
if key_id in seen_signature_key_ids:
duplicate_signature_key_ids.add(key_id)
continue
seen_signature_key_ids.add(key_id)
key = authority_keys.get(key_id)
if key is None or signature.get("algorithm") != "ed25519":
continue
try:
signature_value = base64.b64decode(
str(signature.get("value") or ""), validate=True
)
Ed25519PublicKey.from_public_bytes(key[0]).verify(
signature_value, signature_bytes
)
except (ValueError, InvalidSignature):
continue
authorized_scopes.update(key[1])
if duplicate_signature_key_ids:
findings.append(
EvidenceFinding(
"blocker",
"boundary_signature_duplicate",
"Boundary evidence repeats one or more signature key IDs.",
("assessment.external_proof",),
)
)
unauthorized_scopes = {
scope
for scope in claims_by_scope
if scope in PROOF_REQUIREMENTS and scope not in authorized_scopes
}
for scope in sorted(unauthorized_scopes):
findings.append(
EvidenceFinding(
"blocker",
"boundary_authority_untrusted",
f"No valid independently trusted signature is authorized for {scope!r}.",
("assessment.external_proof",),
)
)
global_valid = not any(item.severity == "blocker" for item in findings)
evidence_digest = canonical_sha256(evidence)
for scope, claim in sorted(claims_by_scope.items()):
if scope not in PROOF_REQUIREMENTS:
continue
if scope in unauthorized_scopes:
continue
if scope in duplicate_scopes or scope in semantic_invalid or not time_valid:
continue
if not global_valid:
continue
result = str(claim.get("result"))
valid = result == POSITIVE_RESULTS[scope]
scopes[scope] = {
"checked": True,
"valid": valid,
"evidence_sha256": evidence_digest,
"result": result,
"control_count": len(claim.get("control_ids") or []),
"artifact_count": len(claim.get("artifacts") or []),
"required_evidence": PROOF_REQUIREMENTS[scope],
}
if not valid:
findings.append(
EvidenceFinding(
"review",
"boundary_claim_negative",
f"Authorized boundary claim {scope!r} has result {result!r}.",
("assessment.external_proof",),
)
)
return BoundaryEvidenceReview(tuple(findings), scopes)
def normalize_package_name(value: str) -> str:
return re.sub(r"[-_.]+", "-", value.strip().lower())
def canonical_bytes(payload: object) -> bytes:
return json.dumps(
payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True
).encode("utf-8")
def canonical_sha256(payload: object) -> str:
return hashlib.sha256(canonical_bytes(payload)).hexdigest()
def _distribution_version(distribution: metadata.Distribution) -> str:
try:
return str(distribution.version)
except Exception:
return ""
def _distribution_modules(
*, package_name: str, distribution: metadata.Distribution
) -> tuple[list[dict[str, str]], list[dict[str, str]]]:
modules: list[dict[str, str]] = []
issues: list[dict[str, str]] = []
try:
all_entry_points = sorted(
(
entry_point
for entry_point in distribution.entry_points
if entry_point.group == "govoplan.modules"
),
key=lambda item: (item.name, item.value),
)
except Exception:
return [], [
{
"code": "distribution-metadata-invalid",
"package_name": package_name,
}
]
if len(all_entry_points) > 16:
issues.append(
{
"code": "module-entry-point-limit-exceeded",
"package_name": package_name,
}
)
for entry_point in all_entry_points[:16]:
try:
manifest = entry_point.load()()
module_id = str(manifest.id)
manifest_version = str(manifest.version)
except (Exception, SystemExit):
issues.append(
{
"code": "module-entry-point-load-failed",
"package_name": package_name,
}
)
continue
if (
OPAQUE_ID_PATTERN.fullmatch(module_id) is None
or VERSION_PATTERN.fullmatch(manifest_version) is None
or len(manifest_version) > 128
):
issues.append(
{
"code": "module-entry-point-invalid",
"package_name": package_name,
}
)
continue
modules.append({"module_id": module_id, "manifest_version": manifest_version})
return sorted(modules, key=lambda item: item["module_id"]), issues
def _source_provenance(distribution: metadata.Distribution) -> dict[str, str]:
try:
raw = distribution.read_text("direct_url.json")
except Exception:
return {"kind": "malformed-direct-url"}
if raw is None:
return {"kind": "index-or-unknown"}
if len(raw.encode("utf-8")) > MAX_DIRECT_URL_BYTES:
return {"kind": "malformed-direct-url"}
try:
direct_url = json.loads(raw)
except (TypeError, json.JSONDecodeError):
return {"kind": "malformed-direct-url"}
if not isinstance(direct_url, dict):
return {"kind": "malformed-direct-url"}
vcs_info = direct_url.get("vcs_info")
directory_info = direct_url.get("dir_info")
editable = (
isinstance(directory_info, dict) and directory_info.get("editable") is True
)
if isinstance(vcs_info, dict):
commit = str(vcs_info.get("commit_id") or "").lower()
if GIT_OBJECT_PATTERN.fullmatch(commit):
return {
"kind": "editable-vcs" if editable else "vcs-commit",
"commit": commit,
}
return {"kind": "malformed-direct-url"}
archive_info = direct_url.get("archive_info")
if isinstance(archive_info, dict):
hashes = archive_info.get("hashes")
digest = hashes.get("sha256") if isinstance(hashes, dict) else None
if digest is None:
legacy = str(archive_info.get("hash") or "")
if legacy.startswith("sha256="):
digest = legacy.removeprefix("sha256=")
digest_text = str(digest or "").lower()
if SHA256_PATTERN.fullmatch(digest_text):
return {"kind": "archive", "sha256": digest_text}
return {"kind": "malformed-direct-url"}
if isinstance(directory_info, dict):
return {"kind": "editable-local" if editable else "local-directory"}
return {"kind": "malformed-direct-url"}
def _record_integrity(
distribution: metadata.Distribution,
*,
budget: CollectionHashBudget,
) -> dict[str, Any]:
counts = {
"hashed_file_count": 0,
"permitted_unhashed_file_count": 0,
"unverifiable_file_count": 0,
"missing_file_count": 0,
"mismatched_file_count": 0,
}
try:
files = tuple(distribution.files or ())
except Exception:
files = ()
if not files:
return {"status": "unavailable", **counts}
if len(files) > MAX_DISTRIBUTION_FILES or len(files) > budget.remaining_files:
budget.remaining_files = 0
return {"status": "limit-exceeded", **counts}
budget.remaining_files -= len(files)
try:
distribution_root = Path(distribution.locate_file("")).resolve()
installation_root = Path(sys.prefix).resolve()
except OSError:
return {"status": "unavailable", **counts}
total_hashed_bytes = 0
limit_exceeded = False
for package_path in files:
hash_info = getattr(package_path, "hash", None)
if hash_info is None:
if _permitted_unhashed_record_path(str(package_path)):
counts["permitted_unhashed_file_count"] += 1
else:
counts["unverifiable_file_count"] += 1
continue
if getattr(hash_info, "mode", None) != "sha256":
counts["unverifiable_file_count"] += 1
continue
try:
resolved_path = Path(distribution.locate_file(package_path)).resolve()
except OSError:
counts["missing_file_count"] += 1
continue
if not (
_is_relative_to(resolved_path, distribution_root)
or _is_relative_to(resolved_path, installation_root)
):
counts["unverifiable_file_count"] += 1
continue
try:
stat = resolved_path.stat()
except OSError:
counts["missing_file_count"] += 1
continue
if not resolved_path.is_file():
counts["missing_file_count"] += 1
continue
if stat.st_size > MAX_HASHED_FILE_BYTES:
limit_exceeded = True
continue
total_hashed_bytes += stat.st_size
if (
total_hashed_bytes > MAX_HASHED_DISTRIBUTION_BYTES
or stat.st_size > budget.remaining_bytes
):
limit_exceeded = True
budget.remaining_bytes = 0
continue
budget.remaining_bytes -= stat.st_size
expected = _record_hash_bytes(str(getattr(hash_info, "value", "")))
if expected is None:
counts["unverifiable_file_count"] += 1
continue
digest = hashlib.sha256()
try:
with resolved_path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
except OSError:
counts["missing_file_count"] += 1
continue
counts["hashed_file_count"] += 1
if not hmac.compare_digest(digest.digest(), expected):
counts["mismatched_file_count"] += 1
if limit_exceeded:
status = "limit-exceeded"
elif counts["missing_file_count"] or counts["mismatched_file_count"]:
status = "mismatch"
elif counts["hashed_file_count"] == 0:
status = "unavailable"
elif counts["unverifiable_file_count"]:
status = "partial"
else:
status = "verified"
return {"status": status, **counts}
def _record_hash_bytes(value: str) -> bytes | None:
try:
decoded = base64.urlsafe_b64decode(value + "=" * (-len(value) % 4))
except (ValueError, TypeError):
return None
return decoded if len(decoded) == hashlib.sha256().digest_size else None
def _permitted_unhashed_record_path(value: str) -> bool:
normalized = value.replace("\\", "/")
return normalized.endswith(
(".dist-info/RECORD", ".dist-info/RECORD.jws", ".dist-info/RECORD.p7s")
)
def _active_authority_keys(
*, authority_keyring: dict[str, Any], verification_time: datetime
) -> tuple[dict[str, tuple[bytes, frozenset[str]]], tuple[EvidenceFinding, ...]]:
result: dict[str, tuple[bytes, frozenset[str]]] = {}
findings: list[EvidenceFinding] = []
seen: set[str] = set()
for item in authority_keyring.get("keys", []):
if not isinstance(item, dict):
continue
key_id = str(item.get("key_id") or "")
if key_id in seen:
findings.append(
EvidenceFinding(
"blocker",
"boundary_authority_key_duplicate",
"Proof-authority keyring repeats a key ID.",
("assessment.external_proof",),
)
)
continue
seen.add(key_id)
if item.get("status") not in {"active", "next"}:
continue
not_before = _datetime(item.get("not_before"))
not_after = _datetime(item.get("not_after"))
if (not_before is not None and verification_time < not_before) or (
not_after is not None and verification_time > not_after
):
continue
try:
public_key = base64.b64decode(
str(item.get("public_key") or ""), validate=True
)
Ed25519PublicKey.from_public_bytes(public_key)
except ValueError:
continue
result[key_id] = (
public_key,
frozenset(str(value) for value in item.get("allowed_scopes", [])),
)
return result, tuple(findings)
def _record_change(
*,
findings: list[EvidenceFinding],
changes: list[dict[str, Any]],
changed_repositories: set[str],
affected_module_ids: set[str],
component: Mapping[str, Any],
kind: str,
code: str,
message: str,
extra: Mapping[str, Any] | None = None,
) -> None:
module_id = str(component.get("module_id"))
repository = str(component.get("repository"))
changed_repositories.add(repository)
affected_module_ids.add(module_id)
findings.append(
EvidenceFinding(
"review",
code,
message,
(f"composition.{module_id}",),
)
)
change: dict[str, Any] = {
"module_id": module_id,
"repository": repository,
"kind": kind,
}
if extra:
change.update(extra)
changes.append(change)
def _unchecked_installed_scope() -> dict[str, Any]:
return {
"installed_artifacts": {
"checked": False,
"valid": None,
"required_evidence": (
"A bounded installed-composition evidence document collected from the Python environment under review.",
),
},
"installed_record_integrity": {"checked": False, "valid": None},
"installed_source_provenance": {"checked": False, "valid": None},
"runtime_activation": {
"checked": False,
"valid": None,
"required_evidence": (
"A separately collected runtime registry/configuration snapshot bound to installed evidence.",
),
},
}
def _invalid_installed_scope(*, digest: str) -> dict[str, Any]:
scope = _unchecked_installed_scope()
scope["installed_artifacts"] = {
"checked": True,
"valid": False,
"evidence_sha256": digest,
}
return scope
def _unchecked_boundary_scope() -> dict[str, Any]:
return {
scope: {
"checked": False,
"valid": None,
"required_evidence": requirements,
}
for scope, requirements in PROOF_REQUIREMENTS.items()
}
def _datetime(value: object) -> datetime | None:
if not isinstance(value, str) or not value:
return None
try:
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
except ValueError:
return None
return parsed if parsed.tzinfo is not None else parsed.replace(tzinfo=UTC)
def _json_path(parts: Iterable[object]) -> str:
path = "$"
for part in parts:
path += f"[{part}]" if isinstance(part, int) else f".{part}"
return path
def _unique_dicts(values: Sequence[dict[str, str]]) -> list[dict[str, str]]:
result: list[dict[str, str]] = []
seen: set[tuple[tuple[str, str], ...]] = set()
for value in values:
key = tuple(sorted(value.items()))
if key not in seen:
seen.add(key)
result.append(value)
return result
def _is_relative_to(path: Path, root: Path) -> bool:
try:
path.relative_to(root)
except ValueError:
return False
return True