- Add RateLimitConfig for configuration management with YAML binding support. - Introduce RateLimitDecision to encapsulate the result of rate limit checks. - Implement RateLimitMetrics for OpenTelemetry metrics tracking. - Create RateLimitMiddleware for enforcing rate limits on incoming requests. - Develop RateLimitService to orchestrate instance and environment rate limit checks. - Add RateLimitServiceCollectionExtensions for dependency injection registration.
9.9 KiB
Proof Chain Verification in Air-Gap Mode
Version: 1.0.0
Last Updated: 2025-12-17
Related: Proof Chain API, Key Rotation Runbook
This document describes how to verify proof chains in air-gapped (offline) environments where Rekor transparency log access is unavailable.
Overview
Proof chains in StellaOps consist of cryptographically-linked attestations:
- Evidence statements - Raw vulnerability findings
- Reasoning statements - Policy evaluation traces
- VEX verdict statements - Final vulnerability status determinations
- Proof spine - Merkle tree aggregating all components
In online mode, proof chains include Rekor inclusion proofs for transparency. In air-gap mode, verification proceeds without Rekor but maintains cryptographic integrity.
Verification Levels
Level 1: Content-Addressed ID Verification
Verifies that content-addressed IDs match payload hashes.
# Verify a proof bundle ID
stellaops proof verify --offline \
--proof-bundle sha256:1a2b3c4d... \
--level content-id
# Expected output:
# ✓ Content-addressed ID verified
# ✓ Payload hash: sha256:1a2b3c4d...
Level 2: DSSE Signature Verification
Verifies DSSE envelope signatures against trust anchors.
# Verify signatures with local trust anchors
stellaops proof verify --offline \
--proof-bundle sha256:1a2b3c4d... \
--anchor-file /path/to/trust-anchors.json \
--level signature
# Expected output:
# ✓ DSSE signature valid
# ✓ Signer: key-2025-prod
# ✓ Trust anchor: 550e8400-e29b-41d4-a716-446655440000
Level 3: Merkle Path Verification
Verifies the proof spine merkle tree structure.
# Verify merkle paths
stellaops proof verify --offline \
--proof-bundle sha256:1a2b3c4d... \
--level merkle
# Expected output:
# ✓ Merkle root verified
# ✓ Evidence paths: 3/3 valid
# ✓ Reasoning path: valid
# ✓ VEX verdict path: valid
Level 4: Full Verification (Offline)
Performs all verification steps except Rekor.
# Full offline verification
stellaops proof verify --offline \
--proof-bundle sha256:1a2b3c4d... \
--anchor-file /path/to/trust-anchors.json
# Expected output:
# Proof Chain Verification
# ═══════════════════════
# ✓ Content-addressed IDs verified
# ✓ DSSE signatures verified (3 envelopes)
# ✓ Merkle paths verified
# ⊘ Rekor verification skipped (offline mode)
#
# Overall: VERIFIED (offline)
Trust Anchor Distribution
In air-gap environments, trust anchors must be distributed out-of-band.
Export Trust Anchors
# On the online system, export trust anchors
stellaops anchor export --format json > trust-anchors.json
# Verify export integrity
sha256sum trust-anchors.json > trust-anchors.sha256
Trust Anchor File Format
{
"version": "1.0",
"exportedAt": "2025-12-17T00:00:00Z",
"anchors": [
{
"trustAnchorId": "550e8400-e29b-41d4-a716-446655440000",
"purlPattern": "pkg:*",
"allowedKeyids": ["key-2024-prod", "key-2025-prod"],
"allowedPredicateTypes": [
"evidence.stella/v1",
"reasoning.stella/v1",
"cdx-vex.stella/v1",
"proofspine.stella/v1"
],
"revokedKeys": ["key-2023-prod"],
"keyMaterial": {
"key-2024-prod": {
"algorithm": "ECDSA-P256",
"publicKey": "-----BEGIN PUBLIC KEY-----\n..."
},
"key-2025-prod": {
"algorithm": "ECDSA-P256",
"publicKey": "-----BEGIN PUBLIC KEY-----\n..."
}
}
}
]
}
Import Trust Anchors
# On the air-gapped system
stellaops anchor import --file trust-anchors.json
# Verify import
stellaops anchor list
Proof Bundle Distribution
Export Proof Bundles
# Export a proof bundle for offline transfer
stellaops proof export \
--entry sha256:abc123:pkg:npm/lodash@4.17.21 \
--output proof-bundle.zip
# Bundle contents:
# proof-bundle.zip
# ├── proof-spine.json # The proof spine
# ├── evidence/ # Evidence statements
# │ ├── sha256_e1.json
# │ └── sha256_e2.json
# ├── reasoning.json # Reasoning statement
# ├── vex-verdict.json # VEX verdict statement
# ├── envelopes/ # DSSE envelopes
# │ ├── evidence-e1.dsse
# │ ├── evidence-e2.dsse
# │ ├── reasoning.dsse
# │ ├── vex-verdict.dsse
# │ └── proof-spine.dsse
# └── VERIFY.md # Verification instructions
Verify Exported Bundle
# On the air-gapped system
stellaops proof verify --offline \
--bundle-file proof-bundle.zip \
--anchor-file trust-anchors.json
Batch Verification
For audits, verify multiple proof bundles efficiently:
# Create a verification manifest
cat > verify-manifest.json << 'EOF'
{
"bundles": [
"sha256:1a2b3c4d...",
"sha256:5e6f7g8h...",
"sha256:9i0j1k2l..."
],
"options": {
"checkRekor": false,
"failFast": false
}
}
EOF
# Run batch verification
stellaops proof verify-batch \
--manifest verify-manifest.json \
--anchor-file trust-anchors.json \
--output verification-report.json
Verification Report Format
{
"verifiedAt": "2025-12-17T10:00:00Z",
"mode": "offline",
"anchorsUsed": ["550e8400..."],
"results": [
{
"proofBundleId": "sha256:1a2b3c4d...",
"verified": true,
"checks": {
"contentId": true,
"signature": true,
"merklePath": true,
"rekorInclusion": null
}
}
],
"summary": {
"total": 3,
"verified": 3,
"failed": 0,
"skipped": 0
}
}
Key Rotation in Air-Gap Mode
When keys are rotated, trust anchor updates must be distributed:
1. Export Updated Anchors
# On online system after key rotation
stellaops anchor export --since 2025-01-01 > anchor-update.json
sha256sum anchor-update.json > anchor-update.sha256
2. Verify and Import Update
# On air-gapped system
sha256sum -c anchor-update.sha256
stellaops anchor import --file anchor-update.json --merge
# Verify key history
stellaops anchor show --anchor-id 550e8400... --show-history
3. Temporal Verification
When verifying old proofs after key rotation:
# Verify proof signed with now-revoked key
stellaops proof verify --offline \
--proof-bundle sha256:old-proof... \
--anchor-file trust-anchors.json \
--at-time "2024-06-15T12:00:00Z"
# The verification uses key validity at the specified time
Manual Verification (No CLI)
For environments without the StellaOps CLI, manual verification is possible:
1. Verify Content-Addressed ID
# Extract payload from DSSE envelope
jq -r '.payload' proof-spine.dsse | base64 -d > payload.json
# Compute hash
sha256sum payload.json
# Compare with proof bundle ID
2. Verify DSSE Signature
#!/usr/bin/env python3
import json
import base64
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.serialization import load_pem_public_key
def verify_dsse(envelope_path, public_key_pem):
"""Verify a DSSE envelope signature."""
with open(envelope_path) as f:
envelope = json.load(f)
payload_type = envelope['payloadType']
payload = base64.b64decode(envelope['payload'])
# Build PAE (Pre-Authentication Encoding)
pae = f"DSSEv1 {len(payload_type)} {payload_type} {len(payload)} ".encode() + payload
public_key = load_pem_public_key(public_key_pem.encode())
for sig in envelope['signatures']:
signature = base64.b64decode(sig['sig'])
try:
public_key.verify(signature, pae, ec.ECDSA(hashes.SHA256()))
print(f"✓ Signature valid for keyid: {sig['keyid']}")
return True
except Exception as e:
print(f"✗ Signature invalid: {e}")
return False
3. Verify Merkle Path
#!/usr/bin/env python3
import json
import hashlib
def verify_merkle_path(leaf_hash, path, root_hash, leaf_index):
"""Verify a Merkle inclusion path."""
current = bytes.fromhex(leaf_hash)
index = leaf_index
for sibling in path:
sibling_bytes = bytes.fromhex(sibling)
if index % 2 == 0:
# Current is left child
combined = current + sibling_bytes
else:
# Current is right child
combined = sibling_bytes + current
current = hashlib.sha256(combined).digest()
index //= 2
computed_root = current.hex()
if computed_root == root_hash:
print("✓ Merkle path verified")
return True
else:
print(f"✗ Merkle root mismatch: {computed_root} != {root_hash}")
return False
Exit Codes
Offline verification uses the same exit codes as online:
| Code | Meaning | CI/CD Action |
|---|---|---|
| 0 | Verification passed | Proceed |
| 1 | Verification failed | Block |
| 2 | System error | Retry/investigate |
Troubleshooting
Missing Trust Anchor
Error: No trust anchor found for keyid "key-2025-prod"
Solution: Import updated trust anchors from online system.
Key Not Valid at Time
Error: Key "key-2024-prod" was revoked at 2024-12-01, before proof signature at 2025-01-15
Solution: This indicates the proof was signed after key revocation. Investigate the signature timestamp.
Merkle Path Invalid
Error: Merkle path verification failed for evidence sha256:e1...
Solution: The proof bundle may be corrupted. Re-export from online system.