@primust/verifier 1.0.0 → 1.1.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.
Files changed (49) hide show
  1. package/dist/chunk-LTWQK3HT.js +432 -0
  2. package/dist/chunk-NOADQWB6.js +3012 -0
  3. package/dist/cli.d.ts +3 -2
  4. package/dist/cli.js +309 -361
  5. package/dist/index.d.ts +335 -13
  6. package/dist/index.js +1181 -13
  7. package/dist/tsa-chain-7KSQ5LAH.js +235 -0
  8. package/dist/v29-envelope-GFVVA2S6.js +42 -0
  9. package/package.json +7 -8
  10. package/dist/bounded-trace.d.ts +0 -46
  11. package/dist/bounded-trace.d.ts.map +0 -1
  12. package/dist/bounded-trace.js +0 -558
  13. package/dist/bounded-trace.js.map +0 -1
  14. package/dist/cli.d.ts.map +0 -1
  15. package/dist/cli.js.map +0 -1
  16. package/dist/index.d.ts.map +0 -1
  17. package/dist/index.js.map +0 -1
  18. package/dist/key-cache.d.ts +0 -20
  19. package/dist/key-cache.d.ts.map +0 -1
  20. package/dist/key-cache.js +0 -68
  21. package/dist/key-cache.js.map +0 -1
  22. package/dist/scoped.d.ts +0 -35
  23. package/dist/scoped.d.ts.map +0 -1
  24. package/dist/scoped.js +0 -582
  25. package/dist/scoped.js.map +0 -1
  26. package/dist/types.d.ts +0 -60
  27. package/dist/types.d.ts.map +0 -1
  28. package/dist/types.js +0 -5
  29. package/dist/types.js.map +0 -1
  30. package/dist/upstream_resolver.d.ts +0 -60
  31. package/dist/upstream_resolver.d.ts.map +0 -1
  32. package/dist/upstream_resolver.js +0 -126
  33. package/dist/upstream_resolver.js.map +0 -1
  34. package/dist/v29-envelope.d.ts +0 -55
  35. package/dist/v29-envelope.d.ts.map +0 -1
  36. package/dist/v29-envelope.js +0 -450
  37. package/dist/v29-envelope.js.map +0 -1
  38. package/dist/verifier.d.ts +0 -36
  39. package/dist/verifier.d.ts.map +0 -1
  40. package/dist/verifier.js +0 -1235
  41. package/dist/verifier.js.map +0 -1
  42. package/dist/verifier.test.d.ts +0 -2
  43. package/dist/verifier.test.d.ts.map +0 -1
  44. package/dist/verifier.test.js +0 -395
  45. package/dist/verifier.test.js.map +0 -1
  46. package/dist/verify-html-template.d.ts +0 -45
  47. package/dist/verify-html-template.d.ts.map +0 -1
  48. package/dist/verify-html-template.js +0 -182
  49. package/dist/verify-html-template.js.map +0 -1
@@ -0,0 +1,235 @@
1
+ // src/tsa-chain.ts
2
+ import { X509Certificate, createHash } from "crypto";
3
+ var DIGICERT_TRUSTED_ROOT_G4_FPR_HEX = "552f7bdcf1a7af9e6ce672017f4f12abf77240c78e761ac203d1d9d20ac89988";
4
+ var TIME_STAMPING_EKU_OID = "1.3.6.1.5.5.7.3.8";
5
+ var DIGICERT_TRUSTED_ROOT_G4_PEM = `-----BEGIN CERTIFICATE-----
6
+ MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi
7
+ MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
8
+ d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg
9
+ RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV
10
+ UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu
11
+ Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG
12
+ SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y
13
+ ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If
14
+ xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV
15
+ ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO
16
+ DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ
17
+ jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/
18
+ CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi
19
+ EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM
20
+ fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY
21
+ uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK
22
+ chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t
23
+ 9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
24
+ hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD
25
+ ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2
26
+ SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd
27
+ +SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc
28
+ fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa
29
+ sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N
30
+ cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N
31
+ 0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie
32
+ 4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI
33
+ r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1
34
+ /YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm
35
+ gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+
36
+ -----END CERTIFICATE-----`;
37
+ function loadBundledRoots() {
38
+ const out = [];
39
+ try {
40
+ out.push(new X509Certificate(DIGICERT_TRUSTED_ROOT_G4_PEM));
41
+ } catch {
42
+ }
43
+ return out;
44
+ }
45
+ var BUNDLED_ROOTS = loadBundledRoots();
46
+ function trustedRootFprs() {
47
+ const env = globalThis.process?.env?.PRIMUST_TSA_ROOT_FPR;
48
+ if (!env || !env.trim()) {
49
+ return /* @__PURE__ */ new Set([DIGICERT_TRUSTED_ROOT_G4_FPR_HEX]);
50
+ }
51
+ return new Set(
52
+ env.split(",").map((f) => f.trim().toLowerCase().replace(/:/g, "")).filter(Boolean)
53
+ );
54
+ }
55
+ function verifyTsaChain(tsTokenB64) {
56
+ let tokenDer;
57
+ try {
58
+ tokenDer = Buffer.from(tsTokenB64, "base64");
59
+ } catch {
60
+ return { ok: false, reason: "parse_error" };
61
+ }
62
+ let certs;
63
+ try {
64
+ certs = extractCertificates(tokenDer);
65
+ } catch {
66
+ return { ok: false, reason: "parse_error" };
67
+ }
68
+ if (certs.length === 0) {
69
+ return { ok: false, reason: "no_certs" };
70
+ }
71
+ const trusted = trustedRootFprs();
72
+ let leaf = null;
73
+ for (const c of certs) {
74
+ if (hasTimeStampingEku(c)) {
75
+ leaf = c;
76
+ break;
77
+ }
78
+ }
79
+ if (!leaf) {
80
+ const subjects = new Set(certs.map((c) => c.subject));
81
+ const issuers = new Set(certs.map((c) => c.issuer));
82
+ for (const c of certs) {
83
+ if (subjects.has(c.subject) && !issuers.has(c.subject)) {
84
+ leaf = c;
85
+ break;
86
+ }
87
+ }
88
+ }
89
+ if (!leaf) {
90
+ return { ok: false, reason: "leaf_not_found" };
91
+ }
92
+ if (!hasTimeStampingEku(leaf)) {
93
+ return { ok: false, reason: "missing_eku" };
94
+ }
95
+ if (!isWithinValidity(leaf)) {
96
+ return { ok: false, reason: "expired" };
97
+ }
98
+ const candidateRoots = BUNDLED_ROOTS.filter(
99
+ (r) => trusted.has(certFingerprint(r))
100
+ );
101
+ const opRootPem = globalThis.process?.env?.PRIMUST_TSA_ROOT_PEM;
102
+ if (opRootPem) {
103
+ try {
104
+ const opRoot = new X509Certificate(opRootPem);
105
+ if (trusted.has(certFingerprint(opRoot))) {
106
+ candidateRoots.push(opRoot);
107
+ }
108
+ } catch {
109
+ return { ok: false, reason: "parse_error" };
110
+ }
111
+ }
112
+ if (candidateRoots.length === 0) {
113
+ return { ok: false, reason: "root_not_trusted" };
114
+ }
115
+ const bySubject = /* @__PURE__ */ new Map();
116
+ for (const c of certs) bySubject.set(c.subject, c);
117
+ const byRootSubject = /* @__PURE__ */ new Map();
118
+ for (const r of candidateRoots) byRootSubject.set(r.subject, r);
119
+ let current = leaf;
120
+ const seen = /* @__PURE__ */ new Set();
121
+ for (let hop = 0; hop < 8; hop++) {
122
+ if (seen.has(current.subject)) {
123
+ return { ok: false, reason: "chain_incomplete" };
124
+ }
125
+ seen.add(current.subject);
126
+ const rootCert = byRootSubject.get(current.issuer);
127
+ if (rootCert) {
128
+ if (!current.verify(rootCert.publicKey)) {
129
+ return { ok: false, reason: "signature_invalid" };
130
+ }
131
+ if (!isWithinValidity(rootCert)) {
132
+ return { ok: false, reason: "expired" };
133
+ }
134
+ return { ok: true, reason: "ok" };
135
+ }
136
+ const issuerCert = bySubject.get(current.issuer);
137
+ if (issuerCert && issuerCert !== current) {
138
+ if (!current.verify(issuerCert.publicKey)) {
139
+ return { ok: false, reason: "signature_invalid" };
140
+ }
141
+ if (!isWithinValidity(issuerCert)) {
142
+ return { ok: false, reason: "expired" };
143
+ }
144
+ if (issuerCert.subject === issuerCert.issuer) {
145
+ if (trusted.has(certFingerprint(issuerCert))) {
146
+ return { ok: true, reason: "ok" };
147
+ }
148
+ return { ok: false, reason: "root_not_trusted" };
149
+ }
150
+ current = issuerCert;
151
+ continue;
152
+ }
153
+ return { ok: false, reason: "chain_incomplete" };
154
+ }
155
+ return { ok: false, reason: "chain_incomplete" };
156
+ }
157
+ function certFingerprint(cert) {
158
+ return createHash("sha256").update(cert.raw).digest("hex");
159
+ }
160
+ function hasTimeStampingEku(cert) {
161
+ const info = cert.toString();
162
+ return info.includes(TIME_STAMPING_EKU_OID) || info.includes("Time Stamping");
163
+ }
164
+ function isWithinValidity(cert) {
165
+ const now = Date.now();
166
+ const nb = Date.parse(cert.validFrom);
167
+ const na = Date.parse(cert.validTo);
168
+ if (Number.isNaN(nb) || Number.isNaN(na)) return false;
169
+ return nb <= now && now <= na;
170
+ }
171
+ function extractCertificates(tokenDer) {
172
+ const signedDataOid = Buffer.from([
173
+ 6,
174
+ 9,
175
+ 42,
176
+ 134,
177
+ 72,
178
+ 134,
179
+ 247,
180
+ 13,
181
+ 1,
182
+ 7,
183
+ 2
184
+ ]);
185
+ const oidIdx = tokenDer.indexOf(signedDataOid);
186
+ if (oidIdx === -1) return [];
187
+ const scanStart = oidIdx + signedDataOid.length;
188
+ const limit = tokenDer.length;
189
+ for (let i = scanStart; i < limit - 2; i++) {
190
+ if (tokenDer[i] === 160) {
191
+ const lenInfo = parseDerLength(tokenDer, i + 1);
192
+ if (!lenInfo) continue;
193
+ const { length, headerLen } = lenInfo;
194
+ const setStart = i + 1 + headerLen;
195
+ const setEnd = setStart + length;
196
+ if (setEnd > limit) continue;
197
+ const certs = [];
198
+ let p = setStart;
199
+ while (p < setEnd) {
200
+ if (tokenDer[p] !== 48) break;
201
+ const li = parseDerLength(tokenDer, p + 1);
202
+ if (!li) break;
203
+ const certLen = li.length;
204
+ const certHdr = li.headerLen;
205
+ const certEnd = p + 1 + certHdr + certLen;
206
+ if (certEnd > setEnd) break;
207
+ const certDer = tokenDer.subarray(p, certEnd);
208
+ try {
209
+ certs.push(new X509Certificate(certDer));
210
+ } catch {
211
+ }
212
+ p = certEnd;
213
+ }
214
+ if (certs.length > 0) return certs;
215
+ }
216
+ }
217
+ return [];
218
+ }
219
+ function parseDerLength(buf, offset) {
220
+ if (offset >= buf.length) return null;
221
+ const first = buf[offset];
222
+ if ((first & 128) === 0) {
223
+ return { length: first, headerLen: 1 };
224
+ }
225
+ const n = first & 127;
226
+ if (n === 0 || n > 4 || offset + n >= buf.length) return null;
227
+ let val = 0;
228
+ for (let i = 0; i < n; i++) {
229
+ val = val << 8 | buf[offset + 1 + i];
230
+ }
231
+ return { length: val, headerLen: 1 + n };
232
+ }
233
+ export {
234
+ verifyTsaChain
235
+ };
@@ -0,0 +1,42 @@
1
+ import {
2
+ ALLOWED_KINDS,
3
+ ALLOWED_TRUST_EDGES,
4
+ ENVELOPE_VERSION,
5
+ PROOF_TIER_HIERARCHY,
6
+ canonicalHash,
7
+ canonicalJson,
8
+ reproduceAggregations,
9
+ reproduceManifestCanonicalHash,
10
+ reproduceTierCeiling,
11
+ v29Fail,
12
+ v29Pass,
13
+ validateAggregations,
14
+ validateEnvelopeShape,
15
+ validateManifestCanonicalHash,
16
+ validateManifestSignature,
17
+ validateRecordsAgainstHarness,
18
+ validateRuntimeBindingHash,
19
+ validateRuntimeBindingSignature,
20
+ verifyV29
21
+ } from "./chunk-LTWQK3HT.js";
22
+ export {
23
+ ALLOWED_KINDS,
24
+ ALLOWED_TRUST_EDGES,
25
+ ENVELOPE_VERSION,
26
+ PROOF_TIER_HIERARCHY,
27
+ canonicalHash,
28
+ canonicalJson,
29
+ reproduceAggregations,
30
+ reproduceManifestCanonicalHash,
31
+ reproduceTierCeiling,
32
+ v29Fail,
33
+ v29Pass,
34
+ validateAggregations,
35
+ validateEnvelopeShape,
36
+ validateManifestCanonicalHash,
37
+ validateManifestSignature,
38
+ validateRecordsAgainstHarness,
39
+ validateRuntimeBindingHash,
40
+ validateRuntimeBindingSignature,
41
+ verifyV29
42
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primust/verifier",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Offline and CLI verifier for Primust VPECs. Free forever. No account required.",
5
5
  "homepage": "https://primust.com",
6
6
  "repository": {
@@ -15,15 +15,10 @@
15
15
  "primust-verify": "dist/cli.js"
16
16
  },
17
17
  "scripts": {
18
- "build": "tsc",
18
+ "build": "tsup src/index.ts src/cli.ts --format esm --dts --target node20",
19
19
  "test": "vitest run",
20
20
  "test:list": "vitest list --filesOnly"
21
21
  },
22
- "dependencies": {
23
- "@primust/artifact-core": "^1.0.0",
24
- "@noble/hashes": "^1.8.0",
25
- "@zkpassport/poseidon2": "^0.6.2"
26
- },
27
22
  "peerDependencies": {
28
23
  "better-sqlite3": "^12.0.0"
29
24
  },
@@ -35,9 +30,13 @@
35
30
  "devDependencies": {
36
31
  "@types/node": "^22.0.0",
37
32
  "better-sqlite3": "^12.6.2",
33
+ "tsup": "^8.5.1",
38
34
  "tsx": "^4.19.0",
39
35
  "typescript": "^5.6.0",
40
- "vitest": "^3.0.0"
36
+ "vitest": "^3.0.0",
37
+ "@primust/artifact-core": "^1.0.0",
38
+ "@noble/hashes": "^1.8.0",
39
+ "@zkpassport/poseidon2": "^0.6.2"
41
40
  },
42
41
  "files": [
43
42
  "dist"
@@ -1,46 +0,0 @@
1
- /**
2
- * Reference TypeScript interpreter for bounded_trace_v1 verification.
3
- *
4
- * This is the canonical second-opinion verifier described in PRIMUST_V27 §20.2,
5
- * ported from verifier-py/src/primust_verify/bounded_trace.py. It is
6
- * intentionally small and obviously-correct: consumes a BoundedTraceV1 payload
7
- * plus a ProfileRecord and produces one of the §16.3 canonical downgrade
8
- * reason codes (or "ok" on success).
9
- *
10
- * Crypto used: SHA-256 with RFC 6962-style domain-separated leaves (0x00)
11
- * and nodes (0x01). Canonical JSON follows RFC 8785 + ECMAScript 7.1.12.1
12
- * number serialization for cross-language parity.
13
- *
14
- * SI-3 (VPEC verifiability): this module's output MUST match the Python
15
- * reference byte-for-byte across the shared conformance fixture corpus at
16
- * packages/verifier-py/tests/fixtures/bounded_trace_v1/. The conformance
17
- * runner in scripts/lane_a_conformance.* enforces this.
18
- *
19
- * Zero runtime dependencies besides node:crypto.
20
- */
21
- export declare function canonicalNumber(x: number): string;
22
- /**
23
- * Canonical JSON serialization:
24
- * - object keys sorted lexicographically
25
- * - UTF-8
26
- * - separators ",":":", no whitespace
27
- * - numbers via canonicalNumber
28
- * - strings via JSON.stringify (same escape rules as Python json with ensure_ascii=False)
29
- */
30
- export declare function canonicalJson(obj: unknown): Uint8Array;
31
- export declare function canonicalJsonString(obj: unknown): string;
32
- export declare function buildMerkleRoot(leaves: Uint8Array[]): string;
33
- export interface BoundedTraceResult {
34
- valid: boolean;
35
- proof_level: "operator_bound" | "execution";
36
- reason: string;
37
- disclosed_operator_count: number;
38
- verified_merkle_paths: number;
39
- details: Record<string, unknown>;
40
- }
41
- export declare const REASONS: readonly ["profile_not_found", "profile_signature_invalid", "profile_not_empirical", "profile_expired", "profile_revoked", "profile_no_freshness_window", "profile_trace_mismatch", "runtime_not_supported", "trace_schema_unknown", "missing_merkle_root", "merkle_inclusion_failed", "threshold_violation", "runtime_section_missing", "retrieval_section_missing", "closed_api_pre_cohort", "closed_api_pre_promote", "closed_api_v2_promoted", "profile_deprecated", "unknown_lifecycle_state"];
42
- export type ReasonCode = (typeof REASONS)[number] | "thresholds_verified";
43
- export declare function verifyBoundedTrace(trace: Record<string, unknown>, profile: Record<string, unknown>, options?: {
44
- now?: Date;
45
- }): BoundedTraceResult;
46
- //# sourceMappingURL=bounded-trace.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bounded-trace.d.ts","sourceRoot":"","sources":["../src/bounded-trace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAMH,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CA6BjD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,UAAU,CAGtD;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAiBxD;AAiCD,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,CAgB5D;AAID,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,gBAAgB,GAAG,WAAW,CAAC;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,wBAAwB,EAAE,MAAM,CAAC;IACjC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAID,eAAO,MAAM,OAAO,qeA0BV,CAAC;AAEX,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,OAAO,CAAC,CAAC,MAAM,CAAC,GAAG,qBAAqB,CAAC;AAsS1E,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,IAAI,CAAA;CAAO,GAC3B,kBAAkB,CA2JpB"}