Sign & verify evidence
🟡 Partial — ECDSA-P256+DSSE+in-toto signing done; pluggable backend (local + Scaleway KMS); keyless identity anchoring (Sigstore) pending.
Every output of the froga engine — the evidence bundle, the ISO 23894 cycle reconstruct, the Annex IV, and conformance reports — is signed before being written to disk. Signing is an explicit requirement of the EU AI Act: Annex IV §2(g) requires that evaluation records be “dated and signed”. Cryptographic integrity makes any subsequent modification detectable.
Signing scheme: ECDSA-P256 (RFC 6979) + DSSE + in-toto
Section titled “Signing scheme: ECDSA-P256 (RFC 6979) + DSSE + in-toto”Each .froga/* artifact has a sibling .sig file containing a DSSE envelope (Dead Simple Signing Envelope) with an in-toto Statement signed with ECDSA over the NIST P-256 curve (SHA-256). Signatures are deterministic (RFC 6979): the same key over the same content always yields the same value.
The signing flow is:
- The artifact JSON is serialized in JCS canonical form (RFC 8785) — stable, idempotent bytes regardless of key ordering.
- An in-toto Statement is constructed whose
subjectis the SHA-256 digest of the canonical JSON. This binds the signature to the exact artifact content. - The Statement becomes the
payloadof the DSSE envelope. The envelope applies Pre-Authentication Encoding (PAE) before signing. - Signed with the
p256crate (RustCrypto). The signature is stored as rawr‖s(64 bytes) base64-encoded inside the envelope. - The serialized envelope is written to
<artifact>.sig.
The envelope’s keyid is hex(SHA-256(uncompressed SEC1 pubkey, 65 B)): it identifies the public key (0x04 ‖ X ‖ Y) without transporting the whole key and anchors verification to the expected key.
bundle.json ← evidence bundle (gate, controls, risk analysis…)bundle.json.sig ← DSSE envelope · in-toto Statement · ECDSA-P256 signedThe same applies to reconstruct.json and conformance/<slug>.json.
Pluggable signing backend
Section titled “Pluggable signing backend”The signer is pluggable via the FROGA_SIGNING_BACKEND environment variable:
local(default) — local ECDSA-P256 key. The private scalar is read fromFROGA_SIGNING_KEY(32 bytes in hex, 64 characters); if not set, a fixed, known development scalar is used — it provides integrity/tamper-evidence only, not non-repudiation.scaleway-kms— signing is delegated to the Scaleway Key Manager (algorithmEC-P256-SHA256); the private key never leaves the KMS. The verifying pubkey is obtained from the KMS in uncompressed SEC1 form (65 B).
froga pubkey prints the verifying pubkey of the configured signer (uncompressed SEC1, 130-character hex). The customer exports it and registers it per connection in the cloud (ScmConnection.signerPubkey), so the cloud verifies each piece of evidence against the expected key of THAT connection.
Verifying with froga verify
Section titled “Verifying with froga verify”froga verify verifies the entire evidence dossier present in .froga/ — bundle.json, reconstruct.json, conformance/*.json and risk-projection/*.json, each against its .sig (entitlement.json is excluded: it is signed by the Venturalítica issuer, not by the system; and a dossier artifact missing its .sig is a failure). For each piece it checks four conditions in sequence:
- The envelope is a valid DSSE JSON with
payloadType: "application/vnd.in-toto+json". - The envelope’s
keyidmatcheshex(SHA-256(SEC1 pubkey))of the resolved public key (see below). - The ECDSA-P256 signature (raw
r‖s) over the PAE is valid against that public key. - The Statement’s
subject.sha256matches the digest of the given artifact.
If all pass on every piece, the command prints froga verify: firma VÁLIDA and returns exit 0. If any condition fails, it returns exit 1 with a descriptive message.
froga verify --repo /path/to/project --pubkey 04a1b2…Expected output after a successful froga run:
froga verify: firma VÁLIDAfroga verify is the authoritative verifier. The cloud plane implements the same logic in TypeScript with @noble/curves (P-256) and verifies per connection against the signerPubkey registered for that ScmConnection, in a fail-loud manner: if the connection has no configured pubkey, the cloud does not verify silently but fails. Even so, the definitive result is always from the froga binary; cloud verification is a convenience and MUST match.
What the signature protects
Section titled “What the signature protects”| Scenario | Detected by froga verify? |
|---|---|
Bundle manually modified after froga run | Yes — SHA-256 digest of the subject does not match |
.sig replaced by a valid one from another bundle | Yes — subject points to the original bundle |
| Forged signature with a different key | Yes — the keyid does not match the resolved pubkey (--pubkey / PUBKEY.txt) |
| Valid signature over intact content | Not a problem — result: VALID |
The signature does not cover git history integrity: froga reconstruct — which replays bundle commits — is the mechanism for verifying temporal lineage.
Current status and future directions
Section titled “Current status and future directions”Keyless identity anchoring (Sigstore / Fulcio / Rekor) is pending. This mechanism would associate the public key with a verified OIDC identity (e.g. a CI/CD account) and deposit a transparency log entry in Rekor, eliminating the dependency on the verifier knowing the public key in advance.
The crate guard reflects this state: the froga-sign module explicitly names “keyless identity anchoring pending” in its internal documentation.
For more detail on .froga/* artifacts and their envelope structure, see Reference: .froga/* artifacts.
For context on current engine incompleteness, see State & incompleteness.
References
Section titled “References”.froga/*artifacts —FrogaArtifact<T>envelope structure, JSON Schema v1- State & incompleteness — current engine gaps and future directions
- CLI
frogareference — full documentation offroga verifyand other subcommands