# 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 ```json { "mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial": { "certificate": { ... }, "tlogEntries": [ ... ], "timestampVerificationData": { ... } }, "dsseEnvelope": { "payloadType": "application/vnd.in-toto+json", "payload": "", "signatures": [ { "sig": "", "keyid": "" } ] } } ``` ## 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 ```json { "certificate": { "rawBytes": "" } } ``` #### Public Key (alternative to certificate) ```json { "publicKey": { "hint": "", "rawBytes": "" } } ``` ### Transparency Log Entry ```json { "logIndex": "12345", "logId": { "keyId": "" }, "kindVersion": { "kind": "dsse", "version": "0.0.1" }, "integratedTime": "1703500000", "inclusionPromise": { "signedEntryTimestamp": "" }, "inclusionProof": { "logIndex": "12345", "rootHash": "", "treeSize": "100000", "hashes": ["", ...], "checkpoint": { "envelope": "" } }, "canonicalizedBody": "" } ``` ### DSSE Envelope Standard DSSE envelope format: ```json { "payloadType": "application/vnd.in-toto+json", "payload": "", "signatures": [ { "sig": "", "keyid": "" } ] } ``` ## Usage ### Building a Bundle ```csharp 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 ```csharp 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 ```csharp 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 ```csharp 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: ```bash # 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](./cosign-verification-examples.md) 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 ```json { "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:` | | `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:` 4. **JSON serialization**: Canonical ordering (sorted keys, no whitespace) ## Verification ### Full Bundle Verification ```csharp 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 ```csharp // 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 ```yaml attestor: bundling: storage: backend: s3 s3: bucket: stellaops-attestor prefix: bundles/ objectLock: governance # WORM protection storageClass: STANDARD ``` ### Filesystem ```yaml 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 ```yaml 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: ```bash stella attestor bundle export --format json bundle-sha256-abc.json ``` ### CBOR Compact binary format (~40% smaller): ```bash stella attestor bundle export --format cbor bundle-sha256-abc.cbor ``` ### Compression Both formats support compression: ```yaml attestor: bundling: export: compression: zstd # none | gzip | zstd compressionLevel: 3 ``` ## References - [Sigstore Bundle Specification](https://github.com/sigstore/cosign/blob/main/specs/BUNDLE_SPEC.md) - [Sigstore Protobuf Specs](https://github.com/sigstore/protobuf-specs) - [DSSE Specification](https://github.com/secure-systems-lab/dsse) - [RFC 6962 - Certificate Transparency](https://www.rfc-editor.org/rfc/rfc6962) - [Bundle Rotation Operations](./operations/bundle-rotation.md)