Files
git.stella-ops.org/docs/modules/attestor/bundle-format.md
StellaOps Bot 907783f625 Add property-based tests for SBOM/VEX document ordering and Unicode normalization determinism
- Implement `SbomVexOrderingDeterminismProperties` for testing component list and vulnerability metadata hash consistency.
- Create `UnicodeNormalizationDeterminismProperties` to validate NFC normalization and Unicode string handling.
- Add project file for `StellaOps.Testing.Determinism.Properties` with necessary dependencies.
- Introduce CI/CD template validation tests including YAML syntax checks and documentation content verification.
- Create validation script for CI/CD templates ensuring all required files and structures are present.
2025-12-26 15:17:58 +02:00

13 KiB

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

  • v0.3: application/vnd.dev.sigstore.bundle.v0.3+json (current)
  • v0.2: application/vnd.dev.sigstore.bundle.v0.2+json (legacy)

Verification Material

Contains cryptographic material for verification:

Field Type Description
certificate Object X.509 signing certificate (keyless signing)
publicKey Object Public key (keyful signing, alternative to certificate)
tlogEntries Array Transparency log entries from Rekor
timestampVerificationData Object RFC 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

Check Description
DsseSignature Verifies DSSE signature using PAE encoding
CertificateChain Validates certificate validity period
InclusionProof Verifies Merkle inclusion proof (RFC 6962)
TransparencyLog Validates transparency log entry
Timestamp Verifies RFC 3161 timestamps (optional)

Error Codes

Code Description
InvalidBundleStructure Bundle JSON structure is invalid
MissingDsseEnvelope DSSE envelope is required
DsseSignatureInvalid Signature verification failed
MissingCertificate No certificate or public key
CertificateChainInvalid Certificate chain validation failed
CertificateExpired Certificate has expired
CertificateNotYetValid Certificate not yet valid
InclusionProofInvalid Merkle proof verification failed
RootHashMismatch Computed 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:

  • Long-term verification of keyless-signed artifacts after certificate expiry
  • Organizational endorsement via optional org-key signature
  • Offline verification with bundled Rekor inclusion proofs
  • Regulatory compliance with audit-ready evidence packages

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

Field Type Description
bundleId string Content-addressed ID: sha256:<merkle_root>
version string Bundle schema version (currently "1.0")
createdAt ISO 8601 Bundle creation timestamp (UTC)
periodStart ISO 8601 Start of attestation collection period
periodEnd ISO 8601 End of attestation collection period
attestationCount int Number of attestations in bundle
tenantId string Optional tenant identifier
orgKeyFingerprint string Fingerprint of org signing key (if signed)

Attestations

Each attestation entry contains:

Field Type Description
entryId string Unique entry identifier
rekorUuid string Rekor transparency log UUID
rekorLogIndex long Rekor log index
artifactDigest string SHA256 digest of attested artifact
predicateType string In-toto predicate type
signedAt ISO 8601 When attestation was signed
signingMode string keyless, kms, hsm, or fido2
signingIdentity object Signer identity information
inclusionProof object Rekor Merkle inclusion proof
envelope object DSSE envelope with signatures and certificates

Merkle Tree

Deterministic Merkle tree over attestation hashes:

Field Type Description
algorithm string Hash algorithm (always "SHA256")
root string Merkle root: sha256:<64-hex>
leafCount int Number of leaves (= attestation count)

Org Signature

Optional organizational endorsement:

Field Type Description
keyId string Signing key identifier
algorithm string ECDSA_P256, Ed25519, or RSA_PSS_SHA256
signature string Base64-encoded signature
signedAt ISO 8601 Signature timestamp
certificateChain array PEM-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:

Setting Default Description
defaultMonths 24 Standard retention period
minimumMonths 6 Cannot be reduced below this
maximumMonths 120 Maximum 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