Files
git.stella-ops.org/docs/airgap/proof-chain-verification.md
master 8bbfe4d2d2 feat(rate-limiting): Implement core rate limiting functionality with configuration, decision-making, metrics, middleware, and service registration
- 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.
2025-12-17 18:02:37 +02:00

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:

  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.

# 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.