Sigstore Bundle Format

This document describes the Sigstore Bundle v0.3 format implementation in StellaOps for offline DSSE envelope verification.

Overview

A Sigstore bundle is a self-contained package that includes all verification material needed to verify a DSSE envelope without network access. This enables offline verification scenarios critical for air-gapped environments.

Bundle Structure

{
  "mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json",
  "verificationMaterial": {
    "certificate": { ... },
    "tlogEntries": [ ... ],
    "timestampVerificationData": { ... }
  },
  "dsseEnvelope": {
    "payloadType": "application/vnd.in-toto+json",
    "payload": "<base64-encoded-payload>",
    "signatures": [
      {
        "sig": "<base64-encoded-signature>",
        "keyid": "<optional-key-id>"
      }
    ]
  }
}

Components

Media Type

Verification Material

Contains cryptographic material for verification:

FieldTypeDescription
certificateObjectX.509 signing certificate (keyless signing)
publicKeyObjectPublic key (keyful signing, alternative to certificate)
tlogEntriesArrayTransparency log entries from Rekor
timestampVerificationDataObjectRFC 3161 timestamps

Certificate

{
  "certificate": {
    "rawBytes": "<base64-DER-certificate>"
  }
}

Public Key (alternative to certificate)

{
  "publicKey": {
    "hint": "<optional-key-hint>",
    "rawBytes": "<base64-public-key>"
  }
}

Transparency Log Entry

{
  "logIndex": "12345",
  "logId": {
    "keyId": "<base64-log-key-id>"
  },
  "kindVersion": {
    "kind": "dsse",
    "version": "0.0.1"
  },
  "integratedTime": "1703500000",
  "inclusionPromise": {
    "signedEntryTimestamp": "<base64-SET>"
  },
  "inclusionProof": {
    "logIndex": "12345",
    "rootHash": "<base64-merkle-root>",
    "treeSize": "100000",
    "hashes": ["<base64-hash>", ...],
    "checkpoint": {
      "envelope": "<checkpoint-note>"
    }
  },
  "canonicalizedBody": "<base64-canonical-body>"
}

DSSE Envelope

Standard DSSE envelope format:

{
  "payloadType": "application/vnd.in-toto+json",
  "payload": "<base64-encoded-attestation>",
  "signatures": [
    {
      "sig": "<base64-signature>",
      "keyid": ""
    }
  ]
}

Usage

Building a Bundle

using StellaOps.Attestor.Bundle.Builder;
using StellaOps.Attestor.Bundle.Models;

var bundle = new SigstoreBundleBuilder()
    .WithDsseEnvelope(payloadType, payload, signatures)
    .WithCertificate(certificateBytes)
    .WithRekorEntry(
        logIndex: "12345",
        logIdKeyId: logKeyId,
        integratedTime: "1703500000",
        canonicalizedBody: body)
    .WithInclusionProof(inclusionProof)
    .Build();

// Serialize to JSON
var json = bundle.BuildJson();
File.WriteAllText("attestation.bundle", json);

Deserializing a Bundle

using StellaOps.Attestor.Bundle.Serialization;

var json = File.ReadAllText("attestation.bundle");
var bundle = SigstoreBundleSerializer.Deserialize(json);

// Or with error handling
if (SigstoreBundleSerializer.TryDeserialize(json, out var bundle))
{
    // Use bundle
}

Verifying a Bundle

using StellaOps.Attestor.Bundle.Verification;

var verifier = new SigstoreBundleVerifier();
var result = await verifier.VerifyAsync(bundle);

if (result.IsValid)
{
    Console.WriteLine("Bundle verified successfully");
    Console.WriteLine($"DSSE Signature: {result.Checks.DsseSignature}");
    Console.WriteLine($"Certificate Chain: {result.Checks.CertificateChain}");
    Console.WriteLine($"Inclusion Proof: {result.Checks.InclusionProof}");
}
else
{
    foreach (var error in result.Errors)
    {
        Console.WriteLine($"Error: {error.Code} - {error.Message}");
    }
}

