@colophon-claims/verify 0.0.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -0
- package/dist/admission/application.d.ts +18 -0
- package/dist/admission/application.js +53 -0
- package/dist/admission/contracts.d.ts +506 -0
- package/dist/admission/contracts.js +459 -0
- package/dist/admission/index.d.ts +5 -0
- package/dist/admission/index.js +5 -0
- package/dist/admission/intake.d.ts +75 -0
- package/dist/admission/intake.js +70 -0
- package/dist/admission/result-evaluation.d.ts +18 -0
- package/dist/admission/result-evaluation.js +71 -0
- package/dist/admission/screening-sample.d.ts +56 -0
- package/dist/admission/screening-sample.js +126 -0
- package/dist/admission/verification.d.ts +102 -0
- package/dist/admission/verification.js +844 -0
- package/dist/anchor/check.d.ts +95 -0
- package/dist/anchor/check.js +146 -0
- package/dist/anchor/ports.d.ts +6 -0
- package/dist/anchor/ports.js +354 -0
- package/dist/assets.d.ts +33 -0
- package/dist/assets.js +885 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +20 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.js +205 -0
- package/dist/comparison.d.ts +64 -0
- package/dist/comparison.js +235 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +23 -0
- package/dist/manifest.d.ts +54 -0
- package/dist/manifest.js +240 -0
- package/dist/materialize.d.ts +4 -0
- package/dist/materialize.js +13 -0
- package/dist/profile/admission-receipts.d.ts +11 -0
- package/dist/profile/admission-receipts.js +24 -0
- package/dist/profile/anchor-claims.d.ts +157 -0
- package/dist/profile/anchor-claims.js +330 -0
- package/dist/profile/artifacts.d.ts +261 -0
- package/dist/profile/artifacts.js +139 -0
- package/dist/profile/assembly-ports.d.ts +19 -0
- package/dist/profile/assembly-ports.js +42 -0
- package/dist/profile/binary-judge-manifest.d.ts +121 -0
- package/dist/profile/binary-judge-manifest.js +106 -0
- package/dist/profile/binary-qualification.d.ts +9 -0
- package/dist/profile/binary-qualification.js +58 -0
- package/dist/profile/branding-assets.d.ts +5 -0
- package/dist/profile/branding-assets.js +5 -0
- package/dist/profile/branding.d.ts +24 -0
- package/dist/profile/branding.js +14 -0
- package/dist/profile/claim-consistency.d.ts +26 -0
- package/dist/profile/claim-consistency.js +91 -0
- package/dist/profile/claim.d.ts +327 -0
- package/dist/profile/claim.js +742 -0
- package/dist/profile/errors.d.ts +49 -0
- package/dist/profile/errors.js +78 -0
- package/dist/profile/inspect-assurance.d.ts +16 -0
- package/dist/profile/inspect-assurance.js +30 -0
- package/dist/profile/inspect-disclosure.d.ts +28 -0
- package/dist/profile/inspect-disclosure.js +54 -0
- package/dist/profile/inspect-manifest.d.ts +706 -0
- package/dist/profile/inspect-manifest.js +397 -0
- package/dist/profile/isolation.d.ts +21 -0
- package/dist/profile/isolation.js +31 -0
- package/dist/profile/pinning-evidence.d.ts +41 -0
- package/dist/profile/pinning-evidence.js +43 -0
- package/dist/profile/ports.d.ts +3 -0
- package/dist/profile/ports.js +11 -0
- package/dist/profile/preview-log.d.ts +4 -0
- package/dist/profile/preview-log.js +3 -0
- package/dist/profile/run-results.d.ts +17 -0
- package/dist/profile/run-results.js +23 -0
- package/dist/profile/signing.d.ts +10 -0
- package/dist/profile/signing.js +46 -0
- package/dist/profile/trust.d.ts +13 -0
- package/dist/profile/trust.js +35 -0
- package/dist/profile/venue.d.ts +2 -0
- package/dist/profile/venue.js +2 -0
- package/dist/profile/verdict.d.ts +28 -0
- package/dist/profile/verdict.js +51 -0
- package/dist/reader-instructions.d.ts +41 -0
- package/dist/reader-instructions.js +52 -0
- package/dist/schema.d.ts +380 -0
- package/dist/schema.js +498 -0
- package/dist/verify.d.ts +72 -0
- package/dist/verify.js +1573 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/package.json +63 -3
- package/schemas/assembly-row.schema.json +194 -0
- package/schemas/bundle-manifest.schema.json +23 -0
- package/schemas/claim-package.schema.json +279 -0
- package/schemas/dsse-envelope.schema.json +25 -0
- package/schemas/evidence-catalog.schema.json +43 -0
- package/schemas/public-trust.schema.json +44 -0
- package/schemas/verdict-catalog.schema.json +27 -0
- package/scripts/external-verify.py +238 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$comment": "Describes trust/public-keys.json, format benchmark-product-public-trust/2. Source of truth: BundleTrustSchema in @colophon-claims/verify src/schema.ts (an open object there, so unknown members are permitted here). The verifier additionally requires canonical JSON bytes, an Ed25519 SPKI that parses, report keyId == didKey == did:key derived from the SPKI, and evaluator keyId == 'benchmark-product-verdict-' + first 16 hex of sha256(SPKI DER). Schema validity does not imply verification, and these keys are bundle-carried: they prove signature consistency with the identities the bundle itself names, never third-party custody.",
|
|
4
|
+
"description": "Bundle-carried public trust material for a self-run benchmark-product public bundle (format benchmark-product-public-trust/2).",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"format": { "const": "benchmark-product-public-trust/2" },
|
|
8
|
+
"selfRun": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"properties": {
|
|
11
|
+
"custody": { "const": "workspace-minted" },
|
|
12
|
+
"evaluatorDistinctness": { "const": "agent-distinctness-only" },
|
|
13
|
+
"partyIndependence": { "const": "not-established" }
|
|
14
|
+
},
|
|
15
|
+
"required": ["custody", "evaluatorDistinctness", "partyIndependence"]
|
|
16
|
+
},
|
|
17
|
+
"report": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"properties": {
|
|
20
|
+
"keyId": { "type": "string", "minLength": 1 },
|
|
21
|
+
"algorithm": { "const": "ed25519" },
|
|
22
|
+
"spkiDerBase64": { "type": "string", "minLength": 1 },
|
|
23
|
+
"author": { "type": "string", "minLength": 1 },
|
|
24
|
+
"didKey": { "type": "string", "minLength": 1 },
|
|
25
|
+
"validFrom": { "type": "string", "format": "date-time" }
|
|
26
|
+
},
|
|
27
|
+
"required": ["keyId", "algorithm", "spkiDerBase64", "author", "didKey", "validFrom"]
|
|
28
|
+
},
|
|
29
|
+
"evaluators": {
|
|
30
|
+
"type": "array",
|
|
31
|
+
"items": {
|
|
32
|
+
"type": "object",
|
|
33
|
+
"properties": {
|
|
34
|
+
"keyId": { "type": "string", "minLength": 1 },
|
|
35
|
+
"algorithm": { "const": "ed25519" },
|
|
36
|
+
"spkiDerBase64": { "type": "string", "minLength": 1 },
|
|
37
|
+
"evaluator": { "type": "string", "minLength": 1 }
|
|
38
|
+
},
|
|
39
|
+
"required": ["keyId", "algorithm", "spkiDerBase64", "evaluator"]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"required": ["format", "selfRun", "report", "evaluators"]
|
|
44
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$comment": "Describes verdicts.json, format benchmark-product-verdict-catalog/2. Source of truth: BundleVerdictCatalogSchema in @colophon-claims/verify src/schema.ts (an open object there, so unknown members are permitted here). Each sha256 names a records/<sha256>.bin DSSE envelope (payloadType application/vnd.in-toto+json) whose signature verifies against the trust file's evaluator key with the matching keyId; cellKeys name the matrix cells the verdict judges. The verifier additionally requires canonical JSON bytes and exact closure against the matrix's verdict references.",
|
|
4
|
+
"description": "Verdict catalog of a benchmark-product public bundle (format benchmark-product-verdict-catalog/2).",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"format": { "const": "benchmark-product-verdict-catalog/2" },
|
|
8
|
+
"verdicts": {
|
|
9
|
+
"type": "array",
|
|
10
|
+
"items": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"properties": {
|
|
13
|
+
"sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
|
14
|
+
"evaluator": { "type": "string", "minLength": 1 },
|
|
15
|
+
"keyId": { "type": "string", "minLength": 1 },
|
|
16
|
+
"cellKeys": {
|
|
17
|
+
"type": "array",
|
|
18
|
+
"items": { "type": "string", "minLength": 1 },
|
|
19
|
+
"minItems": 1
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"required": ["sha256", "evaluator", "keyId", "cellKeys"]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"required": ["format", "verdicts"]
|
|
27
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""External check of a benchmark-product public bundle: Python 3 stdlib plus
|
|
3
|
+
the openssl CLI, no Jinn code. Covers the externally verifiable subset only:
|
|
4
|
+
manifest digests, content-addressed records, sealed-byte identity, DSSE
|
|
5
|
+
Ed25519 signatures, digest cross-references, key derivations, and the claim's
|
|
6
|
+
stored mirror. It does NOT re-derive the matrix, recompute the statistical
|
|
7
|
+
method, or rebuild the claim package (the reference verifier does), and no
|
|
8
|
+
tool can prove the producing venue was honest. Exit 0: all checks pass.
|
|
9
|
+
Exit 1: a check failed. Exit 2: usage or environment failure."""
|
|
10
|
+
import base64, hashlib, json, os, re, subprocess, sys, tempfile
|
|
11
|
+
|
|
12
|
+
HEX64 = re.compile(r"^[0-9a-f]{64}$")
|
|
13
|
+
|
|
14
|
+
def sha256(b): return hashlib.sha256(b).hexdigest()
|
|
15
|
+
|
|
16
|
+
def b58(data):
|
|
17
|
+
alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
|
18
|
+
n, out = int.from_bytes(data, "big"), ""
|
|
19
|
+
while n: n, r = divmod(n, 58); out = alphabet[r] + out
|
|
20
|
+
return "1" * (len(data) - len(data.lstrip(b"\0"))) + out
|
|
21
|
+
|
|
22
|
+
def strict_b64(field, label):
|
|
23
|
+
"""Decode base64 the way the reference verifier does: exactly standard OR
|
|
24
|
+
exactly URL-safe, canonical padding, no stray bytes. An inserted newline or
|
|
25
|
+
non-canonical padding fails here rather than being silently dropped. The
|
|
26
|
+
re-encode round trip is what makes this strict: it rejects any spelling that
|
|
27
|
+
is not the one canonical encoding of those bytes."""
|
|
28
|
+
if isinstance(field, str):
|
|
29
|
+
try:
|
|
30
|
+
decoded = base64.b64decode(field, validate=True)
|
|
31
|
+
if base64.b64encode(decoded).decode("ascii") == field:
|
|
32
|
+
return decoded
|
|
33
|
+
except Exception:
|
|
34
|
+
pass
|
|
35
|
+
try:
|
|
36
|
+
decoded = base64.urlsafe_b64decode(field)
|
|
37
|
+
if base64.urlsafe_b64encode(decoded).decode("ascii") == field:
|
|
38
|
+
return decoded
|
|
39
|
+
except Exception:
|
|
40
|
+
pass
|
|
41
|
+
raise ValueError(f"{label} is not strict standard or URL-safe base64")
|
|
42
|
+
|
|
43
|
+
def probe_openssl():
|
|
44
|
+
"""The real environment test: sign and verify a self-generated Ed25519 key.
|
|
45
|
+
If this cannot produce success the host's openssl cannot do the job."""
|
|
46
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
47
|
+
priv, pub = os.path.join(tmp, "priv.pem"), os.path.join(tmp, "pub.der")
|
|
48
|
+
msg, sig = os.path.join(tmp, "msg"), os.path.join(tmp, "sig")
|
|
49
|
+
with open(msg, "wb") as handle: handle.write(b"external-verify probe")
|
|
50
|
+
for argv in (["openssl", "genpkey", "-algorithm", "ed25519", "-out", priv],
|
|
51
|
+
["openssl", "pkey", "-in", priv, "-pubout", "-outform", "DER", "-out", pub],
|
|
52
|
+
["openssl", "pkeyutl", "-sign", "-inkey", priv, "-rawin", "-in", msg, "-out", sig]):
|
|
53
|
+
if subprocess.run(argv, capture_output=True).returncode != 0:
|
|
54
|
+
return False
|
|
55
|
+
run = subprocess.run(["openssl", "pkeyutl", "-verify", "-pubin", "-keyform", "DER",
|
|
56
|
+
"-inkey", pub, "-rawin", "-in", msg, "-sigfile", sig],
|
|
57
|
+
capture_output=True, text=True)
|
|
58
|
+
return "Signature Verified Successfully" in (run.stdout + run.stderr)
|
|
59
|
+
|
|
60
|
+
def openssl_verify(spki_der, sig, message):
|
|
61
|
+
"""After the capability probe, any outcome that is not explicit success is a
|
|
62
|
+
verification failure — a bundle-supplied bad key never reads as 'inconclusive'."""
|
|
63
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
64
|
+
paths = {}
|
|
65
|
+
for name, blob in (("key", spki_der), ("sig", sig), ("msg", message)):
|
|
66
|
+
paths[name] = os.path.join(tmp, name)
|
|
67
|
+
with open(paths[name], "wb") as handle: handle.write(blob)
|
|
68
|
+
run = subprocess.run(["openssl", "pkeyutl", "-verify", "-pubin", "-keyform", "DER",
|
|
69
|
+
"-inkey", paths["key"], "-rawin", "-in", paths["msg"], "-sigfile", paths["sig"]],
|
|
70
|
+
capture_output=True, text=True)
|
|
71
|
+
return "Signature Verified Successfully" in (run.stdout + run.stderr)
|
|
72
|
+
|
|
73
|
+
def pae(payload_type, payload):
|
|
74
|
+
t = payload_type.encode()
|
|
75
|
+
return b"DSSEv1 %d %s %d %s" % (len(t), t, len(payload), payload)
|
|
76
|
+
|
|
77
|
+
if len(sys.argv) != 2:
|
|
78
|
+
print("usage: external-verify.py <bundle-dir>", file=sys.stderr)
|
|
79
|
+
sys.exit(2)
|
|
80
|
+
root = sys.argv[1]
|
|
81
|
+
if not os.path.isdir(root):
|
|
82
|
+
print(f"not a directory: {root}", file=sys.stderr)
|
|
83
|
+
sys.exit(2)
|
|
84
|
+
if not probe_openssl():
|
|
85
|
+
print("openssl cannot sign/verify Ed25519 here (need OpenSSL 3+); cannot check signatures", file=sys.stderr)
|
|
86
|
+
sys.exit(2)
|
|
87
|
+
|
|
88
|
+
root_real = os.path.realpath(root)
|
|
89
|
+
|
|
90
|
+
def resolve(rel):
|
|
91
|
+
"""Bundle-relative read with containment: reject absolute, empty, '.', '..',
|
|
92
|
+
and backslash segments; refuse symlinks; refuse any real path outside the
|
|
93
|
+
bundle. The reference verifier hardens exactly this, and a reference tool
|
|
94
|
+
that dropped it would teach implementers to drop it too."""
|
|
95
|
+
if not isinstance(rel, str) or rel != rel.strip() or rel.startswith("/") or "\\" in rel:
|
|
96
|
+
raise ValueError(f"unsafe path {rel!r}")
|
|
97
|
+
parts = rel.split("/")
|
|
98
|
+
if any(part in ("", ".", "..") for part in parts):
|
|
99
|
+
raise ValueError(f"unsafe path {rel!r}")
|
|
100
|
+
full = os.path.join(root, *parts)
|
|
101
|
+
walk = root
|
|
102
|
+
for part in parts:
|
|
103
|
+
walk = os.path.join(walk, part)
|
|
104
|
+
if os.path.islink(walk):
|
|
105
|
+
raise ValueError(f"symlink not allowed in {rel!r}")
|
|
106
|
+
if os.path.realpath(full) != os.path.join(root_real, *parts):
|
|
107
|
+
raise ValueError(f"path escapes bundle {rel!r}")
|
|
108
|
+
return full
|
|
109
|
+
|
|
110
|
+
def read(rel): return open(resolve(rel), "rb").read()
|
|
111
|
+
|
|
112
|
+
failures, skipped = [], []
|
|
113
|
+
def check(name, fn):
|
|
114
|
+
try:
|
|
115
|
+
problems = fn()
|
|
116
|
+
except Exception as error: # a missing, malformed, or unsafe input fails the check
|
|
117
|
+
problems = [f"{type(error).__name__}: {error}"]
|
|
118
|
+
if problems == "skip":
|
|
119
|
+
print(f"CHECK {name}: skipped"); skipped.append(name); return
|
|
120
|
+
print(f"CHECK {name}: " + ("ok" if not problems else "FAIL " + "; ".join(str(p) for p in problems[:3])))
|
|
121
|
+
if problems: failures.append(name)
|
|
122
|
+
|
|
123
|
+
manifest = json.loads(read("bundle.json"))
|
|
124
|
+
trust = json.loads(read("trust/public-keys.json"))
|
|
125
|
+
claim = json.loads(read("claim-package.json"))
|
|
126
|
+
envelope = json.loads(read("report-envelope.json"))
|
|
127
|
+
|
|
128
|
+
def manifest_files():
|
|
129
|
+
problems = []
|
|
130
|
+
for entry in manifest["files"]:
|
|
131
|
+
try:
|
|
132
|
+
blob = read(entry["path"])
|
|
133
|
+
except Exception as error:
|
|
134
|
+
problems.append(f"{entry['path']!r}: {error}"); continue
|
|
135
|
+
if len(blob) != entry["bytes"] or sha256(blob) != entry["sha256"]:
|
|
136
|
+
problems.append(f"{entry['path']} does not match its manifest entry")
|
|
137
|
+
listed = {entry["path"] for entry in manifest["files"]}
|
|
138
|
+
on_disk = {os.path.relpath(os.path.join(base, name), root).replace(os.sep, "/")
|
|
139
|
+
for base, _, names in os.walk(root) for name in names}
|
|
140
|
+
problems += [f"unlisted file {path}" for path in sorted(on_disk - listed - {"bundle.json"})]
|
|
141
|
+
problems += [f"missing file {path}" for path in sorted(listed - on_disk)]
|
|
142
|
+
return problems
|
|
143
|
+
|
|
144
|
+
def record_bytes(digest):
|
|
145
|
+
if not HEX64.match(digest):
|
|
146
|
+
raise ValueError(f"record name is not a 64-hex digest: {digest!r}")
|
|
147
|
+
return read(f"records/{digest}.bin")
|
|
148
|
+
|
|
149
|
+
def cas_records():
|
|
150
|
+
problems = []
|
|
151
|
+
for name in os.listdir(os.path.join(root, "records")):
|
|
152
|
+
if not name.endswith(".bin") or not HEX64.match(name[:-4]):
|
|
153
|
+
problems.append(f"records/{name} is not a <64-hex>.bin record"); continue
|
|
154
|
+
if sha256(read(f"records/{name}")) != name[:-4]:
|
|
155
|
+
problems.append(f"records/{name} bytes do not hash to its name")
|
|
156
|
+
return problems
|
|
157
|
+
|
|
158
|
+
def sealed_bytes():
|
|
159
|
+
problems = []
|
|
160
|
+
if strict_b64(envelope["payload"], "report envelope payload") != read("report.json"):
|
|
161
|
+
problems.append("report.json is not the exact DSSE payload bytes")
|
|
162
|
+
for field, rel in (("benchmarkSha256", "benchmark.json"), ("runSha256", "run.json"),
|
|
163
|
+
("matrixSha256", "matrix.json"), ("reportSha256", "report.json"),
|
|
164
|
+
("reportEnvelopeSha256", "report-envelope.json")):
|
|
165
|
+
if sha256(read(rel)) != claim["records"][field]:
|
|
166
|
+
problems.append(f"{rel} does not match claim records.{field}")
|
|
167
|
+
return problems
|
|
168
|
+
|
|
169
|
+
def report_signature():
|
|
170
|
+
spki = strict_b64(trust["report"]["spkiDerBase64"], "report SPKI")
|
|
171
|
+
signature = next(s for s in envelope["signatures"] if s.get("keyid") == trust["report"]["keyId"])
|
|
172
|
+
message = pae(envelope["payloadType"], strict_b64(envelope["payload"], "report envelope payload"))
|
|
173
|
+
return [] if openssl_verify(spki, strict_b64(signature["sig"], "report signature"), message) else ["report DSSE signature invalid"]
|
|
174
|
+
|
|
175
|
+
def report_pins_matrix():
|
|
176
|
+
report = json.loads(read("report.json"))
|
|
177
|
+
digests = [subject.get("digest", {}).get("sha256") for subject in report.get("subjects", [])]
|
|
178
|
+
return [] if sha256(read("matrix.json")) in digests else ["signed report does not pin matrix.json"]
|
|
179
|
+
|
|
180
|
+
def verdict_signatures():
|
|
181
|
+
problems, keys = [], {e["keyId"]: strict_b64(e["spkiDerBase64"], "evaluator SPKI") for e in trust["evaluators"]}
|
|
182
|
+
for verdict in json.loads(read("verdicts.json"))["verdicts"]:
|
|
183
|
+
env = json.loads(record_bytes(verdict["sha256"]))
|
|
184
|
+
signature = next(s for s in env["signatures"] if s.get("keyid") == verdict["keyId"])
|
|
185
|
+
message = pae(env["payloadType"], strict_b64(env["payload"], "verdict payload"))
|
|
186
|
+
if not openssl_verify(keys[verdict["keyId"]], strict_b64(signature["sig"], "verdict signature"), message):
|
|
187
|
+
problems.append(f"verdict {verdict['sha256'][:12]} signature invalid")
|
|
188
|
+
return problems
|
|
189
|
+
|
|
190
|
+
def matrix_verdict_closure():
|
|
191
|
+
problems = []
|
|
192
|
+
catalog = {v["sha256"]: set(v["cellKeys"]) for v in json.loads(read("verdicts.json"))["verdicts"]}
|
|
193
|
+
for cell in json.loads(read("matrix.json"))["cells"]:
|
|
194
|
+
for ref in cell.get("verdicts", []) + cell.get("validVerdicts", []):
|
|
195
|
+
digest = ref.split(":", 1)[1]
|
|
196
|
+
if digest not in catalog:
|
|
197
|
+
problems.append(f"cell {cell['cellKey']} cites uncataloged verdict {digest[:12]}"); continue
|
|
198
|
+
if cell["cellKey"] not in catalog[digest]:
|
|
199
|
+
problems.append(f"verdict {digest[:12]} does not name cell {cell['cellKey']}")
|
|
200
|
+
if not os.path.exists(os.path.join(root, "records", f"{digest}.bin")):
|
|
201
|
+
problems.append(f"verdict record {digest[:12]} missing from the store")
|
|
202
|
+
return problems
|
|
203
|
+
|
|
204
|
+
def claim_mirror():
|
|
205
|
+
if "headline" not in claim:
|
|
206
|
+
return "skip" # comparison-shaped (paired-delta) claims carry no headline to mirror
|
|
207
|
+
report = json.loads(read("report.json"))
|
|
208
|
+
arms = report["results"]["perSubject"][0]["results"]["arms"]
|
|
209
|
+
return [] if claim["headline"] == arms else ["claim headline disagrees with the signed report's results"]
|
|
210
|
+
|
|
211
|
+
def key_derivations():
|
|
212
|
+
problems = []
|
|
213
|
+
spki = strict_b64(trust["report"]["spkiDerBase64"], "report SPKI")
|
|
214
|
+
if len(spki) != 44 or spki[:12] != bytes([0x30, 0x2A, 0x30, 0x05, 0x06, 0x03, 0x2B, 0x65, 0x70, 0x03, 0x21, 0x00]):
|
|
215
|
+
problems.append("report SPKI is not a 44-byte Ed25519 SubjectPublicKeyInfo")
|
|
216
|
+
else:
|
|
217
|
+
derived = "did:key:z" + b58(bytes([0xED, 0x01]) + spki[-32:])
|
|
218
|
+
if not (derived == trust["report"]["didKey"] == trust["report"]["keyId"] == trust["report"]["author"]):
|
|
219
|
+
problems.append("report did:key/author does not derive from the carried SPKI")
|
|
220
|
+
for evaluator in trust["evaluators"]:
|
|
221
|
+
expected = "benchmark-product-verdict-" + sha256(strict_b64(evaluator["spkiDerBase64"], "evaluator SPKI"))[:16]
|
|
222
|
+
if evaluator["keyId"] != expected:
|
|
223
|
+
problems.append(f"evaluator keyId {evaluator['keyId']} does not derive from its SPKI")
|
|
224
|
+
return problems
|
|
225
|
+
|
|
226
|
+
for name, fn in (("manifest-files", manifest_files), ("cas-records", cas_records),
|
|
227
|
+
("sealed-bytes", sealed_bytes), ("report-signature", report_signature),
|
|
228
|
+
("report-pins-matrix", report_pins_matrix), ("verdict-signatures", verdict_signatures),
|
|
229
|
+
("matrix-verdict-closure", matrix_verdict_closure), ("claim-mirror", claim_mirror),
|
|
230
|
+
("key-derivations", key_derivations)):
|
|
231
|
+
check(name, fn)
|
|
232
|
+
|
|
233
|
+
print()
|
|
234
|
+
print("These checks prove internal consistency and that the bundle is signed by the")
|
|
235
|
+
print("keys the bundle itself names. They do NOT re-derive the matrix, recompute the")
|
|
236
|
+
print("statistical method, or rebuild the claim package (the reference verifier does),")
|
|
237
|
+
print("and no tool can prove the producing venue was honest.")
|
|
238
|
+
sys.exit(1 if failures else 0)
|