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

Acceptable Signatures

Signature Formats

FormatAlgorithmKey TypeStatus
DSSEEd25519AsymmetricPreferred
DSSEECDSA P-256AsymmetricAccepted
DSSERSA-2048+AsymmetricAccepted
COSEEdDSAAsymmetricAccepted
JWSES256AsymmetricAccepted
JWSRS256AsymmetricDeprecated

Hash Algorithms

AlgorithmUsageStatus
SHA-256PrimaryRequired
BLAKE3-256SecondaryPreferred
SHA-384AlternativeAccepted
SHA-512AlternativeAccepted
SHA-1LegacyRejected
MD5LegacyRejected

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

{
  "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

CodeReasonAction
sig_missingNo signature presentApply fallback (CM6)
sig_invalidSignature verification failedReject
sig_expiredSignature validity period exceededReject
key_unknownSigning key not in keyringReject
key_expiredSigning key validity exceededReject
key_revokedSigning key has been revokedReject
issuer_untrustedIssuer not in trusted listReject
alg_unsupportedAlgorithm not acceptableReject
hash_mismatchContent hash doesn’t matchReject

Flag Policy

When --allow-unsigned is set:

ConditionBehavior
Signature missingAccept with provenance=unknown, emit warning
Signature invalidReject (flag doesn’t override invalid)
Key unknownAccept with provenance=unverified, emit warning

Verification API

Endpoint

POST /api/v1/ingest/verify
Content-Type: application/json

{
  "sbom": "<base64-encoded-sbom>",
  "signature": "<base64-encoded-signature>",
  "options": {
    "allowUnsigned": false,
    "requireProvenance": true
  }
}

Response

{
  "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

Revocation List Format

{
  "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
{
  "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:..."
}