up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-11-28 00:45:16 +02:00
parent 3b96b2e3ea
commit 1c6730a1d2
95 changed files with 14504 additions and 463 deletions

View File

@@ -0,0 +1,254 @@
# Portable Evidence Bundle Verification Guide
This document describes how Advisory AI teams can verify the integrity and authenticity of portable evidence bundles produced by StellaOps Excititor for sealed deployments.
## Overview
Portable evidence bundles are self-contained ZIP archives that include:
- Evidence locker manifest with cryptographic Merkle root
- DSSE attestation envelope (when signing is enabled)
- Raw evidence items organized by provider
- Audit timeline events
- Bundle manifest with content index
## Bundle Structure
```
evidence-bundle-{tenant}-{timestamp}.zip
├── manifest.json # VexLockerManifest with Merkle root
├── attestation.json # DSSE envelope (optional)
├── evidence/
│ └── {provider}/
│ └── sha256_{digest}.json
├── timeline.json # Audit timeline events
├── bundle-manifest.json # Index of all contents
└── VERIFY.md # Verification instructions
```
## Verification Steps
### Step 1: Extract and Validate Structure
```bash
# Extract the bundle
unzip evidence-bundle-*.zip -d evidence-bundle/
# Verify expected files exist
ls -la evidence-bundle/
# Should see: manifest.json, bundle-manifest.json, evidence/, timeline.json, VERIFY.md
```
### Step 2: Verify Evidence Item Integrity
Each evidence item's content hash must match its filename:
```bash
cd evidence-bundle/evidence
# For each provider directory
for provider in */; do
for file in "$provider"*.json; do
# Extract expected hash from filename (sha256_xxxx.json -> xxxx)
expected=$(basename "$file" .json | sed 's/sha256_//')
# Compute actual hash
actual=$(sha256sum "$file" | cut -d' ' -f1)
if [ "$expected" != "$actual" ]; then
echo "MISMATCH: $file"
fi
done
done
```
### Step 3: Verify Merkle Root
The Merkle root provides cryptographic proof that all evidence items are included without modification.
#### Python Verification Script
```python
#!/usr/bin/env python3
import json
import hashlib
from pathlib import Path
def compute_merkle_root(hashes):
"""Compute Merkle root from list of hex hashes."""
if len(hashes) == 0:
return hashlib.sha256(b'').hexdigest()
if len(hashes) == 1:
return hashes[0]
# Pad to even number
if len(hashes) % 2 != 0:
hashes = hashes + [hashes[-1]]
# Compute next level
next_level = []
for i in range(0, len(hashes), 2):
combined = bytes.fromhex(hashes[i] + hashes[i+1])
next_level.append(hashlib.sha256(combined).hexdigest())
return compute_merkle_root(next_level)
def verify_bundle(bundle_path):
"""Verify a portable evidence bundle."""
bundle_path = Path(bundle_path)
# Load manifest
with open(bundle_path / 'manifest.json') as f:
manifest = json.load(f)
# Extract hashes, sorted by observationId then providerId
items = sorted(manifest['items'],
key=lambda x: (x['observationId'], x['providerId'].lower()))
hashes = []
for item in items:
content_hash = item['contentHash']
# Strip sha256: prefix if present
if content_hash.startswith('sha256:'):
content_hash = content_hash[7:]
hashes.append(content_hash.lower())
# Compute Merkle root
computed_root = 'sha256:' + compute_merkle_root(hashes)
expected_root = manifest['merkleRoot']
if computed_root == expected_root:
print(f"✓ Merkle root verified: {computed_root}")
return True
else:
print(f"✗ Merkle root mismatch!")
print(f" Expected: {expected_root}")
print(f" Computed: {computed_root}")
return False
if __name__ == '__main__':
import sys
if len(sys.argv) != 2:
print(f"Usage: {sys.argv[0]} <bundle-directory>")
sys.exit(1)
success = verify_bundle(sys.argv[1])
sys.exit(0 if success else 1)
```
### Step 4: Verify Attestation (if present)
When `attestation.json` exists, verify the DSSE envelope:
```bash
# Check if attestation exists
if [ -f "evidence-bundle/attestation.json" ]; then
# Extract attestation metadata
jq '.' evidence-bundle/attestation.json
# Verify signature using appropriate tool
# For Sigstore/cosign attestations:
# cosign verify-attestation --type custom ...
fi
```
#### Attestation Fields
| Field | Description |
|-------|-------------|
| `dsseEnvelope` | Base64-encoded DSSE envelope |
| `envelopeDigest` | SHA-256 hash of the envelope |
| `predicateType` | in-toto predicate type URI |
| `signatureType` | Signature algorithm (e.g., "ES256") |
| `keyId` | Signing key identifier |
| `issuer` | Certificate issuer |
| `subject` | Certificate subject |
| `signedAt` | Signing timestamp (ISO-8601) |
| `transparencyLogRef` | Rekor transparency log entry URL |
### Step 5: Validate Timeline
The timeline provides audit trail of bundle creation:
```bash
# View timeline events
jq '.' evidence-bundle/timeline.json
# Check for any failed events
jq '.[] | select(.errorCode != null)' evidence-bundle/timeline.json
```
#### Timeline Event Types
| Event Type | Description |
|------------|-------------|
| `airgap.import.started` | Bundle import initiated |
| `airgap.import.completed` | Import succeeded |
| `airgap.import.failed` | Import failed (check errorCode) |
## Error Codes Reference
| Code | Description | Resolution |
|------|-------------|------------|
| `AIRGAP_EGRESS_BLOCKED` | External URL blocked in sealed mode | Use mirror/portable media |
| `AIRGAP_SOURCE_UNTRUSTED` | Publisher not allowlisted | Contact administrator |
| `AIRGAP_SIGNATURE_MISSING` | Required signature absent | Re-export with signing |
| `AIRGAP_SIGNATURE_INVALID` | Signature verification failed | Check key/certificate |
| `AIRGAP_PAYLOAD_STALE` | Timestamp exceeds tolerance | Re-create bundle |
| `AIRGAP_PAYLOAD_MISMATCH` | Hash doesn't match metadata | Verify transfer integrity |
## Advisory AI Integration
### Quick Integrity Check
For automated pipelines, use the bundle manifest:
```python
import json
with open('bundle-manifest.json') as f:
manifest = json.load(f)
# Key fields for Advisory AI
print(f"Bundle ID: {manifest['bundleId']}")
print(f"Merkle Root: {manifest['merkleRoot']}")
print(f"Item Count: {manifest['itemCount']}")
print(f"Has Attestation: {manifest['hasAttestation']}")
```
### Evidence Lookup
Find evidence for specific observations:
```python
# Index evidence by observation ID
evidence_index = {e['observationId']: e for e in manifest['evidence']}
# Lookup specific observation
obs_id = 'obs-123-abc'
if obs_id in evidence_index:
entry = evidence_index[obs_id]
file_path = f"evidence/{entry['providerId']}/sha256_{entry['contentHash'][7:]}.json"
```
### Provenance Chain
Build complete provenance from bundle:
1. `bundle-manifest.json` → Bundle creation metadata
2. `manifest.json` → Evidence locker snapshot
3. `attestation.json` → Cryptographic attestation
4. `timeline.json` → Audit trail
## Offline Verification
For fully air-gapped environments:
1. Transfer bundle via approved media
2. Extract to isolated verification system
3. Run verification scripts without network
4. Document verification results for audit
## Support
For questions or issues:
- Review bundle contents with `jq` and standard Unix tools
- Check timeline for error codes and messages
- Contact StellaOps support with bundle ID and merkle root