SCITT signed statements

A SCITT (Supply Chain Integrity, Transparency, and Trust) signed statement is a b.cose COSE_Sign1 that makes a signed, attributable claim about an artifact — a signed SBOM, a build attestation, a release approval. The artifact (or a hash / reference to it) is the payload; the issuer and the subject are carried in the integrity-protected CWT_Claims header (label 15, RFC 9597): iss (label 1) is who makes the statement, sub (label 2) is the artifact the statement is about. This module builds and verifies that envelope over b.cose + b.cbor.

b.scitt.signStatement(payload, opts) produces the COSE_Sign1, placing iss / sub (plus any extra CWT claims) in the protected CWT_Claims header and declaring the payload media type as the COSE content type. b.scitt.verifyStatement(statement, opts) verifies the signature (delegating the mandatory algorithm allowlist to b.cose.verify), then enforces that a CWT_Claims header with both iss and sub is present — refusing a statement that omits the issuer/subject binding — and optionally checks them against expected values.

The signing algorithms are exactly b.cose's: the classical ES256/384/512 + EdDSA (final COSE ids, interoperable today) and ML-DSA-87 (PQC-forward, draft COSE id). Because the identity binding lives in the protected header it is covered by the signature and cannot be substituted without detection.

Scope. This is the issuer half of SCITT — producing and verifying signed statements, which is buildable today on finalized RFCs (RFC 9052 COSE, RFC 9597 CWT_Claims header, RFC 8392 iss/sub). The transparency receipt (an inclusion proof from an append-only transparency service, COSE Receipts / draft-ietf-cose-merkle-tree-proofs) and the transparency-service registration protocol (draft-ietf-scitt-*) are deferred until those drafts publish — a signed statement produced here is the input a transparency service registers, and the receipt format is the part still in flux. Re-open on COSE-Receipts publication.

b.scitt.signStatement(payload, opts) #

experimental0.12.37soc2cra
{
  {
    alg:          string,        // b.cose alg: "ES256" | … | "ML-DSA-87"
    privateKey:   object,        // matching KeyObject or PEM
    issuer:       string,        // → CWT_Claims iss (label 1) — who makes the statement
    subject:      string,        // → CWT_Claims sub (label 2) — the artifact the statement is about
    contentType?: number|string, // payload media type (e.g. "application/spdx+json")
    claims?:      object,        // extra CWT claims by integer label, merged into CWT_Claims
    kid?:         string,        // → unprotected header label 4
    externalAad?: Buffer,        // bound into the signature
  }
}

Produce a SCITT signed statement: a COSE_Sign1 over payload (the artifact bytes, or a hash / reference to it) whose integrity-protected CWT_Claims header (label 15) binds the issuer (iss) and subject (sub). Declare the payload media type via contentType so a consumer knows how to interpret it.

var stmt = await b.scitt.signStatement(sbomBytes, {
  alg: "ES256", privateKey: issuerKey,
  issuer: "https://builder.example", subject: "pkg:npm/widget@1.2.3",
  contentType: "application/spdx+json",
});

b.scitt.verifyStatement(statement, opts) #

experimental0.12.37soc2cra
{
  {
    algorithms:       string[],  // required — accepted alg names (allowlist)
    publicKey?:       object,    // verification key (KeyObject / PEM)
    keyResolver?:     function,  // (protectedHeaders, unprotectedHeaders) → key
    expectedIssuer?:  string,    // require iss === this
    expectedSubject?: string,    // require sub === this
    externalAad?:     Buffer,    // must match what was signed
    maxBytes?:        number,    // forwarded to b.cose.verify → b.cbor.decode
    maxDepth?:        number,
  }
}

Verify a SCITT signed statement and return its payload + identity binding. The COSE signature is checked through b.cose.verify (the algorithm allowlist is mandatory); a statement that does not carry a CWT_Claims header with both iss and sub is refused — that binding is what makes it a SCITT statement rather than a bare COSE_Sign1. expectedIssuer / expectedSubject, when given, must match.

var out = await b.scitt.verifyStatement(stmt, {
  algorithms: ["ES256"], publicKey: issuerPub,
  expectedSubject: "pkg:npm/widget@1.2.3",
});
// → { payload: , issuer, subject, cwtClaims: Map, alg, protectedHeaders, unprotectedHeaders }

Last updated 2026-08-08T16:39:49.652Z by seeder.