finish off sprint advisories and sprints
This commit is contained in:
@@ -503,3 +503,181 @@ webhooks:
|
||||
- Health endpoints: `/health/liveness`, `/health/readiness`, `/status`, `/surface/fs/cache/status` (see runbook).
|
||||
- Alert hints: deny spikes, latency > 800ms p99, cache freshness lag > 10m, any secrets failure.
|
||||
|
||||
---
|
||||
|
||||
## 17) Offline Witness Verification
|
||||
|
||||
> **Sprint:** SPRINT_20260122_038_Scanner_ebpf_probe_type (EBPF-004)
|
||||
|
||||
This section documents the deterministic replay verification algorithm for runtime witnesses, enabling air-gapped environments to independently verify witness attestations.
|
||||
|
||||
### 17.1 Input Canonicalization (RFC 8785 JCS)
|
||||
|
||||
All witness payloads MUST be canonicalized before hashing or signing using **JSON Canonicalization Scheme (JCS)** per RFC 8785:
|
||||
|
||||
1. **Property ordering**: Object properties are sorted lexicographically by key name (Unicode code point order).
|
||||
2. **Number serialization**: Numbers are serialized without unnecessary precision; integers as integers, decimals with minimal representation.
|
||||
3. **String encoding**: UTF-8 with no BOM; escape sequences normalized to `\uXXXX` form for control characters.
|
||||
4. **Whitespace**: No insignificant whitespace between tokens.
|
||||
5. **Null handling**: Explicit `null` values are preserved; absent keys are omitted.
|
||||
|
||||
**Canonicalization algorithm:**
|
||||
|
||||
```
|
||||
function canonicalize(json_object):
|
||||
if json_object is null:
|
||||
return "null"
|
||||
if json_object is boolean:
|
||||
return "true" | "false"
|
||||
if json_object is number:
|
||||
return serialize_number(json_object) # RFC 8785 §3.2.2.3
|
||||
if json_object is string:
|
||||
return quote(escape(json_object))
|
||||
if json_object is array:
|
||||
return "[" + join(",", [canonicalize(elem) for elem in json_object]) + "]"
|
||||
if json_object is object:
|
||||
keys = sorted(json_object.keys(), key=unicode_codepoint_order)
|
||||
pairs = [quote(key) + ":" + canonicalize(json_object[key]) for key in keys]
|
||||
return "{" + join(",", pairs) + "}"
|
||||
```
|
||||
|
||||
### 17.2 Observation Ordering Rules
|
||||
|
||||
When a witness contains multiple observations (e.g., from eBPF probes), they MUST be ordered deterministically before hashing:
|
||||
|
||||
1. **Primary sort**: By `observedAt` timestamp (UTC, ascending)
|
||||
2. **Secondary sort**: By `nodeHash` (lexicographic ascending)
|
||||
3. **Tertiary sort**: By `observationId` (lexicographic ascending, for tie-breaking)
|
||||
|
||||
**Observation hash computation:**
|
||||
|
||||
```
|
||||
function compute_observations_hash(observations):
|
||||
sorted_observations = sort(observations,
|
||||
key=lambda o: (o.observedAt, o.nodeHash, o.observationId))
|
||||
|
||||
canonical_array = []
|
||||
for obs in sorted_observations:
|
||||
canonical_array.append({
|
||||
"observedAt": obs.observedAt.toISOString(),
|
||||
"nodeHash": obs.nodeHash,
|
||||
"functionName": obs.functionName,
|
||||
"probeType": obs.probeType, # EBPF-001: kprobe|uprobe|tracepoint|usdt|fentry|fexit
|
||||
"containerHash": sha256(obs.containerId + obs.podName + obs.namespace)
|
||||
})
|
||||
|
||||
return sha256(canonicalize(canonical_array))
|
||||
```
|
||||
|
||||
### 17.3 Signature Verification Sequence
|
||||
|
||||
Offline verification MUST follow this exact sequence to ensure deterministic results:
|
||||
|
||||
1. **Parse DSSE envelope**: Extract `payloadType`, `payload` (base64-decoded), and `signatures[]`.
|
||||
|
||||
2. **Verify payload hash**:
|
||||
```
|
||||
expected_hash = sha256(payload_bytes)
|
||||
assert envelope.payload_sha256 == expected_hash
|
||||
```
|
||||
|
||||
3. **Verify DSSE signature(s)**: For each signature in `signatures[]`:
|
||||
```
|
||||
pae_string = "DSSEv1 " + len(payloadType) + " " + payloadType + " " + len(payload) + " " + payload
|
||||
verify_signature(signature.sig, pae_string, get_public_key(signature.keyid))
|
||||
```
|
||||
|
||||
4. **Verify Rekor inclusion** (if present):
|
||||
```
|
||||
fetch_or_load_checkpoint(rekor_log_id)
|
||||
verify_merkle_inclusion(entry_hash, inclusion_proof, checkpoint.root_hash)
|
||||
verify_checkpoint_signature(checkpoint, rekor_public_key)
|
||||
```
|
||||
|
||||
5. **Verify timestamp** (if RFC 3161 TST present):
|
||||
```
|
||||
verify_tst_signature(tst, tsa_certificate)
|
||||
assert tst.timestamp <= now() + allowed_skew
|
||||
```
|
||||
|
||||
6. **Verify witness content**:
|
||||
```
|
||||
witness = parse_json(payload)
|
||||
recomputed_observations_hash = compute_observations_hash(witness.observations)
|
||||
assert witness.observationsDigest == recomputed_observations_hash
|
||||
```
|
||||
|
||||
### 17.4 Offline Bundle Structure Requirements
|
||||
|
||||
A StellaBundle for offline witness verification MUST include:
|
||||
|
||||
```
|
||||
bundle/
|
||||
├── manifest.json # Bundle manifest v2.0.0
|
||||
├── witnesses/
|
||||
│ └── <claim_id>.witness.dsse.json # DSSE-signed witness
|
||||
├── proofs/
|
||||
│ ├── rekor-inclusion.json # Rekor inclusion proof
|
||||
│ ├── checkpoint.json # Rekor checkpoint (signed)
|
||||
│ └── rfc3161-tst.der # Optional RFC 3161 timestamp
|
||||
├── observations/
|
||||
│ └── observations.ndjson # Raw observations (for replay)
|
||||
├── keys/
|
||||
│ ├── signing-key.pub # Public key for DSSE verification
|
||||
│ └── rekor-key.pub # Rekor log public key
|
||||
└── trust/
|
||||
└── trust-root.json # Trust anchors for key verification
|
||||
```
|
||||
|
||||
**Manifest schema (witnesses section):**
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": "2.0.0",
|
||||
"artifacts": [
|
||||
{
|
||||
"type": "witness",
|
||||
"path": "witnesses/<claim_id>.witness.dsse.json",
|
||||
"digest": "sha256:...",
|
||||
"predicateType": "https://stella.ops/predicates/runtime-witness/v1",
|
||||
"proofs": {
|
||||
"rekor": "proofs/rekor-inclusion.json",
|
||||
"checkpoint": "proofs/checkpoint.json",
|
||||
"tst": "proofs/rfc3161-tst.der"
|
||||
},
|
||||
"observationsRef": "observations/observations.ndjson"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 17.5 Verification CLI Commands
|
||||
|
||||
```bash
|
||||
# Verify a witness bundle offline
|
||||
stella bundle verify --bundle witness-bundle.tar.gz --offline
|
||||
|
||||
# Verify with replay (recompute observations hash)
|
||||
stella bundle verify --bundle witness-bundle.tar.gz --offline --replay
|
||||
|
||||
# Verify specific witness from bundle
|
||||
stella witness verify --bundle witness-bundle.tar.gz --witness-id wit:sha256:abc123 --offline
|
||||
|
||||
# Export verification report
|
||||
stella witness verify --bundle witness-bundle.tar.gz --offline --output report.json
|
||||
```
|
||||
|
||||
### 17.6 Determinism Guarantees
|
||||
|
||||
The verification algorithm guarantees:
|
||||
|
||||
1. **Idempotent**: Running verification N times produces identical results.
|
||||
2. **Reproducible**: Different systems with the same bundle produce identical verification outcomes.
|
||||
3. **Isolated**: Verification requires no network access (fully air-gapped).
|
||||
4. **Auditable**: Every step produces evidence that can be independently checked.
|
||||
|
||||
**Test criteria** (per advisory):
|
||||
- Offline verifier reproduces the same mapping on 3 separate air-gapped runs.
|
||||
- No randomness in canonicalization, ordering, or hash computation.
|
||||
- Timestamps use UTC with fixed precision (milliseconds).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user