# Competitor SBOM/Scan Signature Verification (CM2) Status: Draft · Date: 2025-12-04 Scope: Specify signature and provenance verification requirements for accepting external SBOM and scan outputs, including rejection/flag policies. ## Objectives - Define acceptable signature algorithms and formats. - Establish trust root management for external signers. - Specify verification workflow and failure modes. - Enable offline verification with bundled trust roots. ## Acceptable Signatures ### Signature Formats | Format | Algorithm | Key Type | Status | |--------|-----------|----------|--------| | DSSE | Ed25519 | Asymmetric | Preferred | | DSSE | ECDSA P-256 | Asymmetric | Accepted | | DSSE | RSA-2048+ | Asymmetric | Accepted | | COSE | EdDSA | Asymmetric | Accepted | | JWS | ES256 | Asymmetric | Accepted | | JWS | RS256 | Asymmetric | Deprecated | ### Hash Algorithms | Algorithm | Usage | Status | |-----------|-------|--------| | SHA-256 | Primary | Required | | BLAKE3-256 | Secondary | Preferred | | SHA-384 | Alternative | Accepted | | SHA-512 | Alternative | Accepted | | SHA-1 | Legacy | Rejected | | MD5 | Legacy | Rejected | ## Trust Root Management ### Bundled Trust Roots ``` out/offline/competitor-ingest-kit-v1/trust/ ├── root-ca.pem # CA for signed SBOMs ├── keyring.json # Known signing keys ├── cosign-keys/ # Cosign public keys │ ├── syft-release.pub │ ├── trivy-release.pub │ └── clair-release.pub └── fulcio-root.pem # Sigstore Fulcio CA ``` ### Keyring Format ```json { "keys": [ { "id": "syft-release-2025", "type": "ecdsa-p256", "publicKey": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----", "issuer": "https://github.com/anchore/syft", "validFrom": "2025-01-01T00:00:00Z", "validTo": "2026-01-01T00:00:00Z", "purposes": ["sbom-signing", "attestation-signing"] } ], "trustedIssuers": [ "https://github.com/anchore/syft", "https://github.com/aquasecurity/trivy", "https://github.com/quay/clair" ] } ``` ## Verification Workflow ``` ┌─────────────┐ │ Receive │ │ SBOM │ └─────────────┘ │ ▼ ┌─────────────┐ ┌─────────────┐ │ Has DSSE? │──No─► Has JWS? │──No─► Unsigned └─────────────┘ └─────────────┘ │ │ │ │ Yes Yes │ │ │ │ ▼ ▼ ▼ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Verify DSSE │ │ Verify JWS │ │ Apply CM6 │ │ Signature │ │ Signature │ │ Fallback │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ ▼ ▼ ▼ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Valid? │ │ Valid? │ │ Provenance: │ └─────────────┘ └─────────────┘ │ unknown │ │ │ │ │ └─────────────┘ Yes No Yes No │ │ │ │ ▼ ▼ ▼ ▼ Accept Reject Accept Reject ``` ### Verification Steps 1. **Format Detection** - Check for DSSE envelope wrapper - Check for detached signature file (`.sig`) - Check for inline JWS header 2. **Signature Extraction** - Parse envelope/signature structure - Extract signer key ID and algorithm 3. **Key Lookup** - Search bundled keyring for key ID - Verify key is within validity period - Check key purpose matches usage 4. **Cryptographic Verification** - Verify signature over payload - Verify hash matches content - Check for signature expiry 5. **Provenance Validation** - Extract signer identity - Verify issuer is trusted - Check build metadata if present ## Failure Modes ### Rejection Reasons | Code | Reason | Action | |------|--------|--------| | `sig_missing` | No signature present | Apply fallback (CM6) | | `sig_invalid` | Signature verification failed | Reject | | `sig_expired` | Signature validity period exceeded | Reject | | `key_unknown` | Signing key not in keyring | Reject | | `key_expired` | Signing key validity exceeded | Reject | | `key_revoked` | Signing key has been revoked | Reject | | `issuer_untrusted` | Issuer not in trusted list | Reject | | `alg_unsupported` | Algorithm not acceptable | Reject | | `hash_mismatch` | Content hash doesn't match | Reject | ### Flag Policy When `--allow-unsigned` is set: | Condition | Behavior | |-----------|----------| | Signature missing | Accept with `provenance=unknown`, emit warning | | Signature invalid | Reject (flag doesn't override invalid) | | Key unknown | Accept with `provenance=unverified`, emit warning | ## Verification API ### Endpoint ```http POST /api/v1/ingest/verify Content-Type: application/json { "sbom": "", "signature": "", "options": { "allowUnsigned": false, "requireProvenance": true } } ``` ### Response ```json { "verification": { "status": "valid", "signature": { "format": "dsse", "algorithm": "ecdsa-p256", "keyId": "syft-release-2025", "signedAt": "2025-12-04T00:00:00Z" }, "provenance": { "issuer": "https://github.com/anchore/syft", "buildId": "build-12345", "sourceRepo": "https://github.com/example/app" }, "hash": { "algorithm": "sha256", "value": "..." } } } ``` ## Offline Verification ### Requirements - All trust roots bundled in offline kit - No network calls during verification - Keyring includes all expected signers - CRL/OCSP checks disabled (use bundled revocation lists) ### Revocation List Format ```json { "revoked": [ { "keyId": "compromised-key-2024", "revokedAt": "2024-12-01T00:00:00Z", "reason": "Key compromise" } ], "lastUpdated": "2025-12-04T00:00:00Z" } ``` ## Integration with Normalization (CM1) After successful verification: 1. Extract tool metadata from signature/provenance 2. Pass to normalization adapter 3. Include verification result in normalized output ```json { "source": { "tool": "syft", "version": "1.0.0", "hash": "sha256:..." }, "verification": { "status": "verified", "keyId": "syft-release-2025", "signedAt": "2025-12-04T00:00:00Z" }, "components": [...], "normalized_hash": "blake3:..." } ``` ## Links - Sprint: `docs/implplan/SPRINT_0186_0001_0001_record_deterministic_execution.md` (CM2) - Normalization: `docs/modules/scanner/design/competitor-ingest-normalization.md` (CM1) - Fallback: See CM6 in this document series