1269 lines
59 KiB
Python
1269 lines
59 KiB
Python
#!/usr/bin/env python3
|
|
"""Prepare, sign, and verify named legal approval and trust requests for public policies."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import base64
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
from datetime import datetime, timedelta, timezone
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from cryptography.hazmat.primitives import serialization
|
|
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey
|
|
|
|
|
|
PACKAGE_SCHEMA = "yuqei-public-policy-review-package-v1"
|
|
PENDING_PAYLOAD_SCHEMA = "yuqei-public-policy-approval-pending-v1"
|
|
APPROVED_PAYLOAD_SCHEMA = "yuqei-public-policy-approval-payload-v1"
|
|
APPROVAL_SCHEMA = "yuqei-public-policy-legal-approval-v1"
|
|
KEYRING_SCHEMA = "yuqei-public-policy-legal-keyring-v2"
|
|
TRUST_REQUEST_SCHEMA = "yuqei-public-policy-key-trust-request-v2"
|
|
TRUST_APPROVAL_PAYLOAD_SCHEMA = "yuqei-public-policy-key-trust-approval-payload-v2"
|
|
TRUST_APPROVAL_SCHEMA = "yuqei-public-policy-key-trust-approval-v2"
|
|
SIGNATURE_DOMAIN = b"YUQEI_PUBLIC_POLICY_LEGAL_APPROVAL_V1\0"
|
|
TRUST_SIGNATURE_DOMAIN = b"YUQEI_PUBLIC_POLICY_KEY_TRUST_APPROVAL_V2\0"
|
|
SIGN_CONFIRMATION = "I_APPROVE_THIS_EXACT_POLICY_PAYLOAD"
|
|
TRUST_SIGN_CONFIRMATION = "I_APPROVE_THIS_EXACT_POLICY_KEY_TRUST_PAYLOAD"
|
|
TRUST_REVIEW_ATTESTATIONS = [
|
|
"confirmed_same_named_reviewer_identity_and_authority",
|
|
"verified_legal_signing_key_fingerprint_out_of_band",
|
|
"accepts_separate_key_security_or_operations_responsibility",
|
|
]
|
|
EXPECTED_POLICY_IDS = {
|
|
"user_terms",
|
|
"privacy_policy",
|
|
"service_agreement",
|
|
"ai_use_notice",
|
|
}
|
|
MAX_POLICY_BYTES = 512 * 1024
|
|
MAX_JSON_BYTES = 2 * 1024 * 1024
|
|
MAX_APPROVAL_WINDOW = timedelta(days=7)
|
|
SHA256_PATTERN = re.compile(r"^[0-9a-f]{64}$")
|
|
KEY_ID_PATTERN = re.compile(r"^[a-z0-9][a-z0-9._-]{2,127}$")
|
|
PROHIBITED_MARKERS = ("TODO", "TBD", "产品草案", "待补充", "待确认")
|
|
SOLE_POLICY_REVIEWER = {
|
|
"approver_id": "chen-yuqi",
|
|
"reviewer": "陈钰麒",
|
|
"role": "总经理",
|
|
"organization": "内蒙古钰麒科技有限公司",
|
|
}
|
|
|
|
|
|
class PolicyApprovalError(ValueError):
|
|
pass
|
|
|
|
|
|
def canonical_json(value: Any) -> bytes:
|
|
return json.dumps(
|
|
value,
|
|
ensure_ascii=False,
|
|
sort_keys=True,
|
|
separators=(",", ":"),
|
|
allow_nan=False,
|
|
).encode("utf-8")
|
|
|
|
|
|
def sha256_bytes(value: bytes) -> str:
|
|
return hashlib.sha256(value).hexdigest()
|
|
|
|
|
|
def parse_timestamp(value: str, *, label: str) -> datetime:
|
|
try:
|
|
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
except ValueError as exc:
|
|
raise PolicyApprovalError(f"{label} must be an ISO-8601 timestamp") from exc
|
|
if parsed.tzinfo is None or parsed.utcoffset() is None:
|
|
raise PolicyApprovalError(f"{label} must include a timezone")
|
|
return parsed
|
|
|
|
|
|
def normalized_timestamp(value: str, *, label: str) -> str:
|
|
return parse_timestamp(value, label=label).isoformat()
|
|
|
|
|
|
def read_bounded(path: Path, *, label: str, max_bytes: int) -> bytes:
|
|
try:
|
|
size = path.stat().st_size
|
|
except OSError as exc:
|
|
raise PolicyApprovalError(f"{label} is unavailable: {path}") from exc
|
|
if size <= 0 or size > max_bytes:
|
|
raise PolicyApprovalError(f"{label} size is outside the allowed range")
|
|
return path.read_bytes()
|
|
|
|
|
|
def strict_json(raw: bytes, *, label: str) -> dict[str, Any]:
|
|
def reject_duplicates(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
|
|
result: dict[str, Any] = {}
|
|
for key, value in pairs:
|
|
if key in result:
|
|
raise PolicyApprovalError(f"{label} contains duplicate key: {key}")
|
|
result[key] = value
|
|
return result
|
|
|
|
try:
|
|
value = json.loads(raw.decode("utf-8"), object_pairs_hook=reject_duplicates)
|
|
except (UnicodeError, json.JSONDecodeError) as exc:
|
|
raise PolicyApprovalError(f"{label} is not valid UTF-8 JSON") from exc
|
|
if not isinstance(value, dict):
|
|
raise PolicyApprovalError(f"{label} must be a JSON object")
|
|
return value
|
|
|
|
|
|
def write_atomic(path: Path, raw: bytes, *, private: bool = False) -> None:
|
|
path = path.resolve()
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
temporary = path.with_name(f".{path.name}.{os.getpid()}.tmp")
|
|
try:
|
|
temporary.write_bytes(raw)
|
|
if private and os.name != "nt":
|
|
temporary.chmod(0o600)
|
|
temporary.replace(path)
|
|
finally:
|
|
temporary.unlink(missing_ok=True)
|
|
|
|
|
|
def load_public_key(path: Path) -> tuple[Ed25519PublicKey, bytes, bytes]:
|
|
pem = read_bounded(path, label="policy legal public key", max_bytes=64 * 1024)
|
|
try:
|
|
key = serialization.load_pem_public_key(pem)
|
|
except ValueError as exc:
|
|
raise PolicyApprovalError("policy legal public key is not valid PEM") from exc
|
|
if not isinstance(key, Ed25519PublicKey):
|
|
raise PolicyApprovalError("policy legal public key must be Ed25519")
|
|
raw = key.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
|
|
return key, pem, raw
|
|
|
|
|
|
def load_policy_material(policy_dir: Path) -> tuple[dict[str, Any], bytes, list[dict[str, Any]]]:
|
|
policy_dir = policy_dir.resolve()
|
|
manifest_path = policy_dir / "manifest.json"
|
|
manifest_raw = read_bounded(manifest_path, label="policy manifest", max_bytes=MAX_JSON_BYTES)
|
|
manifest = strict_json(manifest_raw, label="policy manifest")
|
|
if manifest.get("schema_version") != "yuqei-public-policy-candidate-v1":
|
|
raise PolicyApprovalError("policy manifest schema_version is invalid")
|
|
|
|
version = _required_text(manifest, "version")
|
|
effective_at = normalized_timestamp(_required_text(manifest, "effective_at"), label="effective_at")
|
|
if effective_at != manifest["effective_at"]:
|
|
raise PolicyApprovalError("effective_at must use canonical ISO-8601 formatting")
|
|
publisher = manifest.get("publisher")
|
|
if not isinstance(publisher, dict):
|
|
raise PolicyApprovalError("policy publisher must be an object")
|
|
for field in (
|
|
"legal_name",
|
|
"unified_social_credit_code",
|
|
"registered_address",
|
|
"public_contact_email",
|
|
"privacy_contact_email",
|
|
):
|
|
_required_text(publisher, field)
|
|
if manifest.get("existing_user_reconsent") not in {"required_on_next_login", "not_required"}:
|
|
raise PolicyApprovalError("existing_user_reconsent is invalid")
|
|
if manifest.get("approval_window_days") != 7:
|
|
raise PolicyApprovalError("policy approval window must be exactly seven days")
|
|
|
|
documents = manifest.get("documents")
|
|
if not isinstance(documents, list) or len(documents) != len(EXPECTED_POLICY_IDS):
|
|
raise PolicyApprovalError("policy manifest must bind exactly four documents")
|
|
material: list[dict[str, Any]] = []
|
|
seen_ids: set[str] = set()
|
|
seen_files: set[str] = set()
|
|
for document in documents:
|
|
if not isinstance(document, dict):
|
|
raise PolicyApprovalError("policy document manifest entry must be an object")
|
|
policy_id = _required_text(document, "policy_id")
|
|
title = _required_text(document, "title")
|
|
public_path = _required_text(document, "public_path")
|
|
source_file = _required_text(document, "source_file")
|
|
if Path(source_file).name != source_file or source_file in seen_files:
|
|
raise PolicyApprovalError("policy source_file must be unique and traversal-free")
|
|
if policy_id in seen_ids:
|
|
raise PolicyApprovalError("policy_id values must be unique")
|
|
source_path = policy_dir / source_file
|
|
raw = read_bounded(source_path, label=f"policy document {policy_id}", max_bytes=MAX_POLICY_BYTES)
|
|
if b"\r" in raw or raw.startswith(b"\xef\xbb\xbf"):
|
|
raise PolicyApprovalError(f"policy document {policy_id} must use BOM-free LF UTF-8")
|
|
try:
|
|
text = raw.decode("utf-8")
|
|
except UnicodeError as exc:
|
|
raise PolicyApprovalError(f"policy document {policy_id} is not UTF-8") from exc
|
|
if not text.startswith(f"# {title}\n"):
|
|
raise PolicyApprovalError(f"policy document {policy_id} title does not match manifest")
|
|
if version not in text or "2026年8月13日 00:00(北京时间)" not in text:
|
|
raise PolicyApprovalError(f"policy document {policy_id} omits version or effective time")
|
|
markers = [marker for marker in PROHIBITED_MARKERS if marker in text]
|
|
if markers:
|
|
raise PolicyApprovalError(f"policy document {policy_id} contains unresolved markers")
|
|
material.append(
|
|
{
|
|
"policy_id": policy_id,
|
|
"title": title,
|
|
"public_path": public_path,
|
|
"source_file": source_file,
|
|
"sha256": sha256_bytes(raw),
|
|
"size_bytes": len(raw),
|
|
}
|
|
)
|
|
seen_ids.add(policy_id)
|
|
seen_files.add(source_file)
|
|
if seen_ids != EXPECTED_POLICY_IDS:
|
|
raise PolicyApprovalError("policy manifest contains an unexpected policy set")
|
|
material.sort(key=lambda item: item["policy_id"])
|
|
return manifest, manifest_raw, material
|
|
|
|
|
|
def build_review_package(
|
|
*,
|
|
policy_dir: Path,
|
|
public_key_file: Path,
|
|
key_id: str,
|
|
approver_id: str,
|
|
reviewer: str,
|
|
role: str,
|
|
organization: str,
|
|
prepared_at: str,
|
|
key_not_before: str,
|
|
key_not_after: str,
|
|
) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]:
|
|
if not KEY_ID_PATTERN.fullmatch(key_id):
|
|
raise PolicyApprovalError("key_id format is invalid")
|
|
prepared_at = normalized_timestamp(prepared_at, label="prepared_at")
|
|
key_not_before = normalized_timestamp(key_not_before, label="key_not_before")
|
|
key_not_after = normalized_timestamp(key_not_after, label="key_not_after")
|
|
if parse_timestamp(key_not_after, label="key_not_after") <= parse_timestamp(
|
|
key_not_before,
|
|
label="key_not_before",
|
|
):
|
|
raise PolicyApprovalError("key_not_after must be later than key_not_before")
|
|
manifest, manifest_raw, documents = load_policy_material(policy_dir)
|
|
_, public_pem, public_raw = load_public_key(public_key_file)
|
|
identity = {
|
|
"approver_id": _nonempty(approver_id, "approver_id"),
|
|
"reviewer": _nonempty(reviewer, "reviewer"),
|
|
"role": _nonempty(role, "role"),
|
|
"organization": _nonempty(organization, "organization"),
|
|
}
|
|
if identity != SOLE_POLICY_REVIEWER:
|
|
raise PolicyApprovalError("policy approval must use the configured sole named reviewer")
|
|
if identity["organization"] != manifest["publisher"]["legal_name"]:
|
|
raise PolicyApprovalError("approver organization must match the policy publisher")
|
|
|
|
review_package = {
|
|
"schema_version": PACKAGE_SCHEMA,
|
|
"status": "pending_named_legal_review",
|
|
"prepared_at": prepared_at,
|
|
"policy_dir_name": policy_dir.resolve().name,
|
|
"policy_version": manifest["version"],
|
|
"effective_at": manifest["effective_at"],
|
|
"manifest_sha256": sha256_bytes(manifest_raw),
|
|
"documents": documents,
|
|
"publisher": manifest["publisher"],
|
|
"governing_law": manifest["governing_law"],
|
|
"dispute_resolution": manifest["dispute_resolution"],
|
|
"existing_user_reconsent": manifest["existing_user_reconsent"],
|
|
"approval_window_days": manifest["approval_window_days"],
|
|
"approver": identity,
|
|
"key": {
|
|
"key_id": key_id,
|
|
"algorithm": "Ed25519",
|
|
"public_key_file_sha256": sha256_bytes(public_pem),
|
|
"public_key_raw_sha256": sha256_bytes(public_raw),
|
|
"not_before": key_not_before,
|
|
"not_after": key_not_after,
|
|
},
|
|
}
|
|
review_package_raw = canonical_json(review_package)
|
|
pending_payload = {
|
|
"schema_version": PENDING_PAYLOAD_SCHEMA,
|
|
"status": "pending_named_legal_review",
|
|
"prepared_at": prepared_at,
|
|
"review_package_sha256": sha256_bytes(review_package_raw),
|
|
"policy_version": manifest["version"],
|
|
"effective_at": manifest["effective_at"],
|
|
"documents": documents,
|
|
"publisher": manifest["publisher"],
|
|
"governing_law": manifest["governing_law"],
|
|
"dispute_resolution": manifest["dispute_resolution"],
|
|
"existing_user_reconsent": manifest["existing_user_reconsent"],
|
|
"approval_request": {
|
|
**identity,
|
|
"key_id": key_id,
|
|
"algorithm": "Ed25519",
|
|
"public_key_file_sha256": sha256_bytes(public_pem),
|
|
"public_key_raw_sha256": sha256_bytes(public_raw),
|
|
"key_not_before": key_not_before,
|
|
"key_not_after": key_not_after,
|
|
"maximum_approval_window_seconds": int(MAX_APPROVAL_WINDOW.total_seconds()),
|
|
"required_confirmation": SIGN_CONFIRMATION,
|
|
},
|
|
}
|
|
keyring_candidate = {
|
|
"schema_version": KEYRING_SCHEMA,
|
|
"status": "candidate_not_trusted",
|
|
"keys": [
|
|
{
|
|
**identity,
|
|
"key_id": key_id,
|
|
"algorithm": "Ed25519",
|
|
"public_key_pem": public_pem.decode("ascii"),
|
|
"public_key_file_sha256": sha256_bytes(public_pem),
|
|
"public_key_raw_sha256": sha256_bytes(public_raw),
|
|
"not_before": key_not_before,
|
|
"not_after": key_not_after,
|
|
"revoked": False,
|
|
}
|
|
],
|
|
}
|
|
return review_package, pending_payload, keyring_candidate
|
|
|
|
|
|
def finalize_payload(
|
|
pending_payload: dict[str, Any],
|
|
*,
|
|
approved_at: str,
|
|
expires_at: str,
|
|
) -> dict[str, Any]:
|
|
if pending_payload.get("schema_version") != PENDING_PAYLOAD_SCHEMA:
|
|
raise PolicyApprovalError("pending payload schema_version is invalid")
|
|
if pending_payload.get("status") != "pending_named_legal_review":
|
|
raise PolicyApprovalError("only a pending policy payload can be finalized")
|
|
approved = parse_timestamp(approved_at, label="approved_at")
|
|
expires = parse_timestamp(expires_at, label="expires_at")
|
|
prepared = parse_timestamp(_required_text(pending_payload, "prepared_at"), label="prepared_at")
|
|
if approved < prepared:
|
|
raise PolicyApprovalError("approved_at cannot precede prepared_at")
|
|
if expires <= approved or expires - approved > MAX_APPROVAL_WINDOW:
|
|
raise PolicyApprovalError("approval validity must be greater than zero and no longer than seven days")
|
|
request = pending_payload.get("approval_request")
|
|
if not isinstance(request, dict):
|
|
raise PolicyApprovalError("pending approval_request is invalid")
|
|
key_not_before = parse_timestamp(_required_text(request, "key_not_before"), label="key_not_before")
|
|
key_not_after = parse_timestamp(_required_text(request, "key_not_after"), label="key_not_after")
|
|
if approved < key_not_before or expires > key_not_after:
|
|
raise PolicyApprovalError("approval window must remain within the signing key validity window")
|
|
return {
|
|
**pending_payload,
|
|
"schema_version": APPROVED_PAYLOAD_SCHEMA,
|
|
"status": "approved",
|
|
"pending_payload_sha256": sha256_bytes(canonical_json(pending_payload)),
|
|
"decision": {
|
|
"decision": "approved",
|
|
"approved_at": approved.isoformat(),
|
|
"expires_at": expires.isoformat(),
|
|
"confirmation": SIGN_CONFIRMATION,
|
|
},
|
|
}
|
|
|
|
|
|
def sign_payload(
|
|
payload: dict[str, Any],
|
|
*,
|
|
private_key_file: Path,
|
|
password_file: Path,
|
|
confirmation: str,
|
|
) -> dict[str, Any]:
|
|
if confirmation != SIGN_CONFIRMATION:
|
|
raise PolicyApprovalError(f"signing requires confirmation {SIGN_CONFIRMATION}")
|
|
if payload.get("schema_version") != APPROVED_PAYLOAD_SCHEMA or payload.get("status") != "approved":
|
|
raise PolicyApprovalError("only a finalized approved payload can be signed")
|
|
private_raw = read_bounded(private_key_file, label="policy legal private key", max_bytes=64 * 1024)
|
|
password = read_bounded(password_file, label="policy legal private key password", max_bytes=4096).strip()
|
|
if not password:
|
|
raise PolicyApprovalError("policy legal private key password is empty")
|
|
try:
|
|
key = serialization.load_pem_private_key(private_raw, password=password)
|
|
except (TypeError, ValueError) as exc:
|
|
raise PolicyApprovalError("policy legal private key or password is invalid") from exc
|
|
if not isinstance(key, Ed25519PrivateKey):
|
|
raise PolicyApprovalError("policy legal private key must be Ed25519")
|
|
public_raw = key.public_key().public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
|
|
expected = payload.get("approval_request", {}).get("public_key_raw_sha256")
|
|
if sha256_bytes(public_raw) != expected:
|
|
raise PolicyApprovalError("private key does not match the approved public key binding")
|
|
payload_raw = canonical_json(payload)
|
|
signature = key.sign(SIGNATURE_DOMAIN + payload_raw)
|
|
approval_id = f"policy-approval-{sha256_bytes(payload_raw + signature)[:24]}"
|
|
return {
|
|
"schema_version": APPROVAL_SCHEMA,
|
|
"approval_id": approval_id,
|
|
"payload": payload,
|
|
"signature": base64.b64encode(signature).decode("ascii"),
|
|
}
|
|
|
|
|
|
def verify_approval(
|
|
approval: dict[str, Any],
|
|
*,
|
|
keyring: dict[str, Any],
|
|
policy_dir: Path,
|
|
checked_at: str,
|
|
allow_candidate_keyring: bool,
|
|
) -> dict[str, Any]:
|
|
if approval.get("schema_version") != APPROVAL_SCHEMA:
|
|
raise PolicyApprovalError("policy approval schema_version is invalid")
|
|
payload = approval.get("payload")
|
|
if not isinstance(payload, dict) or payload.get("schema_version") != APPROVED_PAYLOAD_SCHEMA:
|
|
raise PolicyApprovalError("signed policy approval payload is invalid")
|
|
decision = payload.get("decision")
|
|
if not isinstance(decision, dict) or decision.get("decision") != "approved":
|
|
raise PolicyApprovalError("policy approval decision is not approved")
|
|
checked = parse_timestamp(checked_at, label="checked_at")
|
|
approved = parse_timestamp(_required_text(decision, "approved_at"), label="approved_at")
|
|
expires = parse_timestamp(_required_text(decision, "expires_at"), label="expires_at")
|
|
if checked < approved or checked > expires:
|
|
raise PolicyApprovalError("policy approval is not valid at checked_at")
|
|
|
|
manifest, manifest_raw, documents = load_policy_material(policy_dir)
|
|
if documents != payload.get("documents"):
|
|
raise PolicyApprovalError("policy document bytes do not match the signed payload")
|
|
manifest_bindings = {
|
|
"policy_version": manifest.get("version"),
|
|
"effective_at": manifest.get("effective_at"),
|
|
"publisher": manifest.get("publisher"),
|
|
"governing_law": manifest.get("governing_law"),
|
|
"dispute_resolution": manifest.get("dispute_resolution"),
|
|
"existing_user_reconsent": manifest.get("existing_user_reconsent"),
|
|
}
|
|
if any(payload.get(field) != value for field, value in manifest_bindings.items()):
|
|
raise PolicyApprovalError("policy manifest legal terms do not match the signed payload")
|
|
request = payload.get("approval_request")
|
|
if not isinstance(request, dict):
|
|
raise PolicyApprovalError("policy approval_request is invalid")
|
|
_require_sole_policy_reviewer(request, label="policy approval")
|
|
expected_review_package = {
|
|
"schema_version": PACKAGE_SCHEMA,
|
|
"status": "pending_named_legal_review",
|
|
"prepared_at": _required_text(payload, "prepared_at"),
|
|
"policy_dir_name": policy_dir.resolve().name,
|
|
"policy_version": manifest["version"],
|
|
"effective_at": manifest["effective_at"],
|
|
"manifest_sha256": sha256_bytes(manifest_raw),
|
|
"documents": documents,
|
|
"publisher": manifest["publisher"],
|
|
"governing_law": manifest["governing_law"],
|
|
"dispute_resolution": manifest["dispute_resolution"],
|
|
"existing_user_reconsent": manifest["existing_user_reconsent"],
|
|
"approval_window_days": manifest["approval_window_days"],
|
|
"approver": {
|
|
field: request.get(field)
|
|
for field in ("approver_id", "reviewer", "role", "organization")
|
|
},
|
|
"key": {
|
|
"key_id": request.get("key_id"),
|
|
"algorithm": request.get("algorithm"),
|
|
"public_key_file_sha256": request.get("public_key_file_sha256"),
|
|
"public_key_raw_sha256": request.get("public_key_raw_sha256"),
|
|
"not_before": request.get("key_not_before"),
|
|
"not_after": request.get("key_not_after"),
|
|
},
|
|
}
|
|
if payload.get("review_package_sha256") != sha256_bytes(canonical_json(expected_review_package)):
|
|
raise PolicyApprovalError("policy review package does not match the signed payload")
|
|
|
|
if keyring.get("schema_version") != KEYRING_SCHEMA:
|
|
raise PolicyApprovalError("policy keyring schema_version is invalid")
|
|
if keyring.get("status") == "trusted":
|
|
verify_trusted_keyring_basis(keyring, approval=approval)
|
|
elif not (allow_candidate_keyring and keyring.get("status") == "candidate_not_trusted"):
|
|
raise PolicyApprovalError("policy keyring is not trusted")
|
|
keys = keyring.get("keys")
|
|
if not isinstance(keys, list):
|
|
raise PolicyApprovalError("policy keyring keys are invalid")
|
|
matches = [item for item in keys if isinstance(item, dict) and item.get("key_id") == request.get("key_id")]
|
|
if len(matches) != 1:
|
|
raise PolicyApprovalError("policy signing key must resolve uniquely")
|
|
entry = matches[0]
|
|
_require_sole_policy_reviewer(entry, label="policy keyring entry")
|
|
if entry.get("revoked") is not False:
|
|
raise PolicyApprovalError("policy signing key is revoked")
|
|
key_not_before = parse_timestamp(_required_text(entry, "not_before"), label="key not_before")
|
|
key_not_after = parse_timestamp(_required_text(entry, "not_after"), label="key not_after")
|
|
if approved < key_not_before or expires > key_not_after:
|
|
raise PolicyApprovalError("policy approval is outside the signing key validity window")
|
|
for field in ("approver_id", "reviewer", "role", "organization", "public_key_raw_sha256"):
|
|
if entry.get(field) != request.get(field):
|
|
raise PolicyApprovalError(f"policy key identity mismatch: {field}")
|
|
public_pem = entry.get("public_key_pem")
|
|
if not isinstance(public_pem, str) or not public_pem.strip():
|
|
raise PolicyApprovalError("policy keyring public key is invalid")
|
|
try:
|
|
public_key = serialization.load_pem_public_key(public_pem.encode("ascii"))
|
|
except (UnicodeError, ValueError) as exc:
|
|
raise PolicyApprovalError("policy keyring public key is invalid") from exc
|
|
if not isinstance(public_key, Ed25519PublicKey):
|
|
raise PolicyApprovalError("policy keyring public key must be Ed25519")
|
|
try:
|
|
signature = base64.b64decode(_required_text(approval, "signature"), validate=True)
|
|
public_key.verify(signature, SIGNATURE_DOMAIN + canonical_json(payload))
|
|
except Exception as exc:
|
|
raise PolicyApprovalError("policy approval signature verification failed") from exc
|
|
expected_id = f"policy-approval-{sha256_bytes(canonical_json(payload) + signature)[:24]}"
|
|
if approval.get("approval_id") != expected_id:
|
|
raise PolicyApprovalError("policy approval_id does not match its signed material")
|
|
return {
|
|
"status": "verified",
|
|
"approval_id": expected_id,
|
|
"policy_version": payload["policy_version"],
|
|
"effective_at": payload["effective_at"],
|
|
"checked_at": checked.isoformat(),
|
|
"documents": documents,
|
|
}
|
|
|
|
|
|
def verify_trusted_keyring_basis(keyring: dict[str, Any], *, approval: dict[str, Any]) -> None:
|
|
basis = keyring.get("trust_basis")
|
|
if not isinstance(basis, dict):
|
|
raise PolicyApprovalError("trusted policy keyring lacks a signed single-reviewer trust basis")
|
|
if basis.get("policy_activation_authorized") is not False:
|
|
raise PolicyApprovalError("trusted policy keyring must not authorize policy activation")
|
|
trust_approval = basis.get("trust_approval")
|
|
if not isinstance(trust_approval, dict) or trust_approval.get("schema_version") != TRUST_APPROVAL_SCHEMA:
|
|
raise PolicyApprovalError("trusted policy keyring trust approval is invalid")
|
|
payload = trust_approval.get("payload")
|
|
if not isinstance(payload, dict) or payload.get("schema_version") != TRUST_APPROVAL_PAYLOAD_SCHEMA:
|
|
raise PolicyApprovalError("trusted policy keyring trust payload is invalid")
|
|
decision = payload.get("decision")
|
|
trust_approver = payload.get("trust_approver")
|
|
legal_approver = payload.get("legal_approver")
|
|
if not isinstance(decision, dict) or not isinstance(trust_approver, dict) or not isinstance(legal_approver, dict):
|
|
raise PolicyApprovalError("trusted policy keyring trust bindings are invalid")
|
|
_require_sole_policy_reviewer(legal_approver, label="trusted legal approval")
|
|
_require_sole_policy_reviewer(trust_approver, label="trusted key confirmation")
|
|
if decision.get("decision") != "approve_policy_signing_key_trust":
|
|
raise PolicyApprovalError("trusted policy keyring trust decision is invalid")
|
|
if decision.get("policy_activation_authorized") is not False:
|
|
raise PolicyApprovalError("trusted policy keyring trust decision must not authorize policy activation")
|
|
if decision.get("responsibility") not in {"security", "operations"}:
|
|
raise PolicyApprovalError("trusted policy keyring trust responsibility is invalid")
|
|
if decision.get("attestations") != TRUST_REVIEW_ATTESTATIONS:
|
|
raise PolicyApprovalError("trusted policy keyring trust attestations are incomplete")
|
|
if payload.get("policy_approval_id") != approval.get("approval_id") or payload.get(
|
|
"policy_approval_sha256"
|
|
) != sha256_bytes(canonical_json(approval)):
|
|
raise PolicyApprovalError("trusted policy keyring does not authorize this exact policy approval")
|
|
trusted_at = parse_timestamp(_required_text(basis, "trusted_at"), label="trusted_at")
|
|
approved = parse_timestamp(_required_text(decision, "approved_at"), label="approved_at")
|
|
expires = parse_timestamp(_required_text(decision, "expires_at"), label="expires_at")
|
|
if trusted_at < approved or trusted_at > expires:
|
|
raise PolicyApprovalError("trusted policy keyring was issued outside its approval window")
|
|
candidate = {key: value for key, value in keyring.items() if key != "trust_basis"}
|
|
candidate["status"] = "candidate_not_trusted"
|
|
candidate_sha256 = sha256_bytes(canonical_json(candidate))
|
|
if payload.get("candidate_keyring_sha256") != candidate_sha256:
|
|
raise PolicyApprovalError("trusted policy keyring candidate binding does not match")
|
|
if basis.get("trust_request_sha256") != payload.get("trust_request_sha256"):
|
|
raise PolicyApprovalError("trusted policy keyring trust request binding does not match")
|
|
if basis.get("trust_approval_sha256") != sha256_bytes(canonical_json(trust_approval)):
|
|
raise PolicyApprovalError("trusted policy keyring trust approval hash does not match")
|
|
keys = candidate.get("keys")
|
|
if not isinstance(keys, list) or len(keys) != 1 or not isinstance(keys[0], dict):
|
|
raise PolicyApprovalError("trusted policy keyring candidate key set is invalid")
|
|
legal_key = keys[0]
|
|
_require_sole_policy_reviewer(legal_key, label="trusted policy keyring entry")
|
|
for field in ("approver_id", "reviewer", "role", "organization"):
|
|
if legal_approver.get(field) != legal_key.get(field):
|
|
raise PolicyApprovalError(
|
|
f"trusted policy keyring legal approval identity mismatch: {field}"
|
|
)
|
|
if trust_approver.get(field) != legal_key.get(field):
|
|
raise PolicyApprovalError(
|
|
f"trusted policy keyring does not use the same named reviewer: {field}"
|
|
)
|
|
if trust_approver.get("public_key_raw_sha256") == legal_key.get("public_key_raw_sha256"):
|
|
raise PolicyApprovalError("trusted policy keyring must use a separate trust key")
|
|
public_pem = basis.get("trust_approver_public_key_pem")
|
|
if not isinstance(public_pem, str) or not public_pem.strip():
|
|
raise PolicyApprovalError("trusted policy keyring trust approver public key is invalid")
|
|
try:
|
|
public_key = serialization.load_pem_public_key(public_pem.encode("ascii"))
|
|
except (UnicodeError, ValueError) as exc:
|
|
raise PolicyApprovalError("trusted policy keyring trust approver public key is invalid") from exc
|
|
if not isinstance(public_key, Ed25519PublicKey):
|
|
raise PolicyApprovalError("trusted policy keyring trust approver key must be Ed25519")
|
|
public_raw = public_key.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
|
|
if sha256_bytes(public_pem.encode("ascii")) != trust_approver.get("public_key_file_sha256") or sha256_bytes(
|
|
public_raw
|
|
) != trust_approver.get("public_key_raw_sha256"):
|
|
raise PolicyApprovalError("trusted policy keyring trust approver key binding does not match")
|
|
try:
|
|
signature = base64.b64decode(_required_text(trust_approval, "signature"), validate=True)
|
|
public_key.verify(signature, TRUST_SIGNATURE_DOMAIN + canonical_json(payload))
|
|
except Exception as exc:
|
|
raise PolicyApprovalError("trusted policy keyring trust approval signature verification failed") from exc
|
|
expected_id = f"policy-key-trust-approval-{sha256_bytes(canonical_json(payload) + signature)[:24]}"
|
|
if trust_approval.get("trust_approval_id") != expected_id or basis.get("trust_approval_id") != expected_id:
|
|
raise PolicyApprovalError("trusted policy keyring trust approval_id does not match")
|
|
|
|
|
|
def build_trust_request(
|
|
approval: dict[str, Any],
|
|
*,
|
|
candidate_keyring: dict[str, Any],
|
|
policy_dir: Path,
|
|
requested_at: str,
|
|
) -> dict[str, Any]:
|
|
if candidate_keyring.get("schema_version") != KEYRING_SCHEMA:
|
|
raise PolicyApprovalError("candidate policy keyring schema_version is invalid")
|
|
if candidate_keyring.get("status") != "candidate_not_trusted":
|
|
raise PolicyApprovalError("trust request requires a candidate_not_trusted keyring")
|
|
requested_at = normalized_timestamp(requested_at, label="requested_at")
|
|
verified = verify_approval(
|
|
approval,
|
|
keyring=candidate_keyring,
|
|
policy_dir=policy_dir,
|
|
checked_at=requested_at,
|
|
allow_candidate_keyring=True,
|
|
)
|
|
payload = approval["payload"]
|
|
request = payload["approval_request"]
|
|
decision = payload["decision"]
|
|
return {
|
|
"schema_version": TRUST_REQUEST_SCHEMA,
|
|
"status": "pending_single_reviewer_trust_approval",
|
|
"requested_at": requested_at,
|
|
"activation_authorized": False,
|
|
"approval_id": verified["approval_id"],
|
|
"approval_sha256": sha256_bytes(canonical_json(approval)),
|
|
"candidate_keyring_sha256": sha256_bytes(canonical_json(candidate_keyring)),
|
|
"policy_version": verified["policy_version"],
|
|
"effective_at": verified["effective_at"],
|
|
"approval_expires_at": normalized_timestamp(
|
|
_required_text(decision, "expires_at"),
|
|
label="expires_at",
|
|
),
|
|
"documents": verified["documents"],
|
|
"key": {
|
|
field: request[field]
|
|
for field in (
|
|
"key_id",
|
|
"algorithm",
|
|
"approver_id",
|
|
"reviewer",
|
|
"role",
|
|
"organization",
|
|
"public_key_file_sha256",
|
|
"public_key_raw_sha256",
|
|
)
|
|
},
|
|
"required_reviewer_actions": [
|
|
"confirm_the_named_reviewer_identity_and_authority",
|
|
"verify_public_key_fingerprint_out_of_band",
|
|
"record_one_named_security_or_operations_confirmation",
|
|
"install_a_separate-key_trusted_keyring",
|
|
"rerun_policy_approval_verification_with_candidate_override_disabled",
|
|
],
|
|
"prohibited_automatic_actions": [
|
|
"mark_candidate_keyring_trusted",
|
|
"publish_policy_documents",
|
|
"change_policy_lifecycle_to_effective",
|
|
"start_edge",
|
|
"change_public_traffic",
|
|
],
|
|
}
|
|
|
|
|
|
def validate_trust_request(
|
|
trust_request: dict[str, Any],
|
|
*,
|
|
approval: dict[str, Any],
|
|
candidate_keyring: dict[str, Any],
|
|
policy_dir: Path,
|
|
checked_at: str,
|
|
) -> dict[str, Any]:
|
|
if trust_request.get("schema_version") != TRUST_REQUEST_SCHEMA:
|
|
raise PolicyApprovalError("policy trust request schema_version is invalid")
|
|
if trust_request.get("status") != "pending_single_reviewer_trust_approval":
|
|
raise PolicyApprovalError("policy trust request is not pending named-reviewer approval")
|
|
if trust_request.get("activation_authorized") is not False:
|
|
raise PolicyApprovalError("policy trust request must not authorize activation")
|
|
requested_at = _required_text(trust_request, "requested_at")
|
|
expected = build_trust_request(
|
|
approval,
|
|
candidate_keyring=candidate_keyring,
|
|
policy_dir=policy_dir,
|
|
requested_at=requested_at,
|
|
)
|
|
if canonical_json(trust_request) != canonical_json(expected):
|
|
raise PolicyApprovalError("policy trust request does not match its signed source material")
|
|
checked = parse_timestamp(checked_at, label="checked_at")
|
|
if checked < parse_timestamp(requested_at, label="requested_at"):
|
|
raise PolicyApprovalError("policy trust request cannot be checked before it was requested")
|
|
verify_approval(
|
|
approval,
|
|
keyring=candidate_keyring,
|
|
policy_dir=policy_dir,
|
|
checked_at=checked.isoformat(),
|
|
allow_candidate_keyring=True,
|
|
)
|
|
return expected
|
|
|
|
|
|
def build_trust_approval_payload(
|
|
trust_request: dict[str, Any],
|
|
*,
|
|
approval: dict[str, Any],
|
|
candidate_keyring: dict[str, Any],
|
|
policy_dir: Path,
|
|
trust_approver_public_key_file: Path,
|
|
key_id: str,
|
|
approver_id: str,
|
|
reviewer: str,
|
|
role: str,
|
|
organization: str,
|
|
responsibility: str,
|
|
approved_at: str,
|
|
expires_at: str,
|
|
key_not_before: str,
|
|
key_not_after: str,
|
|
) -> dict[str, Any]:
|
|
if not KEY_ID_PATTERN.fullmatch(key_id):
|
|
raise PolicyApprovalError("trust approver key_id format is invalid")
|
|
approved_at = normalized_timestamp(approved_at, label="approved_at")
|
|
expires_at = normalized_timestamp(expires_at, label="expires_at")
|
|
key_not_before = normalized_timestamp(key_not_before, label="key_not_before")
|
|
key_not_after = normalized_timestamp(key_not_after, label="key_not_after")
|
|
approved = parse_timestamp(approved_at, label="approved_at")
|
|
expires = parse_timestamp(expires_at, label="expires_at")
|
|
if expires <= approved or expires - approved > MAX_APPROVAL_WINDOW:
|
|
raise PolicyApprovalError("trust approval validity must be greater than zero and no longer than seven days")
|
|
if approved < parse_timestamp(key_not_before, label="key_not_before") or expires > parse_timestamp(
|
|
key_not_after, label="key_not_after"
|
|
):
|
|
raise PolicyApprovalError("trust approval window must remain within the trust key validity window")
|
|
request = validate_trust_request(
|
|
trust_request,
|
|
approval=approval,
|
|
candidate_keyring=candidate_keyring,
|
|
policy_dir=policy_dir,
|
|
checked_at=approved_at,
|
|
)
|
|
legal_expiry = parse_timestamp(_required_text(request, "approval_expires_at"), label="approval_expires_at")
|
|
if expires > legal_expiry:
|
|
raise PolicyApprovalError("trust approval cannot outlive the legal policy approval")
|
|
_, public_pem, public_raw = load_public_key(trust_approver_public_key_file)
|
|
identity = {
|
|
"approver_id": _nonempty(approver_id, "trust approver_id"),
|
|
"reviewer": _nonempty(reviewer, "trust reviewer"),
|
|
"role": _nonempty(role, "trust role"),
|
|
"organization": _nonempty(organization, "trust organization"),
|
|
}
|
|
_require_sole_policy_reviewer(identity, label="policy trust confirmation")
|
|
responsibility = _nonempty(responsibility, "trust responsibility").lower()
|
|
if responsibility not in {"security", "operations"}:
|
|
raise PolicyApprovalError("trust responsibility must be security or operations")
|
|
legal_key = request.get("key")
|
|
if not isinstance(legal_key, dict):
|
|
raise PolicyApprovalError("policy trust request legal key binding is invalid")
|
|
_require_sole_policy_reviewer(legal_key, label="policy trust request")
|
|
public_raw_sha256 = sha256_bytes(public_raw)
|
|
for field in ("approver_id", "reviewer", "role", "organization"):
|
|
if identity[field] != legal_key.get(field):
|
|
raise PolicyApprovalError(
|
|
f"trust confirmation must use the same named reviewer: {field}"
|
|
)
|
|
if public_raw_sha256 == legal_key.get("public_key_raw_sha256"):
|
|
raise PolicyApprovalError("trust approver key must differ from the legal approval key")
|
|
return {
|
|
"schema_version": TRUST_APPROVAL_PAYLOAD_SCHEMA,
|
|
"status": "pending_single_reviewer_trust_signature",
|
|
"trust_request_sha256": sha256_bytes(canonical_json(request)),
|
|
"policy_approval_id": request["approval_id"],
|
|
"policy_approval_sha256": request["approval_sha256"],
|
|
"candidate_keyring_sha256": request["candidate_keyring_sha256"],
|
|
"policy_version": request["policy_version"],
|
|
"effective_at": request["effective_at"],
|
|
"documents": request["documents"],
|
|
"legal_approver": {
|
|
field: legal_key[field]
|
|
for field in ("approver_id", "reviewer", "role", "organization", "key_id", "public_key_raw_sha256")
|
|
},
|
|
"trust_approver": {
|
|
**identity,
|
|
"key_id": key_id,
|
|
"algorithm": "Ed25519",
|
|
"public_key_file_sha256": sha256_bytes(public_pem),
|
|
"public_key_raw_sha256": public_raw_sha256,
|
|
"key_not_before": key_not_before,
|
|
"key_not_after": key_not_after,
|
|
},
|
|
"decision": {
|
|
"decision": "approve_policy_signing_key_trust",
|
|
"responsibility": responsibility,
|
|
"attestations": TRUST_REVIEW_ATTESTATIONS,
|
|
"approved_at": approved_at,
|
|
"expires_at": expires_at,
|
|
"confirmation": TRUST_SIGN_CONFIRMATION,
|
|
"policy_activation_authorized": False,
|
|
},
|
|
}
|
|
|
|
|
|
def sign_trust_approval_payload(
|
|
payload: dict[str, Any],
|
|
*,
|
|
private_key_file: Path,
|
|
password_file: Path,
|
|
confirmation: str,
|
|
) -> dict[str, Any]:
|
|
if confirmation != TRUST_SIGN_CONFIRMATION:
|
|
raise PolicyApprovalError(f"trust signing requires confirmation {TRUST_SIGN_CONFIRMATION}")
|
|
if payload.get("schema_version") != TRUST_APPROVAL_PAYLOAD_SCHEMA:
|
|
raise PolicyApprovalError("trust approval payload schema_version is invalid")
|
|
if payload.get("status") != "pending_single_reviewer_trust_signature":
|
|
raise PolicyApprovalError("only a pending trust approval payload can be signed")
|
|
decision = payload.get("decision")
|
|
if not isinstance(decision, dict) or decision.get("policy_activation_authorized") is not False:
|
|
raise PolicyApprovalError("trust approval payload must not authorize policy activation")
|
|
private_raw = read_bounded(private_key_file, label="trust approver private key", max_bytes=64 * 1024)
|
|
password = read_bounded(password_file, label="trust approver private key password", max_bytes=4096).strip()
|
|
if not password:
|
|
raise PolicyApprovalError("trust approver private key password is empty")
|
|
try:
|
|
key = serialization.load_pem_private_key(private_raw, password=password)
|
|
except (TypeError, ValueError) as exc:
|
|
raise PolicyApprovalError("trust approver private key or password is invalid") from exc
|
|
if not isinstance(key, Ed25519PrivateKey):
|
|
raise PolicyApprovalError("trust approver private key must be Ed25519")
|
|
public_raw = key.public_key().public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
|
|
trust_approver = payload.get("trust_approver")
|
|
if not isinstance(trust_approver, dict) or sha256_bytes(public_raw) != trust_approver.get(
|
|
"public_key_raw_sha256"
|
|
):
|
|
raise PolicyApprovalError("trust approver private key does not match the payload binding")
|
|
payload_raw = canonical_json(payload)
|
|
signature = key.sign(TRUST_SIGNATURE_DOMAIN + payload_raw)
|
|
approval_id = f"policy-key-trust-approval-{sha256_bytes(payload_raw + signature)[:24]}"
|
|
return {
|
|
"schema_version": TRUST_APPROVAL_SCHEMA,
|
|
"trust_approval_id": approval_id,
|
|
"payload": payload,
|
|
"signature": base64.b64encode(signature).decode("ascii"),
|
|
}
|
|
|
|
|
|
def issue_trusted_keyring(
|
|
trust_approval: dict[str, Any],
|
|
*,
|
|
trust_request: dict[str, Any],
|
|
approval: dict[str, Any],
|
|
candidate_keyring: dict[str, Any],
|
|
policy_dir: Path,
|
|
trust_approver_public_key_file: Path,
|
|
checked_at: str,
|
|
) -> dict[str, Any]:
|
|
if trust_approval.get("schema_version") != TRUST_APPROVAL_SCHEMA:
|
|
raise PolicyApprovalError("policy key trust approval schema_version is invalid")
|
|
payload = trust_approval.get("payload")
|
|
if not isinstance(payload, dict) or payload.get("schema_version") != TRUST_APPROVAL_PAYLOAD_SCHEMA:
|
|
raise PolicyApprovalError("signed policy key trust approval payload is invalid")
|
|
checked_at = normalized_timestamp(checked_at, label="checked_at")
|
|
request = validate_trust_request(
|
|
trust_request,
|
|
approval=approval,
|
|
candidate_keyring=candidate_keyring,
|
|
policy_dir=policy_dir,
|
|
checked_at=checked_at,
|
|
)
|
|
expected_bindings = {
|
|
"trust_request_sha256": sha256_bytes(canonical_json(request)),
|
|
"policy_approval_id": request["approval_id"],
|
|
"policy_approval_sha256": request["approval_sha256"],
|
|
"candidate_keyring_sha256": request["candidate_keyring_sha256"],
|
|
"policy_version": request["policy_version"],
|
|
"effective_at": request["effective_at"],
|
|
"documents": request["documents"],
|
|
}
|
|
for field, expected in expected_bindings.items():
|
|
if payload.get(field) != expected:
|
|
raise PolicyApprovalError(f"trust approval binding mismatch: {field}")
|
|
decision = payload.get("decision")
|
|
if not isinstance(decision, dict) or decision.get("decision") != "approve_policy_signing_key_trust":
|
|
raise PolicyApprovalError("policy key trust decision is invalid")
|
|
if decision.get("responsibility") not in {"security", "operations"}:
|
|
raise PolicyApprovalError("policy key trust responsibility is invalid")
|
|
if decision.get("attestations") != TRUST_REVIEW_ATTESTATIONS:
|
|
raise PolicyApprovalError("policy key trust attestations are incomplete")
|
|
if decision.get("policy_activation_authorized") is not False:
|
|
raise PolicyApprovalError("policy key trust approval must not authorize policy activation")
|
|
checked = parse_timestamp(checked_at, label="checked_at")
|
|
approved = parse_timestamp(_required_text(decision, "approved_at"), label="approved_at")
|
|
expires = parse_timestamp(_required_text(decision, "expires_at"), label="expires_at")
|
|
if checked < approved or checked > expires:
|
|
raise PolicyApprovalError("policy key trust approval is not valid at checked_at")
|
|
trust_approver = payload.get("trust_approver")
|
|
legal_approver = payload.get("legal_approver")
|
|
if not isinstance(trust_approver, dict) or not isinstance(legal_approver, dict):
|
|
raise PolicyApprovalError("policy key trust approver bindings are invalid")
|
|
expected_payload = build_trust_approval_payload(
|
|
trust_request,
|
|
approval=approval,
|
|
candidate_keyring=candidate_keyring,
|
|
policy_dir=policy_dir,
|
|
trust_approver_public_key_file=trust_approver_public_key_file,
|
|
key_id=_required_text(trust_approver, "key_id"),
|
|
approver_id=_required_text(trust_approver, "approver_id"),
|
|
reviewer=_required_text(trust_approver, "reviewer"),
|
|
role=_required_text(trust_approver, "role"),
|
|
organization=_required_text(trust_approver, "organization"),
|
|
responsibility=_required_text(decision, "responsibility"),
|
|
approved_at=_required_text(decision, "approved_at"),
|
|
expires_at=_required_text(decision, "expires_at"),
|
|
key_not_before=_required_text(trust_approver, "key_not_before"),
|
|
key_not_after=_required_text(trust_approver, "key_not_after"),
|
|
)
|
|
if canonical_json(payload) != canonical_json(expected_payload):
|
|
raise PolicyApprovalError("policy key trust approval payload does not match its source material")
|
|
public_key, public_pem, public_raw = load_public_key(trust_approver_public_key_file)
|
|
if sha256_bytes(public_pem) != trust_approver.get("public_key_file_sha256") or sha256_bytes(
|
|
public_raw
|
|
) != trust_approver.get("public_key_raw_sha256"):
|
|
raise PolicyApprovalError("trust approver public key does not match the signed payload")
|
|
if sha256_bytes(public_raw) == legal_approver.get("public_key_raw_sha256"):
|
|
raise PolicyApprovalError("trust approver key must differ from the legal approval key")
|
|
try:
|
|
signature = base64.b64decode(_required_text(trust_approval, "signature"), validate=True)
|
|
public_key.verify(signature, TRUST_SIGNATURE_DOMAIN + canonical_json(payload))
|
|
except Exception as exc:
|
|
raise PolicyApprovalError("policy key trust approval signature verification failed") from exc
|
|
expected_id = f"policy-key-trust-approval-{sha256_bytes(canonical_json(payload) + signature)[:24]}"
|
|
if trust_approval.get("trust_approval_id") != expected_id:
|
|
raise PolicyApprovalError("policy key trust approval_id does not match its signed material")
|
|
return {
|
|
**candidate_keyring,
|
|
"status": "trusted",
|
|
"trust_basis": {
|
|
"trust_approval_id": expected_id,
|
|
"trust_approval_sha256": sha256_bytes(canonical_json(trust_approval)),
|
|
"trust_request_sha256": expected_bindings["trust_request_sha256"],
|
|
"trusted_at": checked_at,
|
|
"trust_approval": trust_approval,
|
|
"trust_approver_public_key_pem": public_pem.decode("ascii"),
|
|
"trust_approver": {
|
|
field: trust_approver[field]
|
|
for field in (
|
|
"approver_id",
|
|
"reviewer",
|
|
"role",
|
|
"organization",
|
|
"key_id",
|
|
"public_key_file_sha256",
|
|
"public_key_raw_sha256",
|
|
)
|
|
},
|
|
"policy_activation_authorized": False,
|
|
},
|
|
}
|
|
|
|
|
|
def _required_text(value: dict[str, Any], key: str) -> str:
|
|
return _nonempty(value.get(key), key)
|
|
|
|
|
|
def _require_sole_policy_reviewer(value: dict[str, Any], *, label: str) -> dict[str, str]:
|
|
identity = {
|
|
field: _required_text(value, field)
|
|
for field in ("approver_id", "reviewer", "role", "organization")
|
|
}
|
|
if identity != SOLE_POLICY_REVIEWER:
|
|
raise PolicyApprovalError(
|
|
f"{label} must use the configured sole named reviewer"
|
|
)
|
|
return identity
|
|
|
|
|
|
def _nonempty(value: Any, label: str) -> str:
|
|
if not isinstance(value, str) or not value.strip() or value != value.strip():
|
|
raise PolicyApprovalError(f"{label} must be a trimmed non-empty string")
|
|
return value
|
|
|
|
|
|
def _load_json_file(path: Path, *, label: str) -> dict[str, Any]:
|
|
return strict_json(read_bounded(path, label=label, max_bytes=MAX_JSON_BYTES), label=label)
|
|
|
|
|
|
def _prepare_command(args: argparse.Namespace) -> dict[str, Any]:
|
|
package, payload, keyring = build_review_package(
|
|
policy_dir=args.policy_dir,
|
|
public_key_file=args.public_key_file,
|
|
key_id=args.key_id,
|
|
approver_id=args.approver_id,
|
|
reviewer=args.reviewer,
|
|
role=args.role,
|
|
organization=args.organization,
|
|
prepared_at=args.prepared_at or datetime.now(timezone.utc).astimezone().isoformat(),
|
|
key_not_before=args.key_not_before,
|
|
key_not_after=args.key_not_after,
|
|
)
|
|
package_raw = canonical_json(package)
|
|
payload_raw = canonical_json(payload)
|
|
keyring_raw = canonical_json(keyring)
|
|
write_atomic(args.review_package_output, package_raw)
|
|
write_atomic(args.payload_output, payload_raw)
|
|
write_atomic(args.keyring_output, keyring_raw)
|
|
return {
|
|
"status": "prepared_not_signed",
|
|
"review_package_sha256": sha256_bytes(package_raw),
|
|
"payload_sha256": sha256_bytes(payload_raw),
|
|
"keyring_sha256": sha256_bytes(keyring_raw),
|
|
"documents": package["documents"],
|
|
}
|
|
|
|
|
|
def _finalize_command(args: argparse.Namespace) -> dict[str, Any]:
|
|
pending = _load_json_file(args.pending_payload_file, label="pending policy payload")
|
|
payload = finalize_payload(pending, approved_at=args.approved_at, expires_at=args.expires_at)
|
|
raw = canonical_json(payload)
|
|
write_atomic(args.output, raw)
|
|
return {"status": "finalized_not_signed", "payload_sha256": sha256_bytes(raw)}
|
|
|
|
|
|
def _sign_command(args: argparse.Namespace) -> dict[str, Any]:
|
|
payload = _load_json_file(args.payload_file, label="final policy payload")
|
|
approval = sign_payload(
|
|
payload,
|
|
private_key_file=args.private_key_file,
|
|
password_file=args.private_key_password_file,
|
|
confirmation=args.confirmation,
|
|
)
|
|
raw = canonical_json(approval)
|
|
write_atomic(args.output, raw, private=True)
|
|
return {
|
|
"status": "signed",
|
|
"approval_id": approval["approval_id"],
|
|
"approval_sha256": sha256_bytes(raw),
|
|
}
|
|
|
|
|
|
def _verify_command(args: argparse.Namespace) -> dict[str, Any]:
|
|
approval = _load_json_file(args.approval_file, label="policy approval")
|
|
keyring = _load_json_file(args.keyring_file, label="policy keyring")
|
|
return verify_approval(
|
|
approval,
|
|
keyring=keyring,
|
|
policy_dir=args.policy_dir,
|
|
checked_at=args.checked_at or datetime.now(timezone.utc).astimezone().isoformat(),
|
|
allow_candidate_keyring=args.allow_candidate_keyring,
|
|
)
|
|
|
|
|
|
def _prepare_trust_request_command(args: argparse.Namespace) -> dict[str, Any]:
|
|
approval = _load_json_file(args.approval_file, label="policy approval")
|
|
keyring = _load_json_file(args.candidate_keyring_file, label="candidate policy keyring")
|
|
request = build_trust_request(
|
|
approval,
|
|
candidate_keyring=keyring,
|
|
policy_dir=args.policy_dir,
|
|
requested_at=args.requested_at or datetime.now(timezone.utc).astimezone().isoformat(),
|
|
)
|
|
raw = canonical_json(request)
|
|
write_atomic(args.output, raw)
|
|
return {
|
|
"status": request["status"],
|
|
"trust_request_sha256": sha256_bytes(raw),
|
|
"approval_id": request["approval_id"],
|
|
"activation_authorized": False,
|
|
}
|
|
|
|
|
|
def _prepare_trust_approval_command(args: argparse.Namespace) -> dict[str, Any]:
|
|
trust_request = _load_json_file(args.trust_request_file, label="policy trust request")
|
|
approval = _load_json_file(args.approval_file, label="policy approval")
|
|
keyring = _load_json_file(args.candidate_keyring_file, label="candidate policy keyring")
|
|
payload = build_trust_approval_payload(
|
|
trust_request,
|
|
approval=approval,
|
|
candidate_keyring=keyring,
|
|
policy_dir=args.policy_dir,
|
|
trust_approver_public_key_file=args.trust_approver_public_key_file,
|
|
key_id=args.key_id,
|
|
approver_id=args.approver_id,
|
|
reviewer=args.reviewer,
|
|
role=args.role,
|
|
organization=args.organization,
|
|
responsibility=args.responsibility,
|
|
approved_at=args.approved_at,
|
|
expires_at=args.expires_at,
|
|
key_not_before=args.key_not_before,
|
|
key_not_after=args.key_not_after,
|
|
)
|
|
raw = canonical_json(payload)
|
|
write_atomic(args.output, raw)
|
|
return {
|
|
"status": payload["status"],
|
|
"trust_approval_payload_sha256": sha256_bytes(raw),
|
|
"activation_authorized": False,
|
|
}
|
|
|
|
|
|
def _sign_trust_approval_command(args: argparse.Namespace) -> dict[str, Any]:
|
|
payload = _load_json_file(args.payload_file, label="policy trust approval payload")
|
|
trust_approval = sign_trust_approval_payload(
|
|
payload,
|
|
private_key_file=args.private_key_file,
|
|
password_file=args.private_key_password_file,
|
|
confirmation=args.confirmation,
|
|
)
|
|
raw = canonical_json(trust_approval)
|
|
write_atomic(args.output, raw, private=True)
|
|
return {
|
|
"status": "signed_single_reviewer_trust_approval",
|
|
"trust_approval_id": trust_approval["trust_approval_id"],
|
|
"trust_approval_sha256": sha256_bytes(raw),
|
|
"activation_authorized": False,
|
|
}
|
|
|
|
|
|
def _issue_trusted_keyring_command(args: argparse.Namespace) -> dict[str, Any]:
|
|
trust_approval = _load_json_file(args.trust_approval_file, label="policy key trust approval")
|
|
trust_request = _load_json_file(args.trust_request_file, label="policy trust request")
|
|
approval = _load_json_file(args.approval_file, label="policy approval")
|
|
keyring = _load_json_file(args.candidate_keyring_file, label="candidate policy keyring")
|
|
trusted = issue_trusted_keyring(
|
|
trust_approval,
|
|
trust_request=trust_request,
|
|
approval=approval,
|
|
candidate_keyring=keyring,
|
|
policy_dir=args.policy_dir,
|
|
trust_approver_public_key_file=args.trust_approver_public_key_file,
|
|
checked_at=args.checked_at or datetime.now(timezone.utc).astimezone().isoformat(),
|
|
)
|
|
raw = canonical_json(trusted)
|
|
write_atomic(args.output, raw)
|
|
return {
|
|
"status": "trusted_keyring_issued_policy_not_activated",
|
|
"trusted_keyring_sha256": sha256_bytes(raw),
|
|
"trust_approval_id": trusted["trust_basis"]["trust_approval_id"],
|
|
"activation_authorized": False,
|
|
}
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
commands = parser.add_subparsers(dest="command", required=True)
|
|
|
|
prepare = commands.add_parser("prepare", help="create unsigned policy review material")
|
|
prepare.add_argument("--policy-dir", type=Path, required=True)
|
|
prepare.add_argument("--public-key-file", type=Path, required=True)
|
|
prepare.add_argument("--key-id", required=True)
|
|
prepare.add_argument("--approver-id", required=True)
|
|
prepare.add_argument("--reviewer", required=True)
|
|
prepare.add_argument("--role", required=True)
|
|
prepare.add_argument("--organization", required=True)
|
|
prepare.add_argument("--prepared-at")
|
|
prepare.add_argument("--key-not-before", required=True)
|
|
prepare.add_argument("--key-not-after", required=True)
|
|
prepare.add_argument("--review-package-output", type=Path, required=True)
|
|
prepare.add_argument("--payload-output", type=Path, required=True)
|
|
prepare.add_argument("--keyring-output", type=Path, required=True)
|
|
|
|
finalize = commands.add_parser("finalize-payload", help="bind approval timestamps after review")
|
|
finalize.add_argument("--pending-payload-file", type=Path, required=True)
|
|
finalize.add_argument("--approved-at", required=True)
|
|
finalize.add_argument("--expires-at", required=True)
|
|
finalize.add_argument("--output", type=Path, required=True)
|
|
|
|
sign = commands.add_parser("sign", help="sign a finalized payload with the reviewer-owned key")
|
|
sign.add_argument("--payload-file", type=Path, required=True)
|
|
sign.add_argument("--private-key-file", type=Path, required=True)
|
|
sign.add_argument("--private-key-password-file", type=Path, required=True)
|
|
sign.add_argument("--confirmation", required=True)
|
|
sign.add_argument("--output", type=Path, required=True)
|
|
|
|
verify = commands.add_parser("verify", help="verify signature, key identity, and policy bytes")
|
|
verify.add_argument("--approval-file", type=Path, required=True)
|
|
verify.add_argument("--keyring-file", type=Path, required=True)
|
|
verify.add_argument("--policy-dir", type=Path, required=True)
|
|
verify.add_argument("--checked-at")
|
|
verify.add_argument("--allow-candidate-keyring", action="store_true")
|
|
|
|
trust = commands.add_parser(
|
|
"prepare-trust-request",
|
|
help="bind a verified signed approval to a candidate keyring without granting trust",
|
|
)
|
|
trust.add_argument("--approval-file", type=Path, required=True)
|
|
trust.add_argument("--candidate-keyring-file", type=Path, required=True)
|
|
trust.add_argument("--policy-dir", type=Path, required=True)
|
|
trust.add_argument("--requested-at")
|
|
trust.add_argument("--output", type=Path, required=True)
|
|
|
|
trust_prepare = commands.add_parser(
|
|
"prepare-trust-approval",
|
|
help="create the exact same-reviewer, separate-key trust confirmation payload",
|
|
)
|
|
trust_prepare.add_argument("--trust-request-file", type=Path, required=True)
|
|
trust_prepare.add_argument("--approval-file", type=Path, required=True)
|
|
trust_prepare.add_argument("--candidate-keyring-file", type=Path, required=True)
|
|
trust_prepare.add_argument("--policy-dir", type=Path, required=True)
|
|
trust_prepare.add_argument("--trust-approver-public-key-file", type=Path, required=True)
|
|
trust_prepare.add_argument("--key-id", required=True)
|
|
trust_prepare.add_argument("--approver-id", required=True)
|
|
trust_prepare.add_argument("--reviewer", required=True)
|
|
trust_prepare.add_argument("--role", required=True)
|
|
trust_prepare.add_argument("--organization", required=True)
|
|
trust_prepare.add_argument("--responsibility", choices=("security", "operations"), required=True)
|
|
trust_prepare.add_argument("--approved-at", required=True)
|
|
trust_prepare.add_argument("--expires-at", required=True)
|
|
trust_prepare.add_argument("--key-not-before", required=True)
|
|
trust_prepare.add_argument("--key-not-after", required=True)
|
|
trust_prepare.add_argument("--output", type=Path, required=True)
|
|
|
|
trust_sign = commands.add_parser(
|
|
"sign-trust-approval",
|
|
help="sign an exact trust confirmation payload with the reviewer's separate trust key",
|
|
)
|
|
trust_sign.add_argument("--payload-file", type=Path, required=True)
|
|
trust_sign.add_argument("--private-key-file", type=Path, required=True)
|
|
trust_sign.add_argument("--private-key-password-file", type=Path, required=True)
|
|
trust_sign.add_argument("--confirmation", required=True)
|
|
trust_sign.add_argument("--output", type=Path, required=True)
|
|
|
|
trust_issue = commands.add_parser(
|
|
"issue-trusted-keyring",
|
|
help="verify both approvals and issue a trusted keyring without activating policies",
|
|
)
|
|
trust_issue.add_argument("--trust-approval-file", type=Path, required=True)
|
|
trust_issue.add_argument("--trust-request-file", type=Path, required=True)
|
|
trust_issue.add_argument("--approval-file", type=Path, required=True)
|
|
trust_issue.add_argument("--candidate-keyring-file", type=Path, required=True)
|
|
trust_issue.add_argument("--policy-dir", type=Path, required=True)
|
|
trust_issue.add_argument("--trust-approver-public-key-file", type=Path, required=True)
|
|
trust_issue.add_argument("--checked-at")
|
|
trust_issue.add_argument("--output", type=Path, required=True)
|
|
return parser.parse_args()
|
|
|
|
|
|
def main() -> int:
|
|
args = parse_args()
|
|
try:
|
|
if args.command == "prepare":
|
|
result = _prepare_command(args)
|
|
elif args.command == "finalize-payload":
|
|
result = _finalize_command(args)
|
|
elif args.command == "sign":
|
|
result = _sign_command(args)
|
|
elif args.command == "prepare-trust-request":
|
|
result = _prepare_trust_request_command(args)
|
|
elif args.command == "prepare-trust-approval":
|
|
result = _prepare_trust_approval_command(args)
|
|
elif args.command == "sign-trust-approval":
|
|
result = _sign_trust_approval_command(args)
|
|
elif args.command == "issue-trusted-keyring":
|
|
result = _issue_trusted_keyring_command(args)
|
|
else:
|
|
result = _verify_command(args)
|
|
except (OSError, PolicyApprovalError) as exc:
|
|
print(f"POLICY_LEGAL_APPROVAL_FAIL error={exc}", file=sys.stderr)
|
|
return 2
|
|
print(json.dumps(result, ensure_ascii=False, sort_keys=True))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|