proof-aware-crypto-tooling-.../src/pacta/signing.py

169 lines
6.9 KiB
Python
Raw Normal View History

2026-07-03 11:03:58 +00:00
from __future__ import annotations
import base64
import hashlib
import json
import shutil
import subprocess
import tempfile
from pathlib import Path
from typing import Any
class SignatureError(RuntimeError):
pass
def canonical_attestation_payload(attestation: dict[str, Any]) -> bytes:
unsigned = {key: value for key, value in attestation.items() if key != "signature"}
2026-07-03 12:09:34 +00:00
return canonical_json(unsigned)
def canonical_json(document: Any) -> bytes:
return json.dumps(document, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
2026-07-03 11:03:58 +00:00
def payload_digest(attestation: dict[str, Any]) -> str:
return hashlib.sha256(canonical_attestation_payload(attestation)).hexdigest()
def public_key_fingerprint(public_key_path: str | Path) -> str:
data = Path(public_key_path).read_bytes()
return hashlib.sha256(data).hexdigest()
def generate_ed25519_keypair(private_key_path: str | Path, public_key_path: str | Path) -> None:
openssl = _openssl()
private_path = Path(private_key_path)
public_path = Path(public_key_path)
private_path.parent.mkdir(parents=True, exist_ok=True)
public_path.parent.mkdir(parents=True, exist_ok=True)
subprocess.run([openssl, "genpkey", "-algorithm", "ed25519", "-out", str(private_path)], check=True, timeout=30)
subprocess.run([openssl, "pkey", "-in", str(private_path), "-pubout", "-out", str(public_path)], check=True, timeout=30)
def sign_attestation(attestation: dict[str, Any], private_key_path: str | Path, public_key_path: str | Path | None = None) -> dict[str, Any]:
payload = canonical_attestation_payload(attestation)
2026-07-03 12:09:34 +00:00
signature_base64 = sign_payload_ed25519(payload, private_key_path)
2026-07-03 11:03:58 +00:00
signed = dict(attestation)
signed["signature"] = {
"scheme": "openssl-ed25519",
"status": "signed",
"payload_digest_sha256": hashlib.sha256(payload).hexdigest(),
2026-07-03 12:09:34 +00:00
"signature_base64": signature_base64,
2026-07-03 11:03:58 +00:00
}
if public_key_path:
signed["signature"]["public_key_fingerprint_sha256"] = public_key_fingerprint(public_key_path)
return signed
def verify_attestation_signature(attestation: dict[str, Any], public_key_path: str | Path) -> tuple[bool, str | None]:
Dogfood cryptography: pacta verifies signatures through the PROVEN code path "Eat your own dogfood": pacta consumes certificates about a verified Ed25519 implementation while checking those certificates' signatures with OpenSSL. Now it can use the object of its own evidence: - dogfood/pacta-verified-verify: a ~90-line Rust binary built against the PINNED proven source workspace (saymrwulf/curve25519-dalek-source at the exact commit the dalek certificates pin - the build records it: aa0f6ab...) with the serial backend pinned via RUSTFLAGS exactly as the verified extraction pins it. Cargo.toml is committed as a template ({{SOURCE}} placeholder) so no machine path is hardcoded; the rendered file, target/, and the built binary are gitignored. - pacta dogfood-build --source <workspace>: renders, builds, installs to dogfood/state/, and writes a provenance sidecar (source commit, backend cfg, rustc, and an honest coverage note: the certificates cover verify_sha512, the extraction-refactored image of this verify path; SHA-512 and the wire glue remain the theorems' documented boundary). pacta dogfood-status reports the active backend. - signing.verify_payload_ed25519_detailed: dispatch - the dogfood binary when present (backend "verified-dalek-serial"), OpenSSL fallback otherwise, and the backend that ACTUALLY ran is recorded in receipt signature statuses and attestation evidence. Fallback is never silent. - --require-verified-verifier (receipt-verify + agent): policy fails closed when verification did not run on the certificate-covered path. - ML-DSA is deliberately unchanged: no proven implementation exists, so the slot stays fail-closed "unavailable" - the honest hybrid-PQC posture is one proven-classical signature plus one required-but- unproven PQC slot, never a pretend backend. Validated live: receipt verification through the proven verifier (backend recorded), a corrupted signature bit rejected BY the proven binary, tampered attestations rejected, and the policy failing closed when the binary is absent. 49/49 tests green (incl. PEM-SPKI raw-key cross-check against openssl, dispatch/backend recording with a stub, and a real-binary roundtrip that skips gracefully where unbuilt). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 08:13:48 +00:00
ok, error, _backend = verify_attestation_signature_detailed(attestation, public_key_path)
return ok, error
def verify_attestation_signature_detailed(attestation: dict[str, Any], public_key_path: str | Path) -> tuple[bool, str | None, str]:
2026-07-03 11:03:58 +00:00
signature = attestation.get("signature") or {}
if signature.get("scheme") != "openssl-ed25519":
Dogfood cryptography: pacta verifies signatures through the PROVEN code path "Eat your own dogfood": pacta consumes certificates about a verified Ed25519 implementation while checking those certificates' signatures with OpenSSL. Now it can use the object of its own evidence: - dogfood/pacta-verified-verify: a ~90-line Rust binary built against the PINNED proven source workspace (saymrwulf/curve25519-dalek-source at the exact commit the dalek certificates pin - the build records it: aa0f6ab...) with the serial backend pinned via RUSTFLAGS exactly as the verified extraction pins it. Cargo.toml is committed as a template ({{SOURCE}} placeholder) so no machine path is hardcoded; the rendered file, target/, and the built binary are gitignored. - pacta dogfood-build --source <workspace>: renders, builds, installs to dogfood/state/, and writes a provenance sidecar (source commit, backend cfg, rustc, and an honest coverage note: the certificates cover verify_sha512, the extraction-refactored image of this verify path; SHA-512 and the wire glue remain the theorems' documented boundary). pacta dogfood-status reports the active backend. - signing.verify_payload_ed25519_detailed: dispatch - the dogfood binary when present (backend "verified-dalek-serial"), OpenSSL fallback otherwise, and the backend that ACTUALLY ran is recorded in receipt signature statuses and attestation evidence. Fallback is never silent. - --require-verified-verifier (receipt-verify + agent): policy fails closed when verification did not run on the certificate-covered path. - ML-DSA is deliberately unchanged: no proven implementation exists, so the slot stays fail-closed "unavailable" - the honest hybrid-PQC posture is one proven-classical signature plus one required-but- unproven PQC slot, never a pretend backend. Validated live: receipt verification through the proven verifier (backend recorded), a corrupted signature bit rejected BY the proven binary, tampered attestations rejected, and the policy failing closed when the binary is absent. 49/49 tests green (incl. PEM-SPKI raw-key cross-check against openssl, dispatch/backend recording with a stub, and a real-binary roundtrip that skips gracefully where unbuilt). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 08:13:48 +00:00
return False, f"Unsupported attestation signature scheme: {signature.get('scheme')}", "none"
2026-07-03 11:03:58 +00:00
encoded = signature.get("signature_base64")
if not encoded:
Dogfood cryptography: pacta verifies signatures through the PROVEN code path "Eat your own dogfood": pacta consumes certificates about a verified Ed25519 implementation while checking those certificates' signatures with OpenSSL. Now it can use the object of its own evidence: - dogfood/pacta-verified-verify: a ~90-line Rust binary built against the PINNED proven source workspace (saymrwulf/curve25519-dalek-source at the exact commit the dalek certificates pin - the build records it: aa0f6ab...) with the serial backend pinned via RUSTFLAGS exactly as the verified extraction pins it. Cargo.toml is committed as a template ({{SOURCE}} placeholder) so no machine path is hardcoded; the rendered file, target/, and the built binary are gitignored. - pacta dogfood-build --source <workspace>: renders, builds, installs to dogfood/state/, and writes a provenance sidecar (source commit, backend cfg, rustc, and an honest coverage note: the certificates cover verify_sha512, the extraction-refactored image of this verify path; SHA-512 and the wire glue remain the theorems' documented boundary). pacta dogfood-status reports the active backend. - signing.verify_payload_ed25519_detailed: dispatch - the dogfood binary when present (backend "verified-dalek-serial"), OpenSSL fallback otherwise, and the backend that ACTUALLY ran is recorded in receipt signature statuses and attestation evidence. Fallback is never silent. - --require-verified-verifier (receipt-verify + agent): policy fails closed when verification did not run on the certificate-covered path. - ML-DSA is deliberately unchanged: no proven implementation exists, so the slot stays fail-closed "unavailable" - the honest hybrid-PQC posture is one proven-classical signature plus one required-but- unproven PQC slot, never a pretend backend. Validated live: receipt verification through the proven verifier (backend recorded), a corrupted signature bit rejected BY the proven binary, tampered attestations rejected, and the policy failing closed when the binary is absent. 49/49 tests green (incl. PEM-SPKI raw-key cross-check against openssl, dispatch/backend recording with a stub, and a real-binary roundtrip that skips gracefully where unbuilt). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 08:13:48 +00:00
return False, "Attestation signature is missing signature_base64.", "none"
2026-07-03 11:03:58 +00:00
expected_digest = signature.get("payload_digest_sha256")
actual_digest = payload_digest(attestation)
if expected_digest and expected_digest != actual_digest:
Dogfood cryptography: pacta verifies signatures through the PROVEN code path "Eat your own dogfood": pacta consumes certificates about a verified Ed25519 implementation while checking those certificates' signatures with OpenSSL. Now it can use the object of its own evidence: - dogfood/pacta-verified-verify: a ~90-line Rust binary built against the PINNED proven source workspace (saymrwulf/curve25519-dalek-source at the exact commit the dalek certificates pin - the build records it: aa0f6ab...) with the serial backend pinned via RUSTFLAGS exactly as the verified extraction pins it. Cargo.toml is committed as a template ({{SOURCE}} placeholder) so no machine path is hardcoded; the rendered file, target/, and the built binary are gitignored. - pacta dogfood-build --source <workspace>: renders, builds, installs to dogfood/state/, and writes a provenance sidecar (source commit, backend cfg, rustc, and an honest coverage note: the certificates cover verify_sha512, the extraction-refactored image of this verify path; SHA-512 and the wire glue remain the theorems' documented boundary). pacta dogfood-status reports the active backend. - signing.verify_payload_ed25519_detailed: dispatch - the dogfood binary when present (backend "verified-dalek-serial"), OpenSSL fallback otherwise, and the backend that ACTUALLY ran is recorded in receipt signature statuses and attestation evidence. Fallback is never silent. - --require-verified-verifier (receipt-verify + agent): policy fails closed when verification did not run on the certificate-covered path. - ML-DSA is deliberately unchanged: no proven implementation exists, so the slot stays fail-closed "unavailable" - the honest hybrid-PQC posture is one proven-classical signature plus one required-but- unproven PQC slot, never a pretend backend. Validated live: receipt verification through the proven verifier (backend recorded), a corrupted signature bit rejected BY the proven binary, tampered attestations rejected, and the policy failing closed when the binary is absent. 49/49 tests green (incl. PEM-SPKI raw-key cross-check against openssl, dispatch/backend recording with a stub, and a real-binary roundtrip that skips gracefully where unbuilt). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 08:13:48 +00:00
return False, "Attestation payload digest does not match signature metadata.", "none"
2026-07-03 11:03:58 +00:00
expected_fingerprint = signature.get("public_key_fingerprint_sha256")
if expected_fingerprint:
actual_fingerprint = public_key_fingerprint(public_key_path)
if expected_fingerprint != actual_fingerprint:
Dogfood cryptography: pacta verifies signatures through the PROVEN code path "Eat your own dogfood": pacta consumes certificates about a verified Ed25519 implementation while checking those certificates' signatures with OpenSSL. Now it can use the object of its own evidence: - dogfood/pacta-verified-verify: a ~90-line Rust binary built against the PINNED proven source workspace (saymrwulf/curve25519-dalek-source at the exact commit the dalek certificates pin - the build records it: aa0f6ab...) with the serial backend pinned via RUSTFLAGS exactly as the verified extraction pins it. Cargo.toml is committed as a template ({{SOURCE}} placeholder) so no machine path is hardcoded; the rendered file, target/, and the built binary are gitignored. - pacta dogfood-build --source <workspace>: renders, builds, installs to dogfood/state/, and writes a provenance sidecar (source commit, backend cfg, rustc, and an honest coverage note: the certificates cover verify_sha512, the extraction-refactored image of this verify path; SHA-512 and the wire glue remain the theorems' documented boundary). pacta dogfood-status reports the active backend. - signing.verify_payload_ed25519_detailed: dispatch - the dogfood binary when present (backend "verified-dalek-serial"), OpenSSL fallback otherwise, and the backend that ACTUALLY ran is recorded in receipt signature statuses and attestation evidence. Fallback is never silent. - --require-verified-verifier (receipt-verify + agent): policy fails closed when verification did not run on the certificate-covered path. - ML-DSA is deliberately unchanged: no proven implementation exists, so the slot stays fail-closed "unavailable" - the honest hybrid-PQC posture is one proven-classical signature plus one required-but- unproven PQC slot, never a pretend backend. Validated live: receipt verification through the proven verifier (backend recorded), a corrupted signature bit rejected BY the proven binary, tampered attestations rejected, and the policy failing closed when the binary is absent. 49/49 tests green (incl. PEM-SPKI raw-key cross-check against openssl, dispatch/backend recording with a stub, and a real-binary roundtrip that skips gracefully where unbuilt). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 08:13:48 +00:00
return False, "Attestation public key fingerprint does not match signature metadata.", "none"
return verify_payload_ed25519_detailed(canonical_attestation_payload(attestation), encoded, public_key_path)
2026-07-03 12:09:34 +00:00
def sign_payload_ed25519(payload: bytes, private_key_path: str | Path) -> str:
openssl = _openssl()
with tempfile.TemporaryDirectory(prefix="pacta-sign-") as tmp:
payload_path = Path(tmp) / "payload.bin"
signature_path = Path(tmp) / "payload.sig"
payload_path.write_bytes(payload)
completed = subprocess.run(
[openssl, "pkeyutl", "-sign", "-inkey", str(private_key_path), "-rawin", "-in", str(payload_path), "-out", str(signature_path)],
check=False,
capture_output=True,
text=True,
timeout=30,
)
if completed.returncode != 0:
raise SignatureError((completed.stderr or completed.stdout or "openssl signing failed").strip())
signature_bytes = signature_path.read_bytes()
return base64.b64encode(signature_bytes).decode("ascii")
def verify_payload_ed25519(payload: bytes, signature_base64: str, public_key_path: str | Path) -> tuple[bool, str | None]:
Dogfood cryptography: pacta verifies signatures through the PROVEN code path "Eat your own dogfood": pacta consumes certificates about a verified Ed25519 implementation while checking those certificates' signatures with OpenSSL. Now it can use the object of its own evidence: - dogfood/pacta-verified-verify: a ~90-line Rust binary built against the PINNED proven source workspace (saymrwulf/curve25519-dalek-source at the exact commit the dalek certificates pin - the build records it: aa0f6ab...) with the serial backend pinned via RUSTFLAGS exactly as the verified extraction pins it. Cargo.toml is committed as a template ({{SOURCE}} placeholder) so no machine path is hardcoded; the rendered file, target/, and the built binary are gitignored. - pacta dogfood-build --source <workspace>: renders, builds, installs to dogfood/state/, and writes a provenance sidecar (source commit, backend cfg, rustc, and an honest coverage note: the certificates cover verify_sha512, the extraction-refactored image of this verify path; SHA-512 and the wire glue remain the theorems' documented boundary). pacta dogfood-status reports the active backend. - signing.verify_payload_ed25519_detailed: dispatch - the dogfood binary when present (backend "verified-dalek-serial"), OpenSSL fallback otherwise, and the backend that ACTUALLY ran is recorded in receipt signature statuses and attestation evidence. Fallback is never silent. - --require-verified-verifier (receipt-verify + agent): policy fails closed when verification did not run on the certificate-covered path. - ML-DSA is deliberately unchanged: no proven implementation exists, so the slot stays fail-closed "unavailable" - the honest hybrid-PQC posture is one proven-classical signature plus one required-but- unproven PQC slot, never a pretend backend. Validated live: receipt verification through the proven verifier (backend recorded), a corrupted signature bit rejected BY the proven binary, tampered attestations rejected, and the policy failing closed when the binary is absent. 49/49 tests green (incl. PEM-SPKI raw-key cross-check against openssl, dispatch/backend recording with a stub, and a real-binary roundtrip that skips gracefully where unbuilt). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 08:13:48 +00:00
ok, error, _backend = verify_payload_ed25519_detailed(payload, signature_base64, public_key_path)
return ok, error
def verify_payload_ed25519_detailed(
payload: bytes,
signature_base64: str,
public_key_path: str | Path,
) -> tuple[bool, str | None, str]:
"""Verify an Ed25519 signature, preferring pacta's DOGFOOD verifier -
a binary built from the pinned, certificate-covered dalek source with
the serial backend pinned - and falling back to OpenSSL when the
dogfood binary is unavailable. The third element names the backend that
actually ran so callers can record (and policies can require) the
verified path."""
from .dogfood import BACKEND_OPENSSL, BACKEND_VERIFIED, locate_verifier, verify_payload_dogfood
2026-07-03 11:03:58 +00:00
try:
2026-07-03 12:09:34 +00:00
signature_bytes = base64.b64decode(signature_base64, validate=True)
2026-07-03 11:03:58 +00:00
except ValueError as exc:
Dogfood cryptography: pacta verifies signatures through the PROVEN code path "Eat your own dogfood": pacta consumes certificates about a verified Ed25519 implementation while checking those certificates' signatures with OpenSSL. Now it can use the object of its own evidence: - dogfood/pacta-verified-verify: a ~90-line Rust binary built against the PINNED proven source workspace (saymrwulf/curve25519-dalek-source at the exact commit the dalek certificates pin - the build records it: aa0f6ab...) with the serial backend pinned via RUSTFLAGS exactly as the verified extraction pins it. Cargo.toml is committed as a template ({{SOURCE}} placeholder) so no machine path is hardcoded; the rendered file, target/, and the built binary are gitignored. - pacta dogfood-build --source <workspace>: renders, builds, installs to dogfood/state/, and writes a provenance sidecar (source commit, backend cfg, rustc, and an honest coverage note: the certificates cover verify_sha512, the extraction-refactored image of this verify path; SHA-512 and the wire glue remain the theorems' documented boundary). pacta dogfood-status reports the active backend. - signing.verify_payload_ed25519_detailed: dispatch - the dogfood binary when present (backend "verified-dalek-serial"), OpenSSL fallback otherwise, and the backend that ACTUALLY ran is recorded in receipt signature statuses and attestation evidence. Fallback is never silent. - --require-verified-verifier (receipt-verify + agent): policy fails closed when verification did not run on the certificate-covered path. - ML-DSA is deliberately unchanged: no proven implementation exists, so the slot stays fail-closed "unavailable" - the honest hybrid-PQC posture is one proven-classical signature plus one required-but- unproven PQC slot, never a pretend backend. Validated live: receipt verification through the proven verifier (backend recorded), a corrupted signature bit rejected BY the proven binary, tampered attestations rejected, and the policy failing closed when the binary is absent. 49/49 tests green (incl. PEM-SPKI raw-key cross-check against openssl, dispatch/backend recording with a stub, and a real-binary roundtrip that skips gracefully where unbuilt). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 08:13:48 +00:00
return False, f"Invalid base64 signature: {exc}", "none"
binary = locate_verifier()
if binary is not None:
ok, error = verify_payload_dogfood(payload, signature_bytes, public_key_path, binary)
return ok, error, BACKEND_VERIFIED
ok, error = _verify_payload_openssl(payload, signature_bytes, public_key_path)
return ok, error, BACKEND_OPENSSL
def _verify_payload_openssl(payload: bytes, signature_bytes: bytes, public_key_path: str | Path) -> tuple[bool, str | None]:
openssl = _openssl()
2026-07-03 11:03:58 +00:00
with tempfile.TemporaryDirectory(prefix="pacta-verify-") as tmp:
2026-07-03 12:09:34 +00:00
payload_path = Path(tmp) / "payload.bin"
2026-07-03 11:03:58 +00:00
signature_path = Path(tmp) / "payload.sig"
2026-07-03 12:09:34 +00:00
payload_path.write_bytes(payload)
2026-07-03 11:03:58 +00:00
signature_path.write_bytes(signature_bytes)
completed = subprocess.run(
[
openssl,
"pkeyutl",
"-verify",
"-pubin",
"-inkey",
str(public_key_path),
"-rawin",
"-in",
str(payload_path),
"-sigfile",
str(signature_path),
],
check=False,
capture_output=True,
text=True,
timeout=30,
)
if completed.returncode != 0:
return False, (completed.stderr or completed.stdout or "openssl verification failed").strip()
return True, None
def _openssl() -> str:
path = shutil.which("openssl")
if not path:
raise SignatureError("openssl is not available on PATH")
return path