Verification Options

var options = new BundleVerificationOptions
{
    VerifyInclusionProof = true,
    VerifyTimestamps = false,
    VerificationTime = DateTimeOffset.UtcNow,
    TrustedRoots = trustedCertificates
};

var result = await verifier.VerifyAsync(bundle, options);

Verification Checks

CheckDescription
DsseSignatureVerifies DSSE signature using PAE encoding
CertificateChainValidates certificate validity period
InclusionProofVerifies Merkle inclusion proof (RFC 6962)
TransparencyLogValidates transparency log entry
TimestampVerifies RFC 3161 timestamps (optional)

Error Codes

CodeDescription
InvalidBundleStructureBundle JSON structure is invalid
MissingDsseEnvelopeDSSE envelope is required
DsseSignatureInvalidSignature verification failed
MissingCertificateNo certificate or public key
CertificateChainInvalidCertificate chain validation failed
CertificateExpiredCertificate has expired
CertificateNotYetValidCertificate not yet valid
InclusionProofInvalidMerkle proof verification failed
RootHashMismatchComputed root doesn’t match expected

Cosign Compatibility

Bundles created by StellaOps are compatible with cosign verification:

# Verify bundle with cosign
cosign verify-attestation \
    --bundle attestation.bundle \
    --certificate-identity="subject@example.com" \
    --certificate-oidc-issuer="https://issuer.example.com" \
    --type=slsaprovenance \
    registry.example.com/image:tag

See Cosign Verification Examples for more details.


Aggregated Attestation Bundle Format

This section describes the StellaOps Attestation Bundle format for aggregating multiple attestations for long-term verification.

Overview

Aggregated attestation bundles collect multiple attestations from a time period into a single verifiable package. This enables:

Bundle Structure

{
  "metadata": {
    "bundleId": "sha256:abc123...",
    "version": "1.0",
    "createdAt": "2025-12-26T02:00:00Z",
    "periodStart": "2025-12-01T00:00:00Z",
    "periodEnd": "2025-12-31T23:59:59Z",
    "attestationCount": 1542,
    "tenantId": "tenant-1",
    "orgKeyFingerprint": "sha256:def456..."
  },
  "attestations": [
    {
      "entryId": "uuid-1",
      "rekorUuid": "24296fb2...",
      "rekorLogIndex": 12345678,
      "artifactDigest": "sha256:...",
      "predicateType": "verdict.stella/v1",
      "signedAt": "2025-12-15T10:30:00Z",
      "signingMode": "keyless",
      "signingIdentity": {
        "issuer": "https://token.actions.githubusercontent.com",
        "subject": "repo:org/repo:ref:refs/heads/main",
        "san": "https://github.com/org/repo/.github/workflows/release.yml@refs/heads/main"
      },
      "inclusionProof": {
        "checkpoint": {
          "origin": "rekor.sigstore.dev - ...",
          "size": 12000000,
          "rootHash": "base64...",
          "timestamp": "2025-12-15T10:30:05Z"
        },
        "path": ["base64hash1", "base64hash2", ...]
      },
      "envelope": {
        "payloadType": "application/vnd.in-toto+json",
        "payload": "base64...",
        "signatures": [{"sig": "base64...", "keyid": ""}],
        "certificateChain": ["base64cert1", ...]
      }
    }
  ],
  "merkleTree": {
    "algorithm": "SHA256",
    "root": "sha256:abc123...",
    "leafCount": 1542
  },
  "orgSignature": {
    "keyId": "org-signing-key-2025",
    "algorithm": "ECDSA_P256",
    "signature": "base64...",
    "signedAt": "2025-12-26T02:05:00Z",
    "certificateChain": ["base64cert1", ...]
  }
}

Components

Metadata

FieldTypeDescription
bundleIdstringContent-addressed ID: sha256:<merkle_root>
versionstringBundle schema version (currently “1.0”)
createdAtISO 8601Bundle creation timestamp (UTC)
periodStartISO 8601Start of attestation collection period
periodEndISO 8601End of attestation collection period
attestationCountintNumber of attestations in bundle
tenantIdstringOptional tenant identifier
orgKeyFingerprintstringFingerprint of org signing key (if signed)

Attestations

Each attestation entry contains:

FieldTypeDescription
entryIdstringUnique entry identifier
rekorUuidstringRekor transparency log UUID
rekorLogIndexlongRekor log index
artifactDigeststringSHA256 digest of attested artifact
predicateTypestringIn-toto predicate type
signedAtISO 8601When attestation was signed
signingModestringkeyless, kms, hsm, or fido2
signingIdentityobjectSigner identity information
inclusionProofobjectRekor Merkle inclusion proof
envelopeobjectDSSE envelope with signatures and certificates

Merkle Tree

Deterministic Merkle tree over attestation hashes:

FieldTypeDescription
algorithmstringHash algorithm (always “SHA256”)
rootstringMerkle root: sha256:<64-hex>
leafCountintNumber of leaves (= attestation count)

Org Signature

Optional organizational endorsement:

FieldTypeDescription
keyIdstringSigning key identifier
algorithmstringECDSA_P256, Ed25519, or RSA_PSS_SHA256
signaturestringBase64-encoded signature
signedAtISO 8601Signature timestamp
certificateChainarrayPEM-encoded certificate chain

Determinism

Bundles are deterministic - same attestations produce same bundle:

  1. Attestation ordering: Sorted by entryId lexicographically
  2. Merkle tree: Leaves computed as SHA256(canonicalized_attestation_json)
  3. Bundle ID: Derived from Merkle root: sha256:<merkle_root>
  4. JSON serialization: Canonical ordering (sorted keys, no whitespace)

Verification

Full Bundle Verification

using StellaOps.Attestor.Bundling.Verification;

var verifier = new AttestationBundleVerifier();
var result = await verifier.VerifyAsync(bundle);

if (result.Valid)
{
    Console.WriteLine($"Merkle root verified: {result.MerkleRootVerified}");
    Console.WriteLine($"Org signature verified: {result.OrgSignatureVerified}");
    Console.WriteLine($"Attestations verified: {result.AttestationsVerified}");
}

Individual Attestation Verification

// Extract single attestation for verification
var attestation = bundle.Attestations.First(a => a.ArtifactDigest == targetDigest);

// Verify inclusion proof against Rekor
var proofValid = await RekorVerifier.VerifyInclusionAsync(
    attestation.RekorLogIndex,
    attestation.InclusionProof);

// Verify DSSE envelope signature
var sigValid = await DsseVerifier.VerifyAsync(
    attestation.Envelope,
    attestation.SigningIdentity);

Storage

S3/Object Storage

attestor:
  bundling:
    storage:
      backend: s3
      s3:
        bucket: stellaops-attestor
        prefix: bundles/
        objectLock: governance  # WORM protection
        storageClass: STANDARD

Filesystem

attestor:
  bundling:
    storage:
      backend: filesystem
      filesystem:
        path: /var/lib/stellaops/attestor/bundles
        directoryPermissions: "0750"
        filePermissions: "0640"

Retention

Bundles follow configurable retention policies:

SettingDefaultDescription
defaultMonths24Standard retention period
minimumMonths6Cannot be reduced below this
maximumMonths120Maximum allowed retention

Tenant Overrides

attestor:
  bundling:
    retention:
      defaultMonths: 24
      tenantOverrides:
        tenant-gov: 84      # 7 years
        tenant-finance: 120  # 10 years

Export Formats

JSON (Default)

Human-readable, suitable for debugging and audit:

stella attestor bundle export --format json bundle-sha256-abc.json

CBOR

Compact binary format (~40% smaller):

stella attestor bundle export --format cbor bundle-sha256-abc.cbor

Compression

Both formats support compression:

attestor:
  bundling:
    export:
      compression: zstd  # none | gzip | zstd
      compressionLevel: 3

References