mirror of
https://github.com/saymrwulf/proof-aware-crypto-tooling-agent.git
synced 2026-09-03 19:53:43 +00:00
dogfood: anchor the signer path to the package, not the working directory
DEFAULT_STATE_DIR was Path("dogfood")/"state" -- a relative path, so
locate_verifier() resolved against whatever directory the process started in.
The consequence was not a crash but something quieter: run the provider from
the repository root and it signs with the attested dalek build; run it from
anywhere else and the binary is not found, signing falls back to OpenSSL, and
the head records `signing_backend: openssl`. Which implementation signs the
transparency log was an accident of the current directory.
Found by re-signing published head 13 as a reproducibility check. The byte
comparison passed -- the reconstructed payload re-signed to signature_base64
exactly -- but the backend came back `openssl` while head 13 records
`verified-dalek-serial`. The swap is invisible precisely BECAUSE Ed25519 is
deterministic: both implementations emit identical bytes, nothing downstream
differs, no test fails. A silent substitution that changes no output is one
nobody notices until the outputs differ, which for a signing key is late.
(The byte-identity is also a good result in its own right: independent
cross-implementation agreement on the SIGNING side, alongside the five-way
agreement already demonstrated on the verifying side.)
Fix: anchor to the package via Path(__file__).resolve().parents[2]. Resolution
no longer depends on cwd -- demonstrated from /, /tmp and the repo root, all
three now select verified-dalek where before only the repo root did.
Added PACTA_REQUIRE_VERIFIED_SIGNER. Recording a downgrade truthfully, which
this code already did, tells you afterwards which implementation signed; it
does not let you DECIDE which one will. For signing a transparency-log head
that choice should be stated up front and enforced, not discovered in a
provenance field once the head exists. Set it and signing raises instead of
substituting OpenSSL. Off by default: every existing caller keeps the
fall-back-and-record behaviour.
Negative-tested both ways (refuses, naming the path it searched; default still
records `openssl`). Suite: 145 passed, 0 failed, 0 skipped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
cd3b1bc921
commit
5e35a533e1
2 changed files with 45 additions and 2 deletions
|
|
@ -12,7 +12,27 @@ from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
DOGFOOD_ENV = "PACTA_DOGFOOD_VERIFIER"
|
DOGFOOD_ENV = "PACTA_DOGFOOD_VERIFIER"
|
||||||
DEFAULT_STATE_DIR = Path("dogfood") / "state"
|
REQUIRE_VERIFIED_ENV = "PACTA_REQUIRE_VERIFIED_SIGNER"
|
||||||
|
|
||||||
|
# Anchored to the PACKAGE, not to the caller's working directory.
|
||||||
|
#
|
||||||
|
# This was `Path("dogfood") / "state"` — a relative path, so it resolved against
|
||||||
|
# whatever directory the process happened to start in. The consequence was not a
|
||||||
|
# crash but something quieter and worse: run the provider from the repository
|
||||||
|
# root and it signs with the attested dalek build; run it from anywhere else and
|
||||||
|
# locate_verifier() finds nothing, signing falls back to OpenSSL, and the head
|
||||||
|
# records `signing_backend: openssl`. WHICH IMPLEMENTATION SIGNS THE
|
||||||
|
# TRANSPARENCY LOG WAS AN ACCIDENT OF THE CURRENT DIRECTORY.
|
||||||
|
#
|
||||||
|
# Found 2026-08-04 by re-signing the published head 13 as a reproducibility
|
||||||
|
# check: the byte comparison passed, but the backend came back `openssl` while
|
||||||
|
# the published head says `verified-dalek-serial`. Both produced identical bytes
|
||||||
|
# — Ed25519 is deterministic, so that is expected and is itself useful evidence
|
||||||
|
# — which is exactly why the substitution was invisible. A silent backend swap
|
||||||
|
# that changes no output is one nobody notices until the outputs differ.
|
||||||
|
#
|
||||||
|
# __file__ is <repo>/src/pacta/dogfood.py, so parents[2] is the repo root.
|
||||||
|
DEFAULT_STATE_DIR = Path(__file__).resolve().parents[2] / "dogfood" / "state"
|
||||||
BACKEND_VERIFIED = "verified-dalek-serial"
|
BACKEND_VERIFIED = "verified-dalek-serial"
|
||||||
BACKEND_OPENSSL = "openssl"
|
BACKEND_OPENSSL = "openssl"
|
||||||
|
|
||||||
|
|
@ -112,6 +132,21 @@ def locate_verifier(state_dir: str | Path | None = None) -> Path | None:
|
||||||
return path if path.exists() else None
|
return path if path.exists() else None
|
||||||
|
|
||||||
|
|
||||||
|
def require_verified_signer() -> bool:
|
||||||
|
"""Whether a downgrade to OpenSSL is forbidden for this process.
|
||||||
|
|
||||||
|
Recording a downgrade truthfully, which this code already does, tells you
|
||||||
|
afterwards which implementation signed. It does not let you DECIDE which
|
||||||
|
one will. For an operation as consequential as signing a transparency-log
|
||||||
|
head, the choice should be stated up front and enforced, not discovered in
|
||||||
|
the provenance field once the head exists.
|
||||||
|
|
||||||
|
Off by default: every existing caller keeps the fall-back-and-record
|
||||||
|
behaviour. Set PACTA_REQUIRE_VERIFIED_SIGNER=1 and signing raises instead
|
||||||
|
of quietly substituting OpenSSL."""
|
||||||
|
return os.environ.get(REQUIRE_VERIFIED_ENV, "").strip().lower() in {"1", "true", "yes", "on"}
|
||||||
|
|
||||||
|
|
||||||
def load_provenance(binary_path: str | Path) -> dict[str, Any]:
|
def load_provenance(binary_path: str | Path) -> dict[str, Any]:
|
||||||
sidecar = Path(binary_path).with_suffix(".provenance.json")
|
sidecar = Path(binary_path).with_suffix(".provenance.json")
|
||||||
if sidecar.exists():
|
if sidecar.exists():
|
||||||
|
|
|
||||||
|
|
@ -92,12 +92,20 @@ def sign_payload_ed25519_detailed(payload: bytes, private_key_path: str | Path)
|
||||||
library) and falling back to OpenSSL. Returns (base64 signature, the
|
library) and falling back to OpenSSL. Returns (base64 signature, the
|
||||||
backend that actually signed) - the backend is recorded next to every
|
backend that actually signed) - the backend is recorded next to every
|
||||||
signature so the provenance is never silent."""
|
signature so the provenance is never silent."""
|
||||||
from .dogfood import BACKEND_OPENSSL, BACKEND_VERIFIED, locate_verifier, sign_payload_dogfood
|
from .dogfood import (BACKEND_OPENSSL, BACKEND_VERIFIED, REQUIRE_VERIFIED_ENV,
|
||||||
|
default_binary_path, locate_verifier, require_verified_signer,
|
||||||
|
sign_payload_dogfood)
|
||||||
|
|
||||||
binary = locate_verifier()
|
binary = locate_verifier()
|
||||||
if binary is not None:
|
if binary is not None:
|
||||||
signature_bytes = sign_payload_dogfood(payload, private_key_path, binary)
|
signature_bytes = sign_payload_dogfood(payload, private_key_path, binary)
|
||||||
return base64.b64encode(signature_bytes).decode("ascii"), BACKEND_VERIFIED
|
return base64.b64encode(signature_bytes).decode("ascii"), BACKEND_VERIFIED
|
||||||
|
if require_verified_signer():
|
||||||
|
raise SignatureError(
|
||||||
|
f"{REQUIRE_VERIFIED_ENV} is set, so falling back to OpenSSL is refused, but the "
|
||||||
|
f"attested signer was not found at {default_binary_path()}. "
|
||||||
|
f"Build it, or point {'PACTA_DOGFOOD_VERIFIER'} at it, or unset "
|
||||||
|
f"{REQUIRE_VERIFIED_ENV} to accept the recorded downgrade.")
|
||||||
return _sign_payload_openssl(payload, private_key_path), BACKEND_OPENSSL
|
return _sign_payload_openssl(payload, private_key_path), BACKEND_OPENSSL
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue