Files
git.stella-ops.org/docs/modules/attestor/proof-chain-specification.md
2025-12-24 21:45:46 +02:00

478 lines
21 KiB
Markdown

# Proof and Evidence Chain Technical Specification
**Version**: 1.0
**Status**: Implementation Ready
**Source**: `docs/product-advisories/14-Dec-2025 - Proof and Evidence Chain Technical Reference.md`
**Last Updated**: 2025-12-14
---
## Executive Summary
This specification defines the implementation of a cryptographically verifiable proof chain that links SBOM components to VEX verdicts through signed evidence and reasoning statements. The system provides complete traceability from scan results to policy decisions with deterministic, auditable outputs.
## Architecture Overview
```
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ PROOF CHAIN ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Scanner │──►│ Evidence │──►│ Reasoning │──►│ VEX │ │
│ │ SBOM │ │ Statements │ │ Statements │ │ Verdicts │ │
│ │ (CycloneDX) │ │ │ │ │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ CONTENT-ADDRESSED IDs │ │
│ │ SBOMEntryID | EvidenceID | ReasoningID | VEXVerdictID | ProofBundleID │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ PROOF SPINE │ │
│ │ Merkle aggregation: merkle_root(SBOMEntryID, EvidenceID[], ReasoningID, │ │
│ │ VEXVerdictID) = ProofBundleID │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ DSSE ENVELOPES │ │
│ │ - In-toto Statement/v1 format │ │
│ │ - Signed by role-specific keys │ │
│ │ - Predicate types: evidence.stella/v1, reasoning.stella/v1, │ │
│ │ cdx-vex.stella/v1, proofspine.stella/v1 │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ TRUST ANCHORS │ │
│ │ - Per-dependency anchor (PURL pattern matching) │ │
│ │ - Allowed key IDs for verification │ │
│ │ - Key rotation with historical validity │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ REKOR TRANSPARENCY LOG │ │
│ │ - Inclusion proofs for all DSSE envelopes │ │
│ │ - Checkpoint verification │ │
│ │ - Offline verification support │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────┘
```
## Content-Addressed Identifier System
### ID Formats
| ID Type | Format | Example |
|---------|--------|---------|
| ArtifactID | `sha256:<64-hex>` | `sha256:a1b2c3d4e5f6...` |
| SBOMEntryID | `<sbomDigest>:<purl>[@<version>]` | `sha256:91f2ab3c:pkg:npm/lodash@4.17.21` |
| EvidenceID | `sha256:<hash(canonical_json)>` | `sha256:7b8c9d0e...` |
| ReasoningID | `sha256:<hash(canonical_json)>` | `sha256:4a5b6c7d...` |
| VEXVerdictID | `sha256:<hash(canonical_json)>` | `sha256:1f2e3d4c...` |
| ProofBundleID | `sha256:<merkle_root>` | `sha256:9e8d7c6b...` |
| GraphRevisionID | `grv_sha256:<hash>` | `grv_sha256:5a4b3c2d...` |
| TrustAnchorID | `UUID v4` | `550e8400-e29b-41d4-a716-446655440000` |
### Canonicalization Rules
1. **UTF-8 encoding** for all strings
2. **Sorted keys** (lexicographic, case-sensitive)
3. **No insignificant whitespace**
4. **No trailing commas**
5. **Numbers in shortest form**
6. **Deterministic array ordering** (by semantic key: bom-ref, purl)
### Canonicalization Versioning
Content-addressed identifiers embed a canonicalization version marker to prevent hash collisions when the canonicalization algorithm evolves. This ensures that:
- **Forward compatibility**: Future algorithm changes won't invalidate existing hashes.
- **Verifier clarity**: Verifiers know exactly which algorithm to use.
- **Auditability**: Hash provenance is cryptographically bound to algorithm version.
**Version Marker Format:**
```json
{
"_canonVersion": "stella:canon:v1",
"sbomEntryId": "...",
"vulnerabilityId": "..."
}
```
| Field | Description |
|-------|-------------|
| `_canonVersion` | Underscore prefix ensures lexicographic first position after sorting |
| Value format | `stella:canon:v<N>` where N is the version number |
| Current version | `stella:canon:v1` (RFC 8785 JSON canonicalization) |
**V1 Algorithm Specification:**
| Property | Behavior |
|----------|----------|
| Standard | RFC 8785 (JSON Canonicalization Scheme) |
| Key sorting | Ordinal string comparison |
| Whitespace | None (compact JSON) |
| Encoding | UTF-8 without BOM |
| Numbers | IEEE 754, shortest representation |
| Escaping | Minimal (only required characters) |
**Version Detection:**
```csharp
// Detect if canonical JSON includes version marker
public static bool IsVersioned(ReadOnlySpan<byte> canonicalJson)
{
return canonicalJson.Length > 20 &&
canonicalJson.StartsWith("{\"_canonVersion\":"u8);
}
// Extract version from versioned canonical JSON
public static string? ExtractVersion(ReadOnlySpan<byte> canonicalJson)
{
// Parse and return the _canonVersion value, or null if not versioned
}
```
**Migration Strategy:**
| Phase | Behavior | Timeline |
|-------|----------|----------|
| Phase 1 (Current) | Generate v1 hashes; accept both legacy and v1 for verification | Now |
| Phase 2 | Log deprecation warnings for legacy hashes | +6 months |
| Phase 3 | Reject legacy hashes; require v1 | +12 months |
See also: [Canonicalization Migration Guide](../../operations/canon-version-migration.md)
## DSSE Predicate Types
### 1. Evidence Statement (`evidence.stella/v1`)
```json
{
"predicateType": "evidence.stella/v1",
"predicate": {
"source": "scanner/feed name",
"sourceVersion": "tool version",
"collectionTime": "2025-12-14T00:00:00Z",
"sbomEntryId": "<SBOMEntryID>",
"vulnerabilityId": "CVE-XXXX-YYYY",
"rawFinding": "<pointer or data>",
"evidenceId": "<EvidenceID>"
}
}
```
**Signer**: Scanner/Ingestor key
### 2. Reasoning Statement (`reasoning.stella/v1`)
```json
{
"predicateType": "reasoning.stella/v1",
"predicate": {
"sbomEntryId": "<SBOMEntryID>",
"evidenceIds": ["<EvidenceID>", ...],
"policyVersion": "v2.3.1",
"inputs": {
"currentEvaluationTime": "2025-12-14T00:00:00Z",
"severityThresholds": {...},
"latticeRules": {...}
},
"intermediateFindings": {...},
"reasoningId": "<ReasoningID>"
}
}
```
**Signer**: Policy/Authority key
### 3. VEX Verdict Statement (`cdx-vex.stella/v1`)
```json
{
"predicateType": "cdx-vex.stella/v1",
"predicate": {
"sbomEntryId": "<SBOMEntryID>",
"vulnerabilityId": "CVE-XXXX-YYYY",
"status": "not_affected|affected|fixed|under_investigation",
"justification": "vulnerable_code_not_in_execute_path",
"policyVersion": "v2.3.1",
"reasoningId": "<ReasoningID>",
"vexVerdictId": "<VEXVerdictID>"
}
}
```
**Signer**: VEXer/Vendor key
### 4. Proof Spine Statement (`proofspine.stella/v1`)
```json
{
"predicateType": "proofspine.stella/v1",
"predicate": {
"sbomEntryId": "<SBOMEntryID>",
"evidenceIds": ["<ID1>", "<ID2>"],
"reasoningId": "<ID>",
"vexVerdictId": "<ID>",
"policyVersion": "v2.3.1",
"proofBundleId": "<ProofBundleID>"
}
}
```
**Signer**: Authority key
### 5. Verdict Receipt Statement (`verdict.stella/v1`)
```json
{
"predicateType": "verdict.stella/v1",
"predicate": {
"graphRevisionId": "<GraphRevisionID>",
"findingKey": {"sbomEntryId": "<ID>", "vulnerabilityId": "CVE-..."},
"rule": {"id": "POLICY-RULE-123", "version": "v2.3.1"},
"decision": {"status": "block|warn|pass", "reason": "..."},
"inputs": {"sbomDigest": "sha256:...", "feedsDigest": "sha256:...", "policyDigest": "sha256:..."},
"outputs": {"proofBundleId": "<ID>", "reasoningId": "<ID>", "vexVerdictId": "<ID>"},
"createdAt": "2025-12-14T00:00:00Z"
}
}
```
**Signer**: Authority key
### 6. SBOM Linkage Statement (`sbom-linkage/v1`)
```json
{
"predicateType": "https://stella-ops.org/predicates/sbom-linkage/v1",
"predicate": {
"sbom": {"id": "<sbomId>", "format": "CycloneDX", "specVersion": "1.6", ...},
"generator": {"name": "StellaOps.Sbomer", "version": "x.y.z"},
"generatedAt": "2025-12-14T00:00:00Z",
"incompleteSubjects": [],
"tags": {"tenantId": "...", "projectId": "...", "pipelineRunId": "..."}
}
}
```
**Signer**: Generator key
### 7. Graph Root Statement (`graph-root.stella/v1`)
The Graph Root attestation provides tamper-evident commitment to graph analysis results (dependency graphs, call graphs, reachability graphs) by computing a Merkle root over canonicalized node and edge identifiers.
```json
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "graph-root://<graphType>/<merkleRoot>",
"digest": {
"sha256": "<merkle-root-hex>"
}
}
],
"predicateType": "https://stella-ops.org/predicates/graph-root/v1",
"predicate": {
"graphType": "DependencyGraph|CallGraph|ReachabilityGraph|...",
"merkleRoot": "sha256:<hex>",
"nodeCount": 1234,
"edgeCount": 5678,
"canonVersion": "stella:canon:v1",
"inputs": {
"sbomDigest": "sha256:<hex>",
"analyzerDigest": "sha256:<hex>",
"configDigest": "sha256:<hex>"
},
"createdAt": "2025-01-12T10:30:00Z"
}
}
```
**Signer**: Graph Analyzer key
#### Supported Graph Types
| Graph Type | Use Case |
|------------|----------|
| `DependencyGraph` | Package/library dependency analysis |
| `CallGraph` | Function-level call relationships |
| `ReachabilityGraph` | Vulnerability reachability analysis |
| `DataFlowGraph` | Data flow and taint tracking |
| `ControlFlowGraph` | Code execution paths |
| `InheritanceGraph` | OOP class hierarchies |
| `ModuleGraph` | Module/namespace dependencies |
| `BuildGraph` | Build system dependencies |
| `ContainerLayerGraph` | Container layer relationships |
#### Merkle Root Computation
The Merkle root is computed deterministically:
1. **Canonicalize Node IDs**: Sort all node identifiers lexicographically
2. **Canonicalize Edge IDs**: Sort all edge identifiers (format: `{source}->{target}`)
3. **Combine**: Concatenate sorted nodes + sorted edges
4. **Binary Tree**: Build SHA-256 Merkle tree with odd-node duplication
5. **Root**: Extract 32-byte root as `sha256:<hex>`
```
Merkle Tree Structure:
[root]
/ \
[h01] [h23]
/ \ / \
[n0] [n1] [n2] [n3]
```
#### Integration with Proof Spine
Graph root attestations can be referenced in proof spines:
```json
{
"predicateType": "proofspine.stella/v1",
"predicate": {
"sbomEntryId": "<SBOMEntryID>",
"evidenceIds": ["<ID1>", "<ID2>"],
"reasoningId": "<ID>",
"vexVerdictId": "<ID>",
"graphRootIds": ["<GraphRootID1>"],
"policyVersion": "v2.3.1",
"proofBundleId": "<ProofBundleID>"
}
}
```
#### Verification Steps
1. Parse DSSE envelope and verify signature against allowed keys
2. Extract predicate and Merkle root
3. Re-canonicalize provided node/edge IDs using `stella:canon:v1`
4. Recompute Merkle root from canonicalized inputs
5. Compare computed root to claimed root
6. If Rekor entry exists, verify transparency log inclusion
## Database Schema
### Tables
| Table | Purpose |
|-------|---------|
| `proofchain.sbom_entries` | SBOM component entries with content-addressed IDs |
| `proofchain.dsse_envelopes` | Signed DSSE envelopes by predicate type |
| `proofchain.spines` | Proof spine aggregations linking evidence to verdicts |
| `proofchain.trust_anchors` | Trust anchor configurations for verification |
| `proofchain.rekor_entries` | Rekor transparency log entries |
| `proofchain.graph_roots` | Graph root attestations with Merkle roots |
| `proofchain.key_history` | Key lifecycle history for rotation |
| `proofchain.key_audit_log` | Audit log for key operations |
## API Endpoints
| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/proofs/{entry}/spine` | POST | Create proof spine |
| `/proofs/{entry}/receipt` | GET | Get verification receipt |
| `/proofs/{entry}/vex` | GET | Get VEX document |
| `/anchors/{anchor}` | GET/PUT | Trust anchor management |
| `/anchors/{anchor}/keys` | GET/POST | Key management |
| `/anchors/{anchor}/keys/{keyid}/revoke` | POST | Key revocation |
| `/verify` | POST | Artifact verification |
| `/keys/rotation-warnings` | GET | Rotation warnings |
## CLI Exit Codes
| Code | Meaning |
|------|---------|
| 0 | Success - no policy violations |
| 1 | Policy violation detected |
| 2 | System/scanner error |
## Verification Pipeline
The 13-step verification algorithm:
1. Resolve SBOMEntryID → TrustAnchorID
2. Fetch spine and trust anchor
3. Verify spine DSSE signature against TrustAnchor.allowedKeyids
4. Verify VEX DSSE signature
5. Verify reasoning DSSE signature
6. Verify evidence DSSE signatures
7. Recompute EvidenceIDs from stored canonical evidence
8. Recompute ReasoningID from reasoning
9. Recompute VEXVerdictID from VEX body
10. Recompute ProofBundleID (merkle root) from above
11. Compare all computed IDs to stored IDs
12. If using Rekor: verify log inclusion proof
13. Emit Receipt
## Key Rotation
### Process
1. Add new key to TrustAnchor.allowedKeyids
2. Transition period: both keys valid
3. Optionally revoke old key (moves to revokedKeys)
4. Publish key material via attestation feed
### Invariants
- Never mutate old DSSE envelopes
- Revoked keys remain valid for proofs signed before revocation
- All key changes are audited
## Implementation Sprints
| Sprint | Focus | Status |
|--------|-------|--------|
| 0501.2 | Content-Addressed IDs & Core Records | TODO |
| 0501.3 | New DSSE Predicate Types | TODO |
| 0501.4 | Proof Spine Assembly | TODO |
| 0501.5 | API Surface & Verification Pipeline | TODO |
| 0501.6 | Database Schema Implementation | TODO |
| 0501.7 | CLI Integration & Exit Codes | TODO |
| 0501.8 | Key Rotation & Trust Anchors | TODO |
## Related Documents
- [Master Sprint Plan](../../implplan/SPRINT_0501_0001_0001_proof_evidence_chain_master.md)
- [Content-Addressed IDs Sprint](../../implplan/SPRINT_0501_0002_0001_proof_chain_content_addressed_ids.md)
- [DSSE Predicates Sprint](../../implplan/SPRINT_0501_0003_0001_proof_chain_dsse_predicates.md)
- [Proof Spine Assembly Sprint](../../implplan/SPRINT_0501_0004_0001_proof_chain_spine_assembly.md)
- [API Surface Sprint](../../implplan/SPRINT_0501_0005_0001_proof_chain_api_surface.md)
- [Database Schema Sprint](../../implplan/SPRINT_0501_0006_0001_proof_chain_database_schema.md)
- [CLI Integration Sprint](../../implplan/SPRINT_0501_0007_0001_proof_chain_cli_integration.md)
- [Key Rotation Sprint](../../implplan/SPRINT_0501_0008_0001_proof_chain_key_rotation.md)
- [Graph Root Attestation](./graph-root-attestation.md)
- [Attestor Architecture](./architecture.md)
- [Signer Architecture](../signer/architecture.md)
- [Database Specification](../../db/SPECIFICATION.md)
## Cryptographic Profiles
| Profile | Algorithm | Use Case |
|---------|-----------|----------|
| default | SHA256-ED25519 | General purpose |
| fips | SHA256-ECDSA-P256 | FIPS 140-2/3 compliance |
| gost | GOST R 34.10-2012 | Russian regulatory |
| sm | SM2/SM3 | Chinese standards |
| pqc | SHA256-DILITHIUM3 | Post-quantum |
## Determinism Constraints
### Non-Negotiable Invariants
1. **Immutability**: DSSE envelopes are append-only
2. **Determinism**: Same inputs → same outputs
3. **Traceability**: Every verdict traceable to evidence
4. **Least Trust**: Explicit trust via TrustAnchors only
5. **Backward Compatibility**: New code verifies old proofs
### Temporal Handling
- UTC ISO-8601 only
- No local time
- Timestamps only when semantically required
- Derivation from content preferred over wall-clock time
---
**Document Version**: 1.0
**Target Platform**: .NET 10, PostgreSQL ≥16, Angular v17