themed the bulk of advisories
This commit is contained in:
@@ -0,0 +1,971 @@
|
||||
# Proof and Evidence Chain Technical Reference
|
||||
|
||||
**Source Advisories**:
|
||||
- 29-Nov-2025 - SBOM to VEX Proof Pipeline Blueprint
|
||||
- 01-Dec-2025 - Turning SBOM Data Into Verifiable Proofs
|
||||
- 01-Dec-2025 - Proof-Linked VEX User Interface
|
||||
- 02-Dec-2025 - Converting SBOM Data into Proof Chains
|
||||
- 06-Dec-2025 - How to Build a Verifiable SBOM→VEX Chain
|
||||
- 08-Dec-2025 - Defining Stella Ops' Proof‑Linked Advantage
|
||||
- 08-Dec-2025 - Designing UX for Signed Evidence Trails
|
||||
- 03-Dec-2025 - Comparing Proof‑Linked VEX UX Across Tools
|
||||
|
||||
**Last Updated**: 2025-12-14
|
||||
|
||||
---
|
||||
|
||||
## 1. CORE IDENTIFIERS & DATA MODEL
|
||||
|
||||
### 1.1 Canonical IDs
|
||||
|
||||
```
|
||||
ArtifactID = sha256:<digest>
|
||||
SBOMEntryID = <sbomDigest>:<purl>[@<version>]
|
||||
EvidenceID = hash(canonical_evidence_json)
|
||||
ReasoningID = hash(canonical_reasoning_json)
|
||||
VEXVerdictID = hash(canonical_vex_json)
|
||||
ProofBundleID = merkle_root(SBOMEntryID, EvidenceID[], ReasoningID, VEXVerdictID)
|
||||
TrustAnchorID = per-dependency anchor (public key + policy)
|
||||
```
|
||||
|
||||
### 1.2 Component Identifiers (bom-ref)
|
||||
|
||||
```
|
||||
Format: pkg:<ecosystem>/<name>@<version>?sha256=<digest>
|
||||
|
||||
Examples:
|
||||
pkg:maven/org.apache.commons/commons-lang3@3.14.0?sha256=<digest>
|
||||
pkg:apk/alpine/openssl@3.2.1-r0?sha256=2c0f...54e
|
||||
pkg:oci/<repo>@sha256:<manifestDigest>
|
||||
pkg:npm/lodash@4.17.21
|
||||
|
||||
Rules:
|
||||
- Must be stable across regenerations for identical content
|
||||
- Independent of local paths, build numbers
|
||||
- Derived from canonical bytes
|
||||
- Used as CycloneDX bom-ref
|
||||
```
|
||||
|
||||
### 1.3 Subject Schema
|
||||
|
||||
```csharp
|
||||
public sealed record ProofSubject(
|
||||
string Name, // PURL or canonical URI
|
||||
IReadOnlyDictionary<string,string> Digest // {"sha256": "...", "sha512": "..."}
|
||||
);
|
||||
```
|
||||
|
||||
### 1.4 SBOM Identity
|
||||
|
||||
```
|
||||
sbomId = sha256(canonical_sbom_bytes)
|
||||
```
|
||||
|
||||
## 2. DSSE ENVELOPE STRUCTURES
|
||||
|
||||
### 2.1 Evidence Statement
|
||||
|
||||
```json
|
||||
{
|
||||
"payloadType": "application/vnd.in-toto+json",
|
||||
"payload": {
|
||||
"_type": "https://in-toto.io/Statement/v1",
|
||||
"subject": [{"name": "<SBOMEntryID>", "digest": {"sha256": "..."}}],
|
||||
"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>"
|
||||
}
|
||||
},
|
||||
"signatures": [{"keyid": "<KID>", "sig": "BASE64(SIG)"}]
|
||||
}
|
||||
```
|
||||
|
||||
Signer: Scanner/Ingestor key
|
||||
|
||||
### 2.2 Reasoning Statement
|
||||
|
||||
```json
|
||||
{
|
||||
"payloadType": "application/vnd.in-toto+json",
|
||||
"payload": {
|
||||
"_type": "https://in-toto.io/Statement/v1",
|
||||
"subject": [{"name": "<SBOMEntryID>", "digest": {"sha256": "..."}}],
|
||||
"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>"
|
||||
}
|
||||
},
|
||||
"signatures": [{"keyid": "<KID>", "sig": "BASE64(SIG)"}]
|
||||
}
|
||||
```
|
||||
|
||||
Signer: Policy/Authority key
|
||||
|
||||
### 2.3 VEX Verdict Statement
|
||||
|
||||
```json
|
||||
{
|
||||
"payloadType": "application/vnd.in-toto+json",
|
||||
"payload": {
|
||||
"_type": "https://in-toto.io/Statement/v1",
|
||||
"subject": [{"name": "<SBOMEntryID>", "digest": {"sha256": "..."}}],
|
||||
"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>"
|
||||
}
|
||||
},
|
||||
"signatures": [{"keyid": "<KID>", "sig": "BASE64(SIG)"}]
|
||||
}
|
||||
```
|
||||
|
||||
Signer: VEXer key or vendor key
|
||||
|
||||
### 2.4 Proof Spine Statement
|
||||
|
||||
```json
|
||||
{
|
||||
"payloadType": "application/vnd.in-toto+json",
|
||||
"payload": {
|
||||
"_type": "https://in-toto.io/Statement/v1",
|
||||
"subject": [{"name": "<SBOMEntryID>", "digest": {"sha256": "..."}}],
|
||||
"predicateType": "proofspine.stella/v1",
|
||||
"predicate": {
|
||||
"sbomEntryId": "<SBOMEntryID>",
|
||||
"evidenceIds": ["<ID1>", "<ID2>"],
|
||||
"reasoningId": "<ID>",
|
||||
"vexVerdictId": "<ID>",
|
||||
"policyVersion": "v2.3.1",
|
||||
"proofBundleId": "<ProofBundleID>"
|
||||
}
|
||||
},
|
||||
"signatures": [{"keyid": "<KID>", "sig": "BASE64(SIG)"}]
|
||||
}
|
||||
```
|
||||
|
||||
Signer: Authority key
|
||||
|
||||
### 2.5 SBOM Linkage Statement
|
||||
|
||||
```json
|
||||
{
|
||||
"_type": "https://in-toto.io/Statement/v1",
|
||||
"subject": [
|
||||
{"name": "pkg:npm/lodash@4.17.21", "digest": {"sha256": "...", "sha512": "..."}}
|
||||
],
|
||||
"predicateType": "https://stella-ops.org/predicates/sbom-linkage/v1",
|
||||
"predicate": {
|
||||
"sbom": {
|
||||
"id": "<sbomId>",
|
||||
"format": "CycloneDX",
|
||||
"specVersion": "1.6",
|
||||
"mediaType": "application/vnd.cyclonedx+json",
|
||||
"sha256": "<sha256>",
|
||||
"location": "oci://... or file://..."
|
||||
},
|
||||
"generator": {
|
||||
"name": "StellaOps.Sbomer",
|
||||
"version": "x.y.z"
|
||||
},
|
||||
"generatedAt": "2025-12-14T00:00:00Z",
|
||||
"incompleteSubjects": [],
|
||||
"tags": {
|
||||
"tenantId": "...",
|
||||
"projectId": "...",
|
||||
"pipelineRunId": "..."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. CYCLONEDX VEX STRUCTURE
|
||||
|
||||
```json
|
||||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.6",
|
||||
"version": 1,
|
||||
"vulnerabilities": [
|
||||
{
|
||||
"id": "CVE-2024-12345",
|
||||
"source": {"name": "NVD"},
|
||||
"analysis": {
|
||||
"state": "not_affected",
|
||||
"justification": "vulnerable_code_not_present",
|
||||
"response": ["will_not_fix"],
|
||||
"detail": "Linked OpenSSL feature set excludes the vulnerable cipher."
|
||||
},
|
||||
"affects": [
|
||||
{"ref": "pkg:apk/alpine/openssl@3.2.1-r0?sha256=2c0f...54e"}
|
||||
],
|
||||
"properties": [
|
||||
{"name": "evidence.sbomDigest", "value": "sha256:91f2...9a"},
|
||||
{"name": "evidence.rekorLogID", "value": "425c1d1e..."},
|
||||
{"name": "reachability.report", "value": "sha256:reacha..."},
|
||||
{"name": "policy.decision", "value": "TrustGate#R-17.2"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### VEX Status Values
|
||||
|
||||
```
|
||||
not_affected
|
||||
affected
|
||||
fixed
|
||||
under_investigation
|
||||
```
|
||||
|
||||
### VEX Justification Values
|
||||
|
||||
```
|
||||
vulnerable_code_not_present
|
||||
vulnerable_code_not_in_execute_path
|
||||
vulnerable_code_not_configured
|
||||
vulnerable_code_cannot_be_controlled_by_adversary
|
||||
component_not_present
|
||||
inline_mitigations_exist
|
||||
```
|
||||
|
||||
## 4. STORAGE SCHEMA
|
||||
|
||||
### 4.1 PostgreSQL Tables
|
||||
|
||||
```sql
|
||||
CREATE TABLE sbom_entries (
|
||||
entry_id UUID PRIMARY KEY,
|
||||
bom_digest VARCHAR(64) NOT NULL,
|
||||
purl TEXT NOT NULL,
|
||||
version TEXT,
|
||||
artifact_digest VARCHAR(64),
|
||||
trust_anchor_id UUID,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE dsse_envelopes (
|
||||
env_id UUID PRIMARY KEY,
|
||||
entry_id UUID REFERENCES sbom_entries(entry_id),
|
||||
predicate_type TEXT NOT NULL,
|
||||
signer_keyid TEXT NOT NULL,
|
||||
body_hash VARCHAR(64) NOT NULL,
|
||||
envelope_blob_ref TEXT NOT NULL,
|
||||
signed_at TIMESTAMPTZ NOT NULL,
|
||||
INDEX idx_entry_predicate (entry_id, predicate_type)
|
||||
);
|
||||
|
||||
CREATE TABLE spines (
|
||||
entry_id UUID PRIMARY KEY REFERENCES sbom_entries(entry_id),
|
||||
bundle_id VARCHAR(64) NOT NULL,
|
||||
evidence_ids TEXT[] NOT NULL,
|
||||
reasoning_id VARCHAR(64) NOT NULL,
|
||||
vex_id VARCHAR(64) NOT NULL,
|
||||
anchor_id UUID,
|
||||
policy_version TEXT NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE trust_anchors (
|
||||
anchor_id UUID PRIMARY KEY,
|
||||
purl_pattern TEXT NOT NULL,
|
||||
allowed_keyids TEXT[] NOT NULL,
|
||||
policy_ref TEXT,
|
||||
revoked_keys TEXT[],
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE rekor_entries (
|
||||
dsse_sha256 VARCHAR(64) PRIMARY KEY,
|
||||
log_index BIGINT NOT NULL,
|
||||
log_id TEXT NOT NULL,
|
||||
integrated_time BIGINT NOT NULL,
|
||||
inclusion_proof JSONB NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
### 4.2 Proof Graph Nodes
|
||||
|
||||
```
|
||||
Node Types:
|
||||
- Artifact (container image, binary, Helm chart)
|
||||
- SbomDocument (by sbomId)
|
||||
- InTotoStatement (by statement hash)
|
||||
- DsseEnvelope (by envelope hash)
|
||||
- RekorEntry (by log index/UUID)
|
||||
- VexStatement (by vex hash)
|
||||
- Subject (component from SBOM)
|
||||
|
||||
Edge Types:
|
||||
- DESCRIBED_BY: Artifact → SbomDocument
|
||||
- ATTESTED_BY: SbomDocument → InTotoStatement
|
||||
- WRAPPED_BY: InTotoStatement → DsseEnvelope
|
||||
- LOGGED_IN: DsseEnvelope → RekorEntry
|
||||
- HAS_VEX: Artifact/Subject → VexStatement
|
||||
- CONTAINS_SUBJECT: InTotoStatement → Subject
|
||||
- PRODUCES: Build → SBOM
|
||||
- AFFECTS: VEX → Component
|
||||
- SIGNED_BY: Envelope → Key
|
||||
- RECORDED_AT: Envelope → Rekor
|
||||
```
|
||||
|
||||
## 5. API CONTRACTS
|
||||
|
||||
### 5.1 Proof Spine API
|
||||
|
||||
```
|
||||
POST /proofs/:entry/spine
|
||||
Body: {
|
||||
"evidenceIds": ["<ID1>", ...],
|
||||
"reasoningId": "<ID>",
|
||||
"vexVerdictId": "<ID>",
|
||||
"policyVersion": "v2.3.1"
|
||||
}
|
||||
Response: 201 Created, {"proofBundleId": "..."}
|
||||
|
||||
GET /proofs/:entry/receipt
|
||||
Response: {
|
||||
"proofBundleId": "...",
|
||||
"verifiedAt": "2025-12-14T00:00:00Z",
|
||||
"verifierVersion": "1.0.0",
|
||||
"anchorId": "...",
|
||||
"result": "pass|fail",
|
||||
"details": {...}
|
||||
}
|
||||
|
||||
GET /proofs/:entry/vex
|
||||
Response: <VEX JSON body>
|
||||
|
||||
GET /anchors/:anchor
|
||||
Response: {
|
||||
"anchorId": "...",
|
||||
"purlPattern": "pkg:npm/*",
|
||||
"allowedKeyids": ["key1", "key2"],
|
||||
"policyRef": "...",
|
||||
"revokedKeys": []
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 Verification API
|
||||
|
||||
```
|
||||
POST /verify
|
||||
Body: {
|
||||
"artifactDigest": "sha256:...",
|
||||
"sbom": <SBOM JSON or reference>,
|
||||
"vex": <VEX JSON or reference>,
|
||||
"signatures": [...],
|
||||
"logs": [...]
|
||||
}
|
||||
Response: {
|
||||
"artifact": "pkg:oci/...",
|
||||
"sbomVerified": true,
|
||||
"vexVerified": true,
|
||||
"components": [
|
||||
{
|
||||
"bomRef": "pkg:...",
|
||||
"vulnerabilities": [
|
||||
{
|
||||
"id": "CVE-...",
|
||||
"state": "not_affected",
|
||||
"justification": "..."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 6. CANONICALIZATION RULES
|
||||
|
||||
### 6.1 JSON Canonicalization
|
||||
|
||||
```
|
||||
1. UTF-8 encoding
|
||||
2. Sorted keys (lexicographic)
|
||||
3. No insignificant whitespace
|
||||
4. No volatile fields beyond semantic need
|
||||
5. Version schema: evidence.stella/v1, reasoning.stella/v1
|
||||
6. Deterministic array ordering where semantically unordered
|
||||
```
|
||||
|
||||
### 6.2 SBOM Canonicalization
|
||||
|
||||
```csharp
|
||||
public interface ISbomCanonicalizer
|
||||
{
|
||||
byte[] Canonicalize(ReadOnlySpan<byte> rawSbom, string mediaType);
|
||||
}
|
||||
|
||||
public interface IBlobHasher
|
||||
{
|
||||
string ComputeSha256Hex(ReadOnlySpan<byte> data);
|
||||
}
|
||||
```
|
||||
|
||||
Rules:
|
||||
- Remove insignificant whitespace
|
||||
- Sort object keys lexicographically
|
||||
- Sort arrays deterministically (by bom-ref or purl)
|
||||
- Strip volatile fields: generation timestamps, tool build IDs, non-deterministic UUIDs
|
||||
- Convert to internal JSON, then canonicalize
|
||||
|
||||
### 6.3 Subject Extraction
|
||||
|
||||
```csharp
|
||||
IEnumerable<Subject> ToSubjects(CycloneDxSbom sbom)
|
||||
{
|
||||
foreach (var c in sbom.Metadata.Components)
|
||||
{
|
||||
if (c.Hashes == null || c.Hashes.Count == 0) continue;
|
||||
var name = $"pkg:{c.Type}/{c.Name}@{c.Version}";
|
||||
var dig = c.Hashes
|
||||
.OrderBy(h => h.Algorithm)
|
||||
.ToDictionary(
|
||||
h => h.Algorithm.ToLowerInvariant(),
|
||||
h => h.Value.ToLowerInvariant()
|
||||
);
|
||||
yield return new Subject(name, dig);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Digest requirements:
|
||||
- Must have at least sha256 or sha512
|
||||
- Normalize algorithm keys to lowercase
|
||||
- Sort subjects by: 1) Name ascending, 2) Algorithm:value pairs
|
||||
|
||||
## 7. REKOR INTEGRATION
|
||||
|
||||
### 7.1 Rekor Entry Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"dsseSha256": "sha256:...",
|
||||
"rekor": {
|
||||
"uuid": "...",
|
||||
"logIndex": 12345,
|
||||
"logId": "...",
|
||||
"integratedTime": 1733736000,
|
||||
"inclusionProof": {
|
||||
"rootHash": "...",
|
||||
"hashes": ["...", "..."],
|
||||
"checkpoint": "..."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 Offline Update Bundle Structure
|
||||
|
||||
```
|
||||
/bundle-2025-12-14/
|
||||
manifest.json # version, created_at, entries[], sha256s
|
||||
payload.tar.zst # actual DB/indices/feeds
|
||||
payload.tar.zst.sha256
|
||||
statement.dsse.json # DSSE-wrapped statement over payload hash
|
||||
rekor-receipt.json # Rekor v2 inclusion/verification material
|
||||
```
|
||||
|
||||
### 7.3 Offline Update DSSE Predicate
|
||||
|
||||
```json
|
||||
{
|
||||
"predicateType": "https://stella-ops.org/attestations/offline-update/1",
|
||||
"predicate": {
|
||||
"offline_manifest_sha256": "sha256:...",
|
||||
"feeds": [
|
||||
{
|
||||
"name": "nvd",
|
||||
"snapshot_date": "2025-12-14",
|
||||
"archive_digest": "sha256:..."
|
||||
}
|
||||
],
|
||||
"builder": "ci-workflow-id / git-commit / job-id",
|
||||
"created_at": "2025-12-14T00:00:00Z",
|
||||
"oukit_channel": "stable|edge|fips-profile"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.4 Verification Sequence (Offline Kit)
|
||||
|
||||
```
|
||||
1. Validate Cosign signature of tarball
|
||||
2. Validate offline-manifest.json with JWS signature
|
||||
3. Verify file digests for all entries (including /attestations/*)
|
||||
4. Verify DSSE:
|
||||
- Call StellaOps.Attestor.Verify with:
|
||||
- offline-update.dsse.json
|
||||
- offline-update.rekor.json
|
||||
- local Rekor log snapshot/segment
|
||||
- Ensure payload digest matches kit tarball + manifest digests
|
||||
5. Only after all checks pass:
|
||||
- Swap Scanner's feed pointer to new snapshot
|
||||
- Emit audit event (kit filename, tarball digest, DSSE digest, Rekor UUID + log index)
|
||||
```
|
||||
|
||||
## 8. CRYPTOGRAPHIC SPECIFICATIONS
|
||||
|
||||
### 8.1 Signing Keys & Profiles
|
||||
|
||||
```
|
||||
Default Profile:
|
||||
Hash: SHA-256
|
||||
Signature: Ed25519 or ECDSA P-256
|
||||
|
||||
Future Profiles:
|
||||
GOST R 34.10-2012
|
||||
eIDAS-compliant algorithms
|
||||
FIPS 140-2/140-3
|
||||
SM2/SM3 (Chinese standards)
|
||||
PQC: Dilithium/Falcon for long-term archives
|
||||
|
||||
Key Storage:
|
||||
KMS/HSM (production)
|
||||
PKCS#11 for air-gap
|
||||
Per-environment keysets: dev, staging, prod
|
||||
Per-role keysets: Authority, VEXer, Evidence Ingestor
|
||||
```
|
||||
|
||||
### 8.2 Key Rotation
|
||||
|
||||
```
|
||||
Rotation Process:
|
||||
1. Add new allowed_keyids to TrustAnchor
|
||||
2. Never mutate old DSSE envelopes
|
||||
3. Publish key material via attestation feed or Rekor-mirror
|
||||
4. Record all changes in audit log
|
||||
5. Maintain key version history
|
||||
```
|
||||
|
||||
### 8.3 Trust Anchor Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"trustAnchorId": "UUID",
|
||||
"purlPattern": "pkg:npm/*",
|
||||
"allowedKeyids": ["keyid1", "keyid2"],
|
||||
"allowedPredicateTypes": [
|
||||
"evidence.stella/v1",
|
||||
"reasoning.stella/v1",
|
||||
"cdx-vex.stella/v1"
|
||||
],
|
||||
"policyVersion": "v2.3.1",
|
||||
"revokedKeys": []
|
||||
}
|
||||
```
|
||||
|
||||
## 9. VERIFICATION PIPELINE
|
||||
|
||||
### 9.1 Verification Algorithm
|
||||
|
||||
```
|
||||
Input: SBOMEntryID or ProofBundleID
|
||||
|
||||
Steps:
|
||||
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
|
||||
- Verify payload hashes match local files
|
||||
13. Emit Receipt
|
||||
|
||||
Output: Receipt {
|
||||
proofBundleId,
|
||||
verifiedAt,
|
||||
verifierVersion,
|
||||
anchorId,
|
||||
result: "pass|fail",
|
||||
details: [...]
|
||||
}
|
||||
```
|
||||
|
||||
### 9.2 Receipt Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"proofBundleId": "sha256:...",
|
||||
"verifiedAt": "2025-12-14T00:00:00Z",
|
||||
"verifierVersion": "1.0.0",
|
||||
"anchorId": "UUID",
|
||||
"result": "pass",
|
||||
"checks": [
|
||||
{
|
||||
"check": "spine_signature",
|
||||
"status": "pass",
|
||||
"keyid": "..."
|
||||
},
|
||||
{
|
||||
"check": "evidence_id_recompute",
|
||||
"status": "pass",
|
||||
"expected": "sha256:...",
|
||||
"actual": "sha256:..."
|
||||
},
|
||||
{
|
||||
"check": "rekor_inclusion",
|
||||
"status": "pass",
|
||||
"logIndex": 12345
|
||||
}
|
||||
],
|
||||
"toolDigests": {
|
||||
"verifier": "sha256:...",
|
||||
"canonicalizer": "sha256:..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 10. DETERMINISM CONSTRAINTS
|
||||
|
||||
### 10.1 Non-Negotiable Invariants
|
||||
|
||||
```
|
||||
1. Immutability of Signed Facts
|
||||
- DSSE envelopes are append-only
|
||||
- Never edit or delete content inside signed envelope
|
||||
- Corrections via superseding (new statement pointing to old)
|
||||
|
||||
2. Determinism
|
||||
- Same {SBOMEntryID, Evidence set, policyVersion} ⇒ same {ReasoningID, VEXVerdictID, ProofBundleID}
|
||||
- No non-deterministic inputs in ID computation
|
||||
- No current time, random IDs in verdict logic
|
||||
|
||||
3. Traceability
|
||||
- Every VEX verdict → SBOM entry, evidence blobs, policy snapshot, trust anchor
|
||||
|
||||
4. Least Trust/Least Privilege
|
||||
- Trust always explicit via TrustAnchors + signature verification
|
||||
- Never "because it's in our DB"
|
||||
|
||||
5. Backwards Compatibility
|
||||
- New code verifies old proofs
|
||||
- New policies generate new spines, old spines intact
|
||||
```
|
||||
|
||||
### 10.2 Temporal Handling
|
||||
|
||||
```
|
||||
UTC ISO-8601 only
|
||||
No local time
|
||||
Timestamps only when semantically required
|
||||
Derivation from content preferred over wall-clock time
|
||||
Record evaluation time as explicit input if policy needs it
|
||||
```
|
||||
|
||||
### 10.3 Ordering Requirements
|
||||
|
||||
```
|
||||
Subjects: sorted by Name ascending, then digest keys
|
||||
Evidence IDs: sorted lexicographically
|
||||
Keys in JSON: sorted lexicographically
|
||||
Array elements: stable sort by semantic key (bom-ref, purl)
|
||||
```
|
||||
|
||||
## 11. IMPLEMENTATION INTERFACES
|
||||
|
||||
### 11.1 .NET 10 Core Interfaces
|
||||
|
||||
```csharp
|
||||
// DSSE Signing
|
||||
public interface IDsseSigner
|
||||
{
|
||||
Task<DsseEnvelope> SignAsync(
|
||||
ReadOnlyMemory<byte> payload,
|
||||
string payloadType,
|
||||
string keyProfile,
|
||||
CancellationToken ct = default
|
||||
);
|
||||
}
|
||||
|
||||
// Verification
|
||||
public record DsseEnvelope(
|
||||
string PayloadType,
|
||||
byte[] Payload,
|
||||
Signature[] Signatures
|
||||
);
|
||||
|
||||
public record Signature(
|
||||
string Keyid,
|
||||
string Sig,
|
||||
string? Cert
|
||||
);
|
||||
|
||||
// Subject Extraction
|
||||
public sealed record ProofSubject(
|
||||
string Name,
|
||||
IReadOnlyDictionary<string,string> Digest
|
||||
);
|
||||
|
||||
// Predicate Models
|
||||
public record SbomLinkagePredicate(
|
||||
SbomDescriptor Sbom,
|
||||
GeneratorDescriptor Generator,
|
||||
DateTimeOffset GeneratedAt,
|
||||
IReadOnlyList<IncompleteSubject>? IncompleteSubjects,
|
||||
IReadOnlyDictionary<string,string>? Tags
|
||||
);
|
||||
|
||||
public record EvidencePredicate(
|
||||
string Source,
|
||||
string SourceVersion,
|
||||
DateTimeOffset CollectionTime,
|
||||
string SbomEntryId,
|
||||
string? VulnerabilityId,
|
||||
object RawFinding,
|
||||
string EvidenceId
|
||||
);
|
||||
|
||||
public record ReasoningPredicate(
|
||||
string SbomEntryId,
|
||||
string[] EvidenceIds,
|
||||
string PolicyVersion,
|
||||
Dictionary<string,object> Inputs,
|
||||
Dictionary<string,object>? IntermediateFindings,
|
||||
string ReasoningId
|
||||
);
|
||||
|
||||
public record VexPredicate(
|
||||
string SbomEntryId,
|
||||
string VulnerabilityId,
|
||||
string Status,
|
||||
string Justification,
|
||||
string PolicyVersion,
|
||||
string ReasoningId,
|
||||
string VexVerdictId
|
||||
);
|
||||
|
||||
public record ProofSpinePredicate(
|
||||
string SbomEntryId,
|
||||
string[] EvidenceIds,
|
||||
string ReasoningId,
|
||||
string VexVerdictId,
|
||||
string PolicyVersion,
|
||||
string ProofBundleId
|
||||
);
|
||||
```
|
||||
|
||||
### 11.2 Rekor Client Interface
|
||||
|
||||
```csharp
|
||||
public interface IRekorClient
|
||||
{
|
||||
Task<RekorEntry> SubmitDsseAsync(
|
||||
DsseEnvelope envelope,
|
||||
CancellationToken ct = default
|
||||
);
|
||||
|
||||
Task<bool> VerifyInclusionAsync(
|
||||
RekorEntry entry,
|
||||
byte[] payloadDigest,
|
||||
byte[] rekorPublicKey,
|
||||
CancellationToken ct = default
|
||||
);
|
||||
}
|
||||
|
||||
public record RekorEntry(
|
||||
string Uuid,
|
||||
long LogIndex,
|
||||
string LogId,
|
||||
long IntegratedTime,
|
||||
InclusionProof Proof
|
||||
);
|
||||
|
||||
public record InclusionProof(
|
||||
string RootHash,
|
||||
string[] Hashes,
|
||||
string Checkpoint
|
||||
);
|
||||
```
|
||||
|
||||
## 12. CONFIGURATION SCHEMA
|
||||
|
||||
### 12.1 Scanner Offline Kit Config
|
||||
|
||||
```yaml
|
||||
scanner:
|
||||
offlineKit:
|
||||
requireDsse: true
|
||||
rekorOfflineMode: true
|
||||
attestationVerifier: https://attestor.internal
|
||||
trustAnchors:
|
||||
- anchorId: "UUID"
|
||||
purlPattern: "pkg:npm/*"
|
||||
allowedKeyids: ["key1", "key2"]
|
||||
```
|
||||
|
||||
### 12.2 Signer Config
|
||||
|
||||
```yaml
|
||||
signer:
|
||||
profiles:
|
||||
default:
|
||||
algorithm: "SHA256-ED25519"
|
||||
keyStore: "kms://..."
|
||||
fips:
|
||||
algorithm: "SHA256-ECDSA-P256"
|
||||
keyStore: "hsm://..."
|
||||
pqc:
|
||||
algorithm: "SHA256-DILITHIUM3"
|
||||
keyStore: "kms://..."
|
||||
```
|
||||
|
||||
### 12.3 Authority Config
|
||||
|
||||
```yaml
|
||||
authority:
|
||||
trustRoots:
|
||||
- id: "root-ca-1"
|
||||
publicKey: "..."
|
||||
validFrom: "2025-01-01T00:00:00Z"
|
||||
validUntil: "2030-01-01T00:00:00Z"
|
||||
keystores:
|
||||
- type: "kms"
|
||||
url: "aws-kms://..."
|
||||
region: "us-east-1"
|
||||
```
|
||||
|
||||
## 13. ERROR HANDLING
|
||||
|
||||
### 13.1 Ingestion Failures
|
||||
|
||||
```
|
||||
If SBOM invalid:
|
||||
- Reject SBOM
|
||||
- Record DSSE failure attestation:
|
||||
{
|
||||
"error": "schema_validation_failed",
|
||||
"file": "sbom.json",
|
||||
"system_version": "1.0.0"
|
||||
}
|
||||
- Maintain proof trail for "we tried and it failed"
|
||||
```
|
||||
|
||||
### 13.2 Missing Digests
|
||||
|
||||
```
|
||||
If component lacks sha256/sha512:
|
||||
- Do NOT use as primary subject in proof chain
|
||||
- Log in "incompleteSubjects" block in predicate
|
||||
- Expose in UI as "unverifiable component"
|
||||
```
|
||||
|
||||
### 13.3 Rekor Failures
|
||||
|
||||
```
|
||||
If Rekor unavailable:
|
||||
- Store DSSE envelope locally
|
||||
- Queue for retry
|
||||
- Mark proof chain as "rekorStatus: pending"
|
||||
- Internal-only until Rekor sync succeeds
|
||||
- Flag in verification results
|
||||
```
|
||||
|
||||
## 14. METRICS & OBSERVABILITY
|
||||
|
||||
### 14.1 Pipeline Metrics
|
||||
|
||||
```
|
||||
sboms_ingested_total
|
||||
sbom_ingest_errors_total{reason}
|
||||
evidence_statements_created_total
|
||||
reasoning_statements_created_total
|
||||
vex_statements_created_total
|
||||
proof_spines_created_total
|
||||
proof_verifications_total{result}
|
||||
*_duration_seconds (latency histograms per stage)
|
||||
|
||||
offlinekit_import_total{status="success|failed_dsse|failed_rekor|failed_cosign"}
|
||||
offlinekit_attestation_verify_latency_seconds
|
||||
attestor_rekor_success_total
|
||||
attestor_rekor_retry_total
|
||||
rekor_inclusion_latency
|
||||
```
|
||||
|
||||
### 14.2 Structured Logging Fields
|
||||
|
||||
```
|
||||
sbomEntryId
|
||||
proofBundleId
|
||||
anchorId
|
||||
policyVersion
|
||||
requestId / traceId
|
||||
rekorUuid
|
||||
attestationDigest
|
||||
offlineKitHash
|
||||
failureReason
|
||||
```
|
||||
|
||||
## 15. CI/CD INTEGRATION
|
||||
|
||||
### 15.1 Pipeline Hooks
|
||||
|
||||
```
|
||||
On SBOM ingest:
|
||||
- Create/refresh SBOMEntry rows
|
||||
- Attach TrustAnchor
|
||||
|
||||
On scan completion:
|
||||
- Produce Evidence Statements (DSSE) immediately
|
||||
|
||||
On policy evaluation:
|
||||
- Produce Reasoning + VEX
|
||||
- Assemble Spine
|
||||
|
||||
Release gates:
|
||||
- Require: GET /proofs/:entry/receipt == PASS
|
||||
```
|
||||
|
||||
### 15.2 CLI Exit Codes
|
||||
|
||||
```
|
||||
0 = no policy violation
|
||||
1 = policy violation
|
||||
2 = scanner/system error (distinguish from "found vulns")
|
||||
```
|
||||
|
||||
### 15.3 CLI Output Modes
|
||||
|
||||
```
|
||||
Default: Human-readable summary (3-5 lines)
|
||||
--output json: Machine-readable with:
|
||||
- Web UI run page link
|
||||
- Proof bundle ID
|
||||
- Rekor/ledger reference
|
||||
|
||||
-v / -vv: Verbose details
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Document Version**: 1.0
|
||||
**Target Platform**: .NET 10, PostgreSQL ≥16, Angular v17
|
||||
Reference in New Issue
Block a user