# Proof Chain Verification in Air-Gap Mode > **Version**: 1.0.0 > **Last Updated**: 2025-12-17 > **Related**: [Proof Chain API](../api/proofs.md), [Key Rotation Runbook](../operations/key-rotation-runbook.md) 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: 1. **Evidence statements** - Raw vulnerability findings 2. **Reasoning statements** - Policy evaluation traces 3. **VEX verdict statements** - Final vulnerability status determinations 4. **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. ```bash # 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. ```bash # 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. ```bash # 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. ```bash # 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 ```bash # 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 ```json { "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 ```bash # On the air-gapped system stellaops anchor import --file trust-anchors.json # Verify import stellaops anchor list ``` --- ## Proof Bundle Distribution ### Export Proof Bundles ```bash # 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 ```bash # 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: ```bash # 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 ```json { "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 ```bash # 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 ```bash # 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: ```bash # 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 ```bash # 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 ```python #!/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 ```python #!/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. --- ## Related Documentation - [Proof Chain API Reference](../api/proofs.md) - [Key Rotation Runbook](../operations/key-rotation-runbook.md) - [Portable Evidence Bundle Verification](portable-evidence-bundle-verification.md) - [Offline Bundle Format](offline-bundle-format.md)