docs consolidation
This commit is contained in:
@@ -26,7 +26,7 @@
|
||||
|
||||
* **Signer** (caller) — authenticated via **mTLS** and **Authority** OpToks.
|
||||
* **Rekor v2** — tile‑backed transparency log endpoint(s).
|
||||
* **MinIO (S3)** — optional archive store for DSSE envelopes & verification bundles.
|
||||
* **RustFS (S3-compatible)** — optional archive store for DSSE envelopes & verification bundles.
|
||||
* **PostgreSQL** — local cache of `{uuid, index, proof, artifactSha256, bundleSha256}`; job state; audit.
|
||||
* **Valkey** — dedupe/idempotency keys and short‑lived rate‑limit buckets.
|
||||
* **Licensing Service (optional)** — “endorse” call for cross‑log publishing when customer opts‑in.
|
||||
@@ -615,7 +615,7 @@ attestor:
|
||||
connectionString: "Host=postgres;Port=5432;Database=attestor;Username=stellaops;Password=secret"
|
||||
s3:
|
||||
enabled: true
|
||||
endpoint: "http://minio:9000"
|
||||
endpoint: "http://rustfs:8080"
|
||||
bucket: "stellaops"
|
||||
prefix: "attest/"
|
||||
objectLock: "governance"
|
||||
|
||||
239
docs/modules/attestor/bundle-format.md
Normal file
239
docs/modules/attestor/bundle-format.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# 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": "<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
|
||||
|
||||
```json
|
||||
{
|
||||
"certificate": {
|
||||
"rawBytes": "<base64-DER-certificate>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Public Key (alternative to certificate)
|
||||
|
||||
```json
|
||||
{
|
||||
"publicKey": {
|
||||
"hint": "<optional-key-hint>",
|
||||
"rawBytes": "<base64-public-key>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Transparency Log Entry
|
||||
|
||||
```json
|
||||
{
|
||||
"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:
|
||||
|
||||
```json
|
||||
{
|
||||
"payloadType": "application/vnd.in-toto+json",
|
||||
"payload": "<base64-encoded-attestation>",
|
||||
"signatures": [
|
||||
{
|
||||
"sig": "<base64-signature>",
|
||||
"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.
|
||||
|
||||
## 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)
|
||||
374
docs/modules/attestor/cosign-verification-examples.md
Normal file
374
docs/modules/attestor/cosign-verification-examples.md
Normal file
@@ -0,0 +1,374 @@
|
||||
# Cosign Verification Examples
|
||||
|
||||
This document provides examples for verifying StellaOps DSSE attestations using Sigstore cosign.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Install Cosign
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
brew install cosign
|
||||
|
||||
# Linux (download latest release)
|
||||
curl -sSfL https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64 -o cosign
|
||||
chmod +x cosign
|
||||
sudo mv cosign /usr/local/bin/
|
||||
|
||||
# Windows (download from releases page)
|
||||
# https://github.com/sigstore/cosign/releases
|
||||
|
||||
# Verify installation
|
||||
cosign version
|
||||
```
|
||||
|
||||
### Required Files
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `attestation.json` | DSSE envelope exported from StellaOps |
|
||||
| `public.key` | Public key for keyful verification |
|
||||
| `trusted_root.json` | Sigstore TUF root for keyless verification |
|
||||
|
||||
## Export Attestation from StellaOps
|
||||
|
||||
```bash
|
||||
# Export attestation for a specific artifact
|
||||
stellaops attestation export \
|
||||
--artifact sha256:abc123... \
|
||||
--output attestation.json
|
||||
|
||||
# Export with certificate chain
|
||||
stellaops attestation export \
|
||||
--artifact sha256:abc123... \
|
||||
--include-certificate-chain \
|
||||
--output attestation-bundle.json
|
||||
|
||||
# Export as Sigstore bundle
|
||||
stellaops attestation export \
|
||||
--artifact sha256:abc123... \
|
||||
--format sigstore-bundle \
|
||||
--output attestation.sigstore.json
|
||||
```
|
||||
|
||||
## Keyful Verification (KMS/HSM Keys)
|
||||
|
||||
### Verify with Public Key
|
||||
|
||||
```bash
|
||||
# Basic verification
|
||||
cosign verify-attestation \
|
||||
--key public.key \
|
||||
--type custom \
|
||||
sha256:abc123...
|
||||
|
||||
# Verify from exported attestation file
|
||||
cosign verify-attestation \
|
||||
--key public.key \
|
||||
--type custom \
|
||||
--attestation attestation.json \
|
||||
sha256:abc123...
|
||||
```
|
||||
|
||||
### Verify with KMS Key
|
||||
|
||||
```bash
|
||||
# AWS KMS
|
||||
cosign verify-attestation \
|
||||
--key awskms:///arn:aws:kms:us-east-1:123456789:key/abc-123 \
|
||||
--type custom \
|
||||
sha256:abc123...
|
||||
|
||||
# GCP KMS
|
||||
cosign verify-attestation \
|
||||
--key gcpkms://projects/my-project/locations/global/keyRings/my-ring/cryptoKeys/my-key \
|
||||
--type custom \
|
||||
sha256:abc123...
|
||||
|
||||
# Azure Key Vault
|
||||
cosign verify-attestation \
|
||||
--key azurekms://mykeyvault.vault.azure.net/keys/mykey \
|
||||
--type custom \
|
||||
sha256:abc123...
|
||||
|
||||
# HashiCorp Vault
|
||||
cosign verify-attestation \
|
||||
--key hashivault://transit/keys/my-key \
|
||||
--type custom \
|
||||
sha256:abc123...
|
||||
```
|
||||
|
||||
## Keyless Verification (Fulcio/OIDC)
|
||||
|
||||
### Verify with Certificate Identity
|
||||
|
||||
```bash
|
||||
# Verify with issuer and subject
|
||||
cosign verify-attestation \
|
||||
--certificate-identity "signer@example.com" \
|
||||
--certificate-oidc-issuer "https://accounts.google.com" \
|
||||
--type custom \
|
||||
sha256:abc123...
|
||||
|
||||
# Verify with identity regex
|
||||
cosign verify-attestation \
|
||||
--certificate-identity-regexp ".*@stellaops\.io" \
|
||||
--certificate-oidc-issuer "https://github.com/login/oauth" \
|
||||
--type custom \
|
||||
sha256:abc123...
|
||||
```
|
||||
|
||||
### Verify GitHub Actions Workload Identity
|
||||
|
||||
```bash
|
||||
cosign verify-attestation \
|
||||
--certificate-identity "https://github.com/org/repo/.github/workflows/build.yml@refs/heads/main" \
|
||||
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
|
||||
--type custom \
|
||||
sha256:abc123...
|
||||
```
|
||||
|
||||
## Verify Specific Predicate Types
|
||||
|
||||
### StellaOps Attestation Types
|
||||
|
||||
```bash
|
||||
# Verify SBOM attestation
|
||||
cosign verify-attestation \
|
||||
--key public.key \
|
||||
--type "https://spdx.dev/Document" \
|
||||
sha256:abc123...
|
||||
|
||||
# Verify SLSA Provenance
|
||||
cosign verify-attestation \
|
||||
--key public.key \
|
||||
--type "https://slsa.dev/provenance/v1" \
|
||||
sha256:abc123...
|
||||
|
||||
# Verify StellaOps scan results
|
||||
cosign verify-attestation \
|
||||
--key public.key \
|
||||
--type "https://stella-ops.org/attestation/scan-results/v1" \
|
||||
sha256:abc123...
|
||||
|
||||
# Verify StellaOps policy evaluation
|
||||
cosign verify-attestation \
|
||||
--key public.key \
|
||||
--type "https://stella-ops.org/attestation/policy-evaluation/v1" \
|
||||
sha256:abc123...
|
||||
|
||||
# Verify graph root attestation
|
||||
cosign verify-attestation \
|
||||
--key public.key \
|
||||
--type "https://stella-ops.org/attestation/graph-root/v1" \
|
||||
sha256:abc123...
|
||||
```
|
||||
|
||||
## Offline Verification
|
||||
|
||||
### Verify with Cached Bundle
|
||||
|
||||
```bash
|
||||
# Verify using a Sigstore bundle (includes certificate and Rekor entry)
|
||||
cosign verify-attestation \
|
||||
--bundle attestation.sigstore.json \
|
||||
--certificate-identity "signer@example.com" \
|
||||
--certificate-oidc-issuer "https://accounts.google.com" \
|
||||
sha256:abc123...
|
||||
```
|
||||
|
||||
### Verify with Local TUF Root
|
||||
|
||||
```bash
|
||||
# Initialize TUF root (run once)
|
||||
cosign initialize --mirror https://tuf-repo.sigstore.dev --root root.json
|
||||
|
||||
# Verify using local TUF data
|
||||
SIGSTORE_ROOT_FILE=trusted_root.json \
|
||||
cosign verify-attestation \
|
||||
--certificate-identity "signer@example.com" \
|
||||
--certificate-oidc-issuer "https://accounts.google.com" \
|
||||
sha256:abc123...
|
||||
```
|
||||
|
||||
### Air-Gapped Verification
|
||||
|
||||
```bash
|
||||
# 1. On connected machine: download required artifacts
|
||||
cosign download attestation sha256:abc123... > attestation.json
|
||||
cosign download signature sha256:abc123... > signature.sig
|
||||
|
||||
# 2. Transfer files to air-gapped environment
|
||||
|
||||
# 3. On air-gapped machine: verify with public key
|
||||
cosign verify-attestation \
|
||||
--key public.key \
|
||||
--offline \
|
||||
--type custom \
|
||||
--attestation attestation.json \
|
||||
sha256:abc123...
|
||||
```
|
||||
|
||||
## Verify with Policy
|
||||
|
||||
### CUE Policy
|
||||
|
||||
```cue
|
||||
// policy.cue
|
||||
package attestation
|
||||
|
||||
predicateType: "https://stella-ops.org/attestation/scan-results/v1"
|
||||
predicate: {
|
||||
severity: *"low" | "medium" | "high" | "critical"
|
||||
vulnerabilities: [...{
|
||||
id: =~"^CVE-"
|
||||
severity: !="critical"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
cosign verify-attestation \
|
||||
--key public.key \
|
||||
--type custom \
|
||||
--policy policy.cue \
|
||||
sha256:abc123...
|
||||
```
|
||||
|
||||
### Rego Policy
|
||||
|
||||
```rego
|
||||
# policy.rego
|
||||
package attestation
|
||||
|
||||
default allow = false
|
||||
|
||||
allow {
|
||||
input.predicateType == "https://stella-ops.org/attestation/policy-evaluation/v1"
|
||||
input.predicate.verdict == "PASS"
|
||||
input.predicate.score >= 7.0
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
cosign verify-attestation \
|
||||
--key public.key \
|
||||
--type custom \
|
||||
--policy policy.rego \
|
||||
sha256:abc123...
|
||||
```
|
||||
|
||||
## Multi-Signature Verification
|
||||
|
||||
```bash
|
||||
# Verify that multiple signatures are present
|
||||
cosign verify-attestation \
|
||||
--key builder.pub \
|
||||
--type custom \
|
||||
sha256:abc123... && \
|
||||
cosign verify-attestation \
|
||||
--key witness.pub \
|
||||
--type custom \
|
||||
sha256:abc123...
|
||||
```
|
||||
|
||||
## Output Formats
|
||||
|
||||
### JSON Output
|
||||
|
||||
```bash
|
||||
cosign verify-attestation \
|
||||
--key public.key \
|
||||
--type custom \
|
||||
--output-file verification-result.json \
|
||||
sha256:abc123...
|
||||
```
|
||||
|
||||
### Text Output with Details
|
||||
|
||||
```bash
|
||||
cosign verify-attestation \
|
||||
--key public.key \
|
||||
--type custom \
|
||||
-v \
|
||||
sha256:abc123...
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Errors
|
||||
|
||||
| Error | Cause | Solution |
|
||||
|-------|-------|----------|
|
||||
| `no matching attestation found` | No attestation attached to image | Verify attestation was uploaded |
|
||||
| `key verification failed` | Wrong key or corrupted signature | Check key matches signer |
|
||||
| `certificate expired` | Signing certificate past validity | Use Rekor timestamp verification |
|
||||
| `OIDC issuer mismatch` | Wrong issuer in verify command | Check certificate's issuer field |
|
||||
| `predicate type mismatch` | Wrong --type argument | Use correct predicate URI |
|
||||
|
||||
### Debug Commands
|
||||
|
||||
```bash
|
||||
# List all attestations on an image
|
||||
cosign tree sha256:abc123...
|
||||
|
||||
# Download and inspect attestation
|
||||
cosign download attestation sha256:abc123... | jq .
|
||||
|
||||
# Verify with verbose output
|
||||
cosign verify-attestation \
|
||||
--key public.key \
|
||||
--type custom \
|
||||
-v \
|
||||
sha256:abc123... 2>&1 | tee verify.log
|
||||
|
||||
# Check certificate chain
|
||||
cosign download attestation sha256:abc123... | \
|
||||
jq -r '.payload' | base64 -d | jq -r '.subject'
|
||||
```
|
||||
|
||||
### Verify Certificate Details
|
||||
|
||||
```bash
|
||||
# Extract and inspect the signing certificate
|
||||
cosign download attestation sha256:abc123... | \
|
||||
jq -r '.signatures[0].cert' | base64 -d | \
|
||||
openssl x509 -noout -text
|
||||
```
|
||||
|
||||
## Integration with CI/CD
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
```yaml
|
||||
- name: Verify attestation
|
||||
uses: sigstore/cosign-installer@main
|
||||
|
||||
- name: Verify StellaOps attestation
|
||||
run: |
|
||||
cosign verify-attestation \
|
||||
--certificate-identity "https://github.com/${{ github.repository }}/.github/workflows/build.yml@${{ github.ref }}" \
|
||||
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
|
||||
--type "https://stella-ops.org/attestation/scan-results/v1" \
|
||||
${{ env.IMAGE_DIGEST }}
|
||||
```
|
||||
|
||||
### GitLab CI
|
||||
|
||||
```yaml
|
||||
verify-attestation:
|
||||
image: bitnami/cosign:latest
|
||||
script:
|
||||
- cosign verify-attestation
|
||||
--certificate-identity "https://gitlab.com/${CI_PROJECT_PATH}/.gitlab-ci.yml@${CI_COMMIT_REF_NAME}"
|
||||
--certificate-oidc-issuer "https://gitlab.com"
|
||||
--type "https://stella-ops.org/attestation/scan-results/v1"
|
||||
${IMAGE_DIGEST}
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [DSSE Round-Trip Verification](./dsse-roundtrip-verification.md)
|
||||
- [Transparency Log Integration](./transparency.md)
|
||||
- [Air-Gap Operation](./airgap.md)
|
||||
- [Sigstore Documentation](https://docs.sigstore.dev)
|
||||
292
docs/modules/attestor/dsse-roundtrip-verification.md
Normal file
292
docs/modules/attestor/dsse-roundtrip-verification.md
Normal file
@@ -0,0 +1,292 @@
|
||||
# DSSE Round-Trip Verification
|
||||
|
||||
This document describes the DSSE (Dead Simple Signing Envelope) round-trip verification process in StellaOps, including bundling, offline verification, and cosign compatibility.
|
||||
|
||||
## Overview
|
||||
|
||||
DSSE round-trip verification ensures that attestations can be:
|
||||
1. Created and signed
|
||||
2. Serialized to persistent storage
|
||||
3. Deserialized and rebundled
|
||||
4. Verified offline without network access
|
||||
5. Verified by external tools (cosign)
|
||||
|
||||
## Round-Trip Process
|
||||
|
||||
### Standard Flow
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
|
||||
│ Payload │───>│ Sign │───>│ DSSE │
|
||||
│ (in-toto) │ │ │ │ Envelope │
|
||||
└─────────────┘ └──────────────┘ └──────┬──────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
|
||||
│ Verify │<───│ Deserialize │<───│ Bundle │
|
||||
│ (Pass) │ │ │ │ (JSON) │
|
||||
└──────┬──────┘ └──────────────┘ └─────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
|
||||
│ Re-bundle │───>│ Serialize │───>│ Archive │
|
||||
│ │ │ │ │ (.tar.gz) │
|
||||
└──────┬──────┘ └──────────────┘ └─────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ Re-verify │
|
||||
│ (Pass) │
|
||||
└─────────────┘
|
||||
```
|
||||
|
||||
### Steps
|
||||
|
||||
1. **Create Payload**: Build an in-toto statement with subject digests and predicate
|
||||
2. **Sign**: Create DSSE envelope with signature(s)
|
||||
3. **Bundle**: Wrap envelope in Sigstore-compatible bundle format
|
||||
4. **Serialize**: Convert to JSON bytes
|
||||
5. **Deserialize**: Parse JSON back to bundle structure
|
||||
6. **Extract**: Get DSSE envelope from bundle
|
||||
7. **Re-bundle**: Create new bundle from extracted envelope
|
||||
8. **Re-verify**: Confirm signature validity
|
||||
|
||||
## Verification Types
|
||||
|
||||
### Signature Verification
|
||||
|
||||
Verifies the cryptographic signature against the payload:
|
||||
|
||||
```csharp
|
||||
var result = await signatureService.VerifyAsync(envelope, cancellationToken);
|
||||
if (!result.IsValid)
|
||||
{
|
||||
throw new VerificationException(result.Error);
|
||||
}
|
||||
```
|
||||
|
||||
### Payload Integrity
|
||||
|
||||
Verifies the payload hash matches the signed content:
|
||||
|
||||
```csharp
|
||||
var computedHash = SHA256.HashData(envelope.Payload.Span);
|
||||
var declaredHash = ParseDigest(envelope.PayloadDigest);
|
||||
if (!computedHash.SequenceEqual(declaredHash))
|
||||
{
|
||||
throw new IntegrityException("Payload hash mismatch");
|
||||
}
|
||||
```
|
||||
|
||||
### Certificate Chain Validation
|
||||
|
||||
For keyless (Fulcio) signatures:
|
||||
|
||||
```csharp
|
||||
var chain = envelope.Signatures[0].CertificateChain;
|
||||
var result = await certificateValidator.ValidateAsync(chain, options);
|
||||
// Checks: expiry, revocation, issuer, extended key usage
|
||||
```
|
||||
|
||||
## Determinism Requirements
|
||||
|
||||
Round-trip verification requires deterministic serialization:
|
||||
|
||||
| Property | Requirement |
|
||||
|----------|-------------|
|
||||
| Key Order | Alphabetical |
|
||||
| Whitespace | No trailing whitespace, single space after colons |
|
||||
| Encoding | UTF-8, no BOM |
|
||||
| Numbers | No unnecessary trailing zeros |
|
||||
| Arrays | Stable ordering |
|
||||
|
||||
### Verification
|
||||
|
||||
```csharp
|
||||
var bytes1 = CanonJson.CanonicalizeVersioned(envelope);
|
||||
var envelope2 = JsonSerializer.Deserialize<DsseEnvelope>(bytes1);
|
||||
var bytes2 = CanonJson.CanonicalizeVersioned(envelope2);
|
||||
|
||||
// bytes1 and bytes2 must be identical
|
||||
Assert.Equal(bytes1.ToArray(), bytes2.ToArray());
|
||||
```
|
||||
|
||||
## Multi-Signature Support
|
||||
|
||||
DSSE envelopes can contain multiple signatures from different signers:
|
||||
|
||||
```csharp
|
||||
var envelope = new DsseEnvelope(
|
||||
payloadType: "application/vnd.in-toto+json",
|
||||
payload: canonicalPayload,
|
||||
signatures:
|
||||
[
|
||||
signerA.Sign(canonicalPayload), // Builder signature
|
||||
signerB.Sign(canonicalPayload), // Witness signature
|
||||
signerC.Sign(canonicalPayload) // Approver signature
|
||||
]);
|
||||
```
|
||||
|
||||
Verification checks all signatures:
|
||||
|
||||
```csharp
|
||||
foreach (var signature in envelope.Signatures)
|
||||
{
|
||||
var result = await VerifySignatureAsync(envelope.Payload, signature);
|
||||
if (!result.IsValid)
|
||||
{
|
||||
return VerificationResult.Failure(
|
||||
$"Signature {signature.KeyId} failed: {result.Error}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Cosign Compatibility
|
||||
|
||||
StellaOps DSSE envelopes are compatible with Sigstore cosign for verification.
|
||||
|
||||
### Envelope Format
|
||||
|
||||
```json
|
||||
{
|
||||
"payloadType": "application/vnd.in-toto+json",
|
||||
"payload": "<base64-encoded-payload>",
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "SHA256:abc123...",
|
||||
"sig": "<base64-encoded-signature>"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Cosign Verification Commands
|
||||
|
||||
See [Cosign Verification Examples](./cosign-verification-examples.md) for detailed commands.
|
||||
|
||||
## Offline Verification
|
||||
|
||||
For air-gapped environments, verification can proceed without network access:
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. **Trusted Root**: Pre-downloaded Sigstore TUF root
|
||||
2. **Cached Certificates**: Fulcio certificate chain
|
||||
3. **Rekor Entry** (optional): Cached transparency log entry
|
||||
|
||||
### Offline Flow
|
||||
|
||||
```csharp
|
||||
var verifier = new OfflineVerifier(
|
||||
trustedRoot: TrustedRoot.LoadFromFile("trusted_root.json"),
|
||||
rekorEntries: RekorCache.LoadFromDirectory("rekor-cache/"));
|
||||
|
||||
var result = await verifier.VerifyAsync(envelope);
|
||||
```
|
||||
|
||||
### Bundle for Offline Use
|
||||
|
||||
```bash
|
||||
# Export attestation bundle with all dependencies
|
||||
stellaops export attestation-bundle \
|
||||
--artifact sha256:abc123... \
|
||||
--include-certificate-chain \
|
||||
--include-rekor-entry \
|
||||
--output bundle.tar.gz
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Common Verification Failures
|
||||
|
||||
| Error | Cause | Resolution |
|
||||
|-------|-------|------------|
|
||||
| `SignatureInvalid` | Signature doesn't match payload | Re-sign with correct key |
|
||||
| `CertificateExpired` | Signing certificate expired | Use Rekor entry timestamp |
|
||||
| `PayloadTampered` | Payload modified after signing | Restore original payload |
|
||||
| `KeyNotTrusted` | Key not in trusted set | Add key to trust policy |
|
||||
| `ParseError` | Malformed envelope JSON | Validate envelope format |
|
||||
|
||||
### Example Error Handling
|
||||
|
||||
```csharp
|
||||
try
|
||||
{
|
||||
var result = await verifier.VerifyAsync(envelope);
|
||||
if (!result.IsValid)
|
||||
{
|
||||
logger.LogWarning("Verification failed: {Reason}", result.FailureReason);
|
||||
// Handle policy violation
|
||||
}
|
||||
}
|
||||
catch (CertificateExpiredException ex)
|
||||
{
|
||||
// Fall back to Rekor timestamp verification
|
||||
var result = await verifier.VerifyWithRekorTimestampAsync(
|
||||
envelope, ex.Certificate, rekorEntry);
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
logger.LogError(ex, "Failed to parse envelope");
|
||||
throw new VerificationException("Malformed envelope", ex);
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Round-Trip Verification
|
||||
|
||||
### Unit Test Example
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public async Task RoundTrip_SignVerifyRebundle_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
var payload = CreateInTotoStatement();
|
||||
var signer = CreateTestSigner();
|
||||
|
||||
// Act - Sign
|
||||
var envelope = await signer.SignAsync(payload);
|
||||
|
||||
// Act - Serialize and deserialize
|
||||
var json = JsonSerializer.Serialize(envelope);
|
||||
var restored = JsonSerializer.Deserialize<DsseEnvelope>(json);
|
||||
|
||||
// Act - Verify restored envelope
|
||||
var result = await signer.VerifyAsync(restored);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeTrue();
|
||||
restored.Payload.ToArray().Should().Equal(envelope.Payload.ToArray());
|
||||
}
|
||||
```
|
||||
|
||||
### Integration Test Example
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public async Task RoundTrip_ArchiveExtractVerify_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
var envelope = await CreateSignedEnvelope();
|
||||
var archive = new AttestationArchive();
|
||||
|
||||
// Act - Archive
|
||||
var archivePath = Path.GetTempFileName() + ".tar.gz";
|
||||
await archive.WriteAsync(envelope, archivePath);
|
||||
|
||||
// Act - Extract and verify
|
||||
var extracted = await archive.ReadAsync(archivePath);
|
||||
var result = await verifier.VerifyAsync(extracted);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeTrue();
|
||||
}
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Proof Chain Specification](./proof-chain-specification.md)
|
||||
- [Graph Root Attestation](./graph-root-attestation.md)
|
||||
- [Transparency Log Integration](./transparency.md)
|
||||
- [Air-Gap Operation](./airgap.md)
|
||||
- [Cosign Verification Examples](./cosign-verification-examples.md)
|
||||
Reference in New Issue
Block a user