@peac/protocol 0.11.2 → 0.12.0-preview.1
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 +68 -3
- package/dist/discovery.d.ts.map +1 -1
- package/dist/index.cjs +311 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +309 -20
- package/dist/index.mjs.map +1 -1
- package/dist/issue.d.ts +60 -1
- package/dist/issue.d.ts.map +1 -1
- package/dist/jwks-resolver.d.ts +20 -0
- package/dist/jwks-resolver.d.ts.map +1 -1
- package/dist/policy-binding.d.ts +55 -0
- package/dist/policy-binding.d.ts.map +1 -0
- package/dist/verify-local.cjs +155 -12
- package/dist/verify-local.cjs.map +1 -1
- package/dist/verify-local.d.ts +94 -21
- package/dist/verify-local.d.ts.map +1 -1
- package/dist/verify-local.mjs +156 -14
- package/dist/verify-local.mjs.map +1 -1
- package/dist/verify.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * from './verify-local';
|
|
|
8
8
|
export * from './headers';
|
|
9
9
|
export * from './discovery';
|
|
10
10
|
export * from './jwks-resolver';
|
|
11
|
+
export { computePolicyDigestJcs, checkPolicyBinding } from './policy-binding';
|
|
11
12
|
export * from './verifier-types';
|
|
12
13
|
export * from './verifier-core';
|
|
13
14
|
export * from './verification-report';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAGhC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAGhC,OAAO,EACL,eAAe,EACf,eAAe,EACf,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,WAAW,EACX,SAAS,EACT,MAAM,GACP,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAGhC,OAAO,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAG9E,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAGhC,OAAO,EACL,eAAe,EACf,eAAe,EACf,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,WAAW,EACX,SAAS,EACT,MAAM,GACP,MAAM,cAAc,CAAC"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { uuidv7 } from 'uuidv7';
|
|
2
|
-
import { sign, decode, verify, sha256Hex, computeJwkThumbprint, jwkToPublicKeyBytes, base64urlDecode } from '@peac/crypto';
|
|
2
|
+
import { sign, signWire02, decode, verify, jcsHash, sha256Hex, computeJwkThumbprint, jwkToPublicKeyBytes, base64urlDecode } from '@peac/crypto';
|
|
3
3
|
export { base64urlDecode, base64urlEncode, computeJwkThumbprint, generateKeypair, jwkToPublicKeyBytes, sha256Bytes, sha256Hex, verify } from '@peac/crypto';
|
|
4
4
|
import { ZodError } from 'zod';
|
|
5
|
-
import { isValidPurposeToken, isCanonicalPurpose, isValidPurposeReason, isValidWorkflowContext, createWorkflowContextInvalidError, hasValidDagSemantics, createWorkflowDagInvalidError, WORKFLOW_EXTENSION_KEY, validateKernelConstraints, createConstraintViolationError, ReceiptClaims, createEvidenceNotJsonError, validateSubjectSnapshot, PEAC_ISSUER_CONFIG_MAX_BYTES, PEAC_ISSUER_CONFIG_PATH, PEAC_POLICY_MAX_BYTES, PEAC_POLICY_PATH, PEAC_POLICY_FALLBACK_PATH, parseReceiptClaims, PEAC_RECEIPT_HEADER, PEAC_PURPOSE_HEADER, parsePurposeHeader, PEAC_PURPOSE_APPLIED_HEADER, PEAC_PURPOSE_REASON_HEADER } from '@peac/schema';
|
|
5
|
+
import { isValidPurposeToken, isCanonicalPurpose, isValidPurposeReason, isValidWorkflowContext, createWorkflowContextInvalidError, hasValidDagSemantics, createWorkflowDagInvalidError, WORKFLOW_EXTENSION_KEY, validateKernelConstraints, createConstraintViolationError, ReceiptClaims, createEvidenceNotJsonError, validateSubjectSnapshot, isCanonicalIss, Wire02ClaimsSchema, PEAC_ISSUER_CONFIG_MAX_BYTES, validateRevokedKeys, PEAC_ISSUER_CONFIG_PATH, PEAC_POLICY_MAX_BYTES, PEAC_POLICY_PATH, PEAC_POLICY_FALLBACK_PATH, WARNING_TYP_MISSING, parseReceiptClaims, checkOccurredAtSkew, REGISTERED_RECEIPT_TYPES, WARNING_TYPE_UNREGISTERED, REGISTERED_EXTENSION_GROUP_KEYS, isValidExtensionKey, WARNING_UNKNOWN_EXTENSION, verifyPolicyBinding, sortWarnings, PEAC_RECEIPT_HEADER, PEAC_PURPOSE_HEADER, parsePurposeHeader, PEAC_PURPOSE_APPLIED_HEADER, PEAC_PURPOSE_REASON_HEADER } from '@peac/schema';
|
|
6
6
|
import { createHash } from 'crypto';
|
|
7
|
-
import { VERIFIER_LIMITS, VERIFIER_NETWORK, VERIFIER_POLICY_VERSION, VERIFICATION_REPORT_VERSION, WIRE_TYPE } from '@peac/kernel';
|
|
7
|
+
import { VERIFIER_LIMITS, VERIFIER_NETWORK, HASH, VERIFIER_POLICY_VERSION, VERIFICATION_REPORT_VERSION, WIRE_TYPE } from '@peac/kernel';
|
|
8
8
|
|
|
9
9
|
// src/issue.ts
|
|
10
10
|
function fireTelemetryHook(fn, input) {
|
|
@@ -178,6 +178,52 @@ async function issueJws(options) {
|
|
|
178
178
|
const result = await issue(options);
|
|
179
179
|
return result.jws;
|
|
180
180
|
}
|
|
181
|
+
async function issueWire02(options) {
|
|
182
|
+
if (!isCanonicalIss(options.iss)) {
|
|
183
|
+
throw new IssueError({
|
|
184
|
+
code: "E_ISS_NOT_CANONICAL",
|
|
185
|
+
category: "validation",
|
|
186
|
+
severity: "error",
|
|
187
|
+
retryable: false,
|
|
188
|
+
http_status: 400,
|
|
189
|
+
details: {
|
|
190
|
+
message: `iss is not in canonical form: "${options.iss}". Use https:// origin or did: identifier.`
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
const jti = options.jti ?? uuidv7();
|
|
195
|
+
const iat = Math.floor(Date.now() / 1e3);
|
|
196
|
+
const claims = {
|
|
197
|
+
peac_version: "0.2",
|
|
198
|
+
kind: options.kind,
|
|
199
|
+
type: options.type,
|
|
200
|
+
iss: options.iss,
|
|
201
|
+
iat,
|
|
202
|
+
jti,
|
|
203
|
+
...options.sub !== void 0 && { sub: options.sub },
|
|
204
|
+
...options.pillars !== void 0 && { pillars: options.pillars },
|
|
205
|
+
...options.occurred_at !== void 0 && { occurred_at: options.occurred_at },
|
|
206
|
+
...options.purpose_declared !== void 0 && { purpose_declared: options.purpose_declared },
|
|
207
|
+
...options.policy !== void 0 && { policy: options.policy },
|
|
208
|
+
...options.extensions !== void 0 && { extensions: options.extensions }
|
|
209
|
+
};
|
|
210
|
+
const parseResult = Wire02ClaimsSchema.safeParse(claims);
|
|
211
|
+
if (!parseResult.success) {
|
|
212
|
+
const firstIssue = parseResult.error.issues[0];
|
|
213
|
+
throw new IssueError({
|
|
214
|
+
code: "E_INVALID_FORMAT",
|
|
215
|
+
category: "validation",
|
|
216
|
+
severity: "error",
|
|
217
|
+
retryable: false,
|
|
218
|
+
http_status: 400,
|
|
219
|
+
details: {
|
|
220
|
+
message: `Wire 0.2 claims schema validation failed: ${firstIssue?.message ?? "unknown"}`
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
const jws = await signWire02(claims, options.privateKey, options.kid);
|
|
225
|
+
return { jws };
|
|
226
|
+
}
|
|
181
227
|
function parseIssuerConfig(json) {
|
|
182
228
|
let config;
|
|
183
229
|
if (typeof json === "string") {
|
|
@@ -237,6 +283,17 @@ function parseIssuerConfig(json) {
|
|
|
237
283
|
throw new Error("payment_rails must be an array");
|
|
238
284
|
}
|
|
239
285
|
}
|
|
286
|
+
let revokedKeys;
|
|
287
|
+
if (obj.revoked_keys !== void 0) {
|
|
288
|
+
if (!Array.isArray(obj.revoked_keys)) {
|
|
289
|
+
throw new Error("revoked_keys must be an array");
|
|
290
|
+
}
|
|
291
|
+
const result = validateRevokedKeys(obj.revoked_keys);
|
|
292
|
+
if (!result.ok) {
|
|
293
|
+
throw new Error(`Invalid revoked_keys: ${result.error}`);
|
|
294
|
+
}
|
|
295
|
+
revokedKeys = result.value;
|
|
296
|
+
}
|
|
240
297
|
return {
|
|
241
298
|
version: obj.version,
|
|
242
299
|
issuer: obj.issuer,
|
|
@@ -245,7 +302,8 @@ function parseIssuerConfig(json) {
|
|
|
245
302
|
receipt_versions: obj.receipt_versions,
|
|
246
303
|
algorithms: obj.algorithms,
|
|
247
304
|
payment_rails: obj.payment_rails,
|
|
248
|
-
security_contact: obj.security_contact
|
|
305
|
+
security_contact: obj.security_contact,
|
|
306
|
+
revoked_keys: revokedKeys
|
|
249
307
|
};
|
|
250
308
|
}
|
|
251
309
|
async function fetchIssuerConfig(issuerUrl) {
|
|
@@ -932,6 +990,53 @@ async function fetchPointerSafe(pointerUrl, options) {
|
|
|
932
990
|
var DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
933
991
|
var DEFAULT_MAX_CACHE_ENTRIES = 1e3;
|
|
934
992
|
var jwksCache = /* @__PURE__ */ new Map();
|
|
993
|
+
var KID_RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
994
|
+
var MAX_KID_THUMBPRINT_ENTRIES = 1e4;
|
|
995
|
+
var kidThumbprints = /* @__PURE__ */ new Map();
|
|
996
|
+
function pruneExpiredKidEntries(now) {
|
|
997
|
+
for (const [key, entry] of kidThumbprints) {
|
|
998
|
+
if (now - entry.firstSeen >= KID_RETENTION_MS) {
|
|
999
|
+
kidThumbprints.delete(key);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
function evictOldestKidEntries() {
|
|
1004
|
+
if (kidThumbprints.size <= MAX_KID_THUMBPRINT_ENTRIES) return;
|
|
1005
|
+
const entries = Array.from(kidThumbprints.entries()).sort(
|
|
1006
|
+
(a, b) => a[1].firstSeen - b[1].firstSeen
|
|
1007
|
+
);
|
|
1008
|
+
const toRemove = kidThumbprints.size - MAX_KID_THUMBPRINT_ENTRIES;
|
|
1009
|
+
for (let i = 0; i < toRemove; i++) {
|
|
1010
|
+
kidThumbprints.delete(entries[i][0]);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
function checkKidReuse(issuer, jwks, now) {
|
|
1014
|
+
pruneExpiredKidEntries(now);
|
|
1015
|
+
for (const key of jwks.keys) {
|
|
1016
|
+
if (!key.kid || !key.x) continue;
|
|
1017
|
+
const mapKey = `${issuer}|${key.kid}`;
|
|
1018
|
+
const existing = kidThumbprints.get(mapKey);
|
|
1019
|
+
if (existing) {
|
|
1020
|
+
if (now - existing.firstSeen < KID_RETENTION_MS) {
|
|
1021
|
+
if (existing.thumbprint !== key.x) {
|
|
1022
|
+
return `Kid reuse detected: kid=${key.kid} for issuer ${issuer} maps to different key material`;
|
|
1023
|
+
}
|
|
1024
|
+
} else {
|
|
1025
|
+
kidThumbprints.set(mapKey, { thumbprint: key.x, firstSeen: now });
|
|
1026
|
+
}
|
|
1027
|
+
} else {
|
|
1028
|
+
kidThumbprints.set(mapKey, { thumbprint: key.x, firstSeen: now });
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
evictOldestKidEntries();
|
|
1032
|
+
return null;
|
|
1033
|
+
}
|
|
1034
|
+
function clearKidThumbprints() {
|
|
1035
|
+
kidThumbprints.clear();
|
|
1036
|
+
}
|
|
1037
|
+
function getKidThumbprintSize() {
|
|
1038
|
+
return kidThumbprints.size;
|
|
1039
|
+
}
|
|
935
1040
|
function cacheGet(key, now) {
|
|
936
1041
|
const entry = jwksCache.get(key);
|
|
937
1042
|
if (!entry) return void 0;
|
|
@@ -1020,7 +1125,7 @@ async function resolveJWKS(issuerUrl, options) {
|
|
|
1020
1125
|
if (!noCache) {
|
|
1021
1126
|
const cached = cacheGet(normalizedIssuer, now);
|
|
1022
1127
|
if (cached) {
|
|
1023
|
-
return { ok: true, jwks: cached.jwks, fromCache: true };
|
|
1128
|
+
return { ok: true, jwks: cached.jwks, fromCache: true, revokedKeys: cached.revokedKeys };
|
|
1024
1129
|
}
|
|
1025
1130
|
}
|
|
1026
1131
|
if (!normalizedIssuer.startsWith("https://")) {
|
|
@@ -1115,14 +1220,28 @@ async function resolveJWKS(issuerUrl, options) {
|
|
|
1115
1220
|
message: `JWKS has too many keys: ${jwks.keys.length} > ${VERIFIER_LIMITS.maxJwksKeys}`
|
|
1116
1221
|
};
|
|
1117
1222
|
}
|
|
1223
|
+
const kidReuseError = checkKidReuse(normalizedIssuer, jwks, now);
|
|
1224
|
+
if (kidReuseError) {
|
|
1225
|
+
return {
|
|
1226
|
+
ok: false,
|
|
1227
|
+
code: "E_KID_REUSE_DETECTED",
|
|
1228
|
+
message: kidReuseError
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
const revokedKeys = issuerConfig.revoked_keys?.map((entry) => ({
|
|
1232
|
+
kid: entry.kid,
|
|
1233
|
+
revoked_at: entry.revoked_at,
|
|
1234
|
+
reason: entry.reason
|
|
1235
|
+
}));
|
|
1118
1236
|
if (!noCache) {
|
|
1119
|
-
cacheSet(normalizedIssuer, { jwks, expiresAt: now + cacheTtlMs }, maxCacheEntries);
|
|
1237
|
+
cacheSet(normalizedIssuer, { jwks, expiresAt: now + cacheTtlMs, revokedKeys }, maxCacheEntries);
|
|
1120
1238
|
}
|
|
1121
1239
|
return {
|
|
1122
1240
|
ok: true,
|
|
1123
1241
|
jwks,
|
|
1124
1242
|
fromCache: false,
|
|
1125
|
-
rawBytes: jwksResult.rawBytes
|
|
1243
|
+
rawBytes: jwksResult.rawBytes,
|
|
1244
|
+
revokedKeys
|
|
1126
1245
|
};
|
|
1127
1246
|
}
|
|
1128
1247
|
|
|
@@ -1183,6 +1302,25 @@ async function verifyReceipt(optionsOrJws) {
|
|
|
1183
1302
|
if (!jwksResult.fromCache) {
|
|
1184
1303
|
jwksFetchTime = performance.now() - jwksFetchStart;
|
|
1185
1304
|
}
|
|
1305
|
+
if (jwksResult.revokedKeys) {
|
|
1306
|
+
const revokedEntry = jwksResult.revokedKeys.find((rk) => rk.kid === header.kid);
|
|
1307
|
+
if (revokedEntry) {
|
|
1308
|
+
const durationMs = performance.now() - startTime;
|
|
1309
|
+
fireTelemetryHook(telemetry?.onReceiptVerified, {
|
|
1310
|
+
receiptHash: hashReceipt(receiptJws),
|
|
1311
|
+
valid: false,
|
|
1312
|
+
reasonCode: "key_revoked",
|
|
1313
|
+
issuer: payload.iss,
|
|
1314
|
+
kid: header.kid,
|
|
1315
|
+
durationMs
|
|
1316
|
+
});
|
|
1317
|
+
return {
|
|
1318
|
+
ok: false,
|
|
1319
|
+
reason: "key_revoked",
|
|
1320
|
+
details: `Key kid=${header.kid} was revoked at ${revokedEntry.revoked_at}${revokedEntry.reason ? ` (reason: ${revokedEntry.reason})` : ""}`
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1186
1324
|
const jwk = jwksResult.jwks.keys.find((k) => k.kid === header.kid);
|
|
1187
1325
|
if (!jwk) {
|
|
1188
1326
|
const durationMs = performance.now() - startTime;
|
|
@@ -1260,6 +1398,13 @@ var FORMAT_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
|
1260
1398
|
"CRYPTO_INVALID_ALG",
|
|
1261
1399
|
"CRYPTO_INVALID_KEY_LENGTH"
|
|
1262
1400
|
]);
|
|
1401
|
+
var JOSE_CODE_MAP = {
|
|
1402
|
+
CRYPTO_JWS_EMBEDDED_KEY: "E_JWS_EMBEDDED_KEY",
|
|
1403
|
+
CRYPTO_JWS_CRIT_REJECTED: "E_JWS_CRIT_REJECTED",
|
|
1404
|
+
CRYPTO_JWS_MISSING_KID: "E_JWS_MISSING_KID",
|
|
1405
|
+
CRYPTO_JWS_B64_REJECTED: "E_JWS_B64_REJECTED",
|
|
1406
|
+
CRYPTO_JWS_ZIP_REJECTED: "E_JWS_ZIP_REJECTED"
|
|
1407
|
+
};
|
|
1263
1408
|
var MAX_PARSE_ISSUES = 25;
|
|
1264
1409
|
function sanitizeParseIssues(issues) {
|
|
1265
1410
|
if (!Array.isArray(issues)) return void 0;
|
|
@@ -1269,7 +1414,16 @@ function sanitizeParseIssues(issues) {
|
|
|
1269
1414
|
}));
|
|
1270
1415
|
}
|
|
1271
1416
|
async function verifyLocal(jws, publicKey, options = {}) {
|
|
1272
|
-
const {
|
|
1417
|
+
const {
|
|
1418
|
+
issuer,
|
|
1419
|
+
audience,
|
|
1420
|
+
subjectUri,
|
|
1421
|
+
rid,
|
|
1422
|
+
requireExp = false,
|
|
1423
|
+
maxClockSkew = 300,
|
|
1424
|
+
strictness = "strict",
|
|
1425
|
+
policyDigest
|
|
1426
|
+
} = options;
|
|
1273
1427
|
const now = options.now ?? Math.floor(Date.now() / 1e3);
|
|
1274
1428
|
try {
|
|
1275
1429
|
const result = await verify(jws, publicKey);
|
|
@@ -1280,6 +1434,20 @@ async function verifyLocal(jws, publicKey, options = {}) {
|
|
|
1280
1434
|
message: "Ed25519 signature verification failed"
|
|
1281
1435
|
};
|
|
1282
1436
|
}
|
|
1437
|
+
const accumulatedWarnings = [];
|
|
1438
|
+
if (result.header.typ === void 0) {
|
|
1439
|
+
if (strictness === "strict") {
|
|
1440
|
+
return {
|
|
1441
|
+
valid: false,
|
|
1442
|
+
code: "E_INVALID_FORMAT",
|
|
1443
|
+
message: "Missing JWS typ header: strict mode requires typ to be present"
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1446
|
+
accumulatedWarnings.push({
|
|
1447
|
+
code: WARNING_TYP_MISSING,
|
|
1448
|
+
message: "JWS typ header is absent; accepted in interop mode"
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1283
1451
|
const constraintResult = validateKernelConstraints(result.payload);
|
|
1284
1452
|
if (!constraintResult.valid) {
|
|
1285
1453
|
const v = constraintResult.violations[0];
|
|
@@ -1298,46 +1466,136 @@ async function verifyLocal(jws, publicKey, options = {}) {
|
|
|
1298
1466
|
details: { parse_code: pr.error.code, issues: sanitizeParseIssues(pr.error.issues) }
|
|
1299
1467
|
};
|
|
1300
1468
|
}
|
|
1301
|
-
if (
|
|
1469
|
+
if (pr.wireVersion === "0.2") {
|
|
1470
|
+
accumulatedWarnings.push(...pr.warnings);
|
|
1471
|
+
}
|
|
1472
|
+
if (pr.wireVersion === "0.2") {
|
|
1473
|
+
const claims = pr.claims;
|
|
1474
|
+
if (issuer !== void 0 && claims.iss !== issuer) {
|
|
1475
|
+
return {
|
|
1476
|
+
valid: false,
|
|
1477
|
+
code: "E_INVALID_ISSUER",
|
|
1478
|
+
message: `Issuer mismatch: expected "${issuer}", got "${claims.iss}"`
|
|
1479
|
+
};
|
|
1480
|
+
}
|
|
1481
|
+
if (subjectUri !== void 0 && claims.sub !== subjectUri) {
|
|
1482
|
+
return {
|
|
1483
|
+
valid: false,
|
|
1484
|
+
code: "E_INVALID_SUBJECT",
|
|
1485
|
+
message: `Subject mismatch: expected "${subjectUri}", got "${claims.sub ?? "undefined"}"`
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
if (claims.iat > now + maxClockSkew) {
|
|
1489
|
+
return {
|
|
1490
|
+
valid: false,
|
|
1491
|
+
code: "E_NOT_YET_VALID",
|
|
1492
|
+
message: `Receipt not yet valid: issued at ${new Date(claims.iat * 1e3).toISOString()}, now is ${new Date(now * 1e3).toISOString()}`
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
if (claims.kind === "evidence") {
|
|
1496
|
+
const skewResult = checkOccurredAtSkew(claims.occurred_at, claims.iat, now, maxClockSkew);
|
|
1497
|
+
if (skewResult === "future_error") {
|
|
1498
|
+
return {
|
|
1499
|
+
valid: false,
|
|
1500
|
+
code: "E_OCCURRED_AT_FUTURE",
|
|
1501
|
+
message: `occurred_at is in the future beyond tolerance (${maxClockSkew}s)`
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
if (skewResult !== null) {
|
|
1505
|
+
accumulatedWarnings.push(skewResult);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
if (!REGISTERED_RECEIPT_TYPES.has(claims.type)) {
|
|
1509
|
+
accumulatedWarnings.push({
|
|
1510
|
+
code: WARNING_TYPE_UNREGISTERED,
|
|
1511
|
+
message: "Receipt type is not in the recommended type registry",
|
|
1512
|
+
pointer: "/type"
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1515
|
+
if (claims.extensions !== void 0) {
|
|
1516
|
+
for (const key of Object.keys(claims.extensions)) {
|
|
1517
|
+
if (!REGISTERED_EXTENSION_GROUP_KEYS.has(key) && isValidExtensionKey(key)) {
|
|
1518
|
+
const escapedKey = key.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
1519
|
+
accumulatedWarnings.push({
|
|
1520
|
+
code: WARNING_UNKNOWN_EXTENSION,
|
|
1521
|
+
message: "Unknown extension key preserved without schema validation",
|
|
1522
|
+
pointer: `/extensions/${escapedKey}`
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
if (policyDigest !== void 0 && !HASH.pattern.test(policyDigest)) {
|
|
1528
|
+
return {
|
|
1529
|
+
valid: false,
|
|
1530
|
+
code: "E_INVALID_FORMAT",
|
|
1531
|
+
message: "policyDigest option must be in sha256:<64 lowercase hex> format"
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
const receiptPolicyDigest = claims.policy?.digest;
|
|
1535
|
+
const bindingStatus = receiptPolicyDigest === void 0 || policyDigest === void 0 ? "unavailable" : verifyPolicyBinding(receiptPolicyDigest, policyDigest);
|
|
1536
|
+
if (bindingStatus === "failed") {
|
|
1537
|
+
return {
|
|
1538
|
+
valid: false,
|
|
1539
|
+
code: "E_POLICY_BINDING_FAILED",
|
|
1540
|
+
message: "Policy binding check failed: receipt policy digest does not match local policy",
|
|
1541
|
+
details: {
|
|
1542
|
+
receipt_policy_digest: receiptPolicyDigest,
|
|
1543
|
+
local_policy_digest: policyDigest,
|
|
1544
|
+
...claims.policy?.uri !== void 0 && { policy_uri: claims.policy.uri }
|
|
1545
|
+
}
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1548
|
+
return {
|
|
1549
|
+
valid: true,
|
|
1550
|
+
variant: "wire-02",
|
|
1551
|
+
claims,
|
|
1552
|
+
kid: result.header.kid,
|
|
1553
|
+
wireVersion: "0.2",
|
|
1554
|
+
warnings: sortWarnings(accumulatedWarnings),
|
|
1555
|
+
policy_binding: bindingStatus
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
const w01 = pr.claims;
|
|
1559
|
+
if (issuer !== void 0 && w01.iss !== issuer) {
|
|
1302
1560
|
return {
|
|
1303
1561
|
valid: false,
|
|
1304
1562
|
code: "E_INVALID_ISSUER",
|
|
1305
|
-
message: `Issuer mismatch: expected "${issuer}", got "${
|
|
1563
|
+
message: `Issuer mismatch: expected "${issuer}", got "${w01.iss}"`
|
|
1306
1564
|
};
|
|
1307
1565
|
}
|
|
1308
|
-
if (audience !== void 0 &&
|
|
1566
|
+
if (audience !== void 0 && w01.aud !== audience) {
|
|
1309
1567
|
return {
|
|
1310
1568
|
valid: false,
|
|
1311
1569
|
code: "E_INVALID_AUDIENCE",
|
|
1312
|
-
message: `Audience mismatch: expected "${audience}", got "${
|
|
1570
|
+
message: `Audience mismatch: expected "${audience}", got "${w01.aud}"`
|
|
1313
1571
|
};
|
|
1314
1572
|
}
|
|
1315
|
-
if (rid !== void 0 &&
|
|
1573
|
+
if (rid !== void 0 && w01.rid !== rid) {
|
|
1316
1574
|
return {
|
|
1317
1575
|
valid: false,
|
|
1318
1576
|
code: "E_INVALID_RECEIPT_ID",
|
|
1319
|
-
message: `Receipt ID mismatch: expected "${rid}", got "${
|
|
1577
|
+
message: `Receipt ID mismatch: expected "${rid}", got "${w01.rid}"`
|
|
1320
1578
|
};
|
|
1321
1579
|
}
|
|
1322
|
-
if (requireExp &&
|
|
1580
|
+
if (requireExp && w01.exp === void 0) {
|
|
1323
1581
|
return {
|
|
1324
1582
|
valid: false,
|
|
1325
1583
|
code: "E_MISSING_EXP",
|
|
1326
1584
|
message: "Receipt missing required exp claim"
|
|
1327
1585
|
};
|
|
1328
1586
|
}
|
|
1329
|
-
if (
|
|
1587
|
+
if (w01.iat > now + maxClockSkew) {
|
|
1330
1588
|
return {
|
|
1331
1589
|
valid: false,
|
|
1332
1590
|
code: "E_NOT_YET_VALID",
|
|
1333
|
-
message: `Receipt not yet valid: issued at ${new Date(
|
|
1591
|
+
message: `Receipt not yet valid: issued at ${new Date(w01.iat * 1e3).toISOString()}, now is ${new Date(now * 1e3).toISOString()}`
|
|
1334
1592
|
};
|
|
1335
1593
|
}
|
|
1336
|
-
if (
|
|
1594
|
+
if (w01.exp !== void 0 && w01.exp < now - maxClockSkew) {
|
|
1337
1595
|
return {
|
|
1338
1596
|
valid: false,
|
|
1339
1597
|
code: "E_EXPIRED",
|
|
1340
|
-
message: `Receipt expired at ${new Date(
|
|
1598
|
+
message: `Receipt expired at ${new Date(w01.exp * 1e3).toISOString()}`
|
|
1341
1599
|
};
|
|
1342
1600
|
}
|
|
1343
1601
|
if (pr.variant === "commerce") {
|
|
@@ -1354,6 +1612,8 @@ async function verifyLocal(jws, publicKey, options = {}) {
|
|
|
1354
1612
|
variant: "commerce",
|
|
1355
1613
|
claims,
|
|
1356
1614
|
kid: result.header.kid,
|
|
1615
|
+
wireVersion: "0.1",
|
|
1616
|
+
warnings: [],
|
|
1357
1617
|
policy_binding: "unavailable"
|
|
1358
1618
|
};
|
|
1359
1619
|
} else {
|
|
@@ -1370,11 +1630,20 @@ async function verifyLocal(jws, publicKey, options = {}) {
|
|
|
1370
1630
|
variant: "attestation",
|
|
1371
1631
|
claims,
|
|
1372
1632
|
kid: result.header.kid,
|
|
1633
|
+
wireVersion: "0.1",
|
|
1634
|
+
warnings: [],
|
|
1373
1635
|
policy_binding: "unavailable"
|
|
1374
1636
|
};
|
|
1375
1637
|
}
|
|
1376
1638
|
} catch (err) {
|
|
1377
1639
|
if (isCryptoError(err)) {
|
|
1640
|
+
if (Object.prototype.hasOwnProperty.call(JOSE_CODE_MAP, err.code)) {
|
|
1641
|
+
return {
|
|
1642
|
+
valid: false,
|
|
1643
|
+
code: JOSE_CODE_MAP[err.code],
|
|
1644
|
+
message: err.message
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1378
1647
|
if (FORMAT_ERROR_CODES.has(err.code)) {
|
|
1379
1648
|
return {
|
|
1380
1649
|
valid: false,
|
|
@@ -1389,6 +1658,13 @@ async function verifyLocal(jws, publicKey, options = {}) {
|
|
|
1389
1658
|
message: err.message
|
|
1390
1659
|
};
|
|
1391
1660
|
}
|
|
1661
|
+
if (err.code === "CRYPTO_WIRE_VERSION_MISMATCH") {
|
|
1662
|
+
return {
|
|
1663
|
+
valid: false,
|
|
1664
|
+
code: "E_WIRE_VERSION_MISMATCH",
|
|
1665
|
+
message: err.message
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1392
1668
|
}
|
|
1393
1669
|
if (err !== null && typeof err === "object" && "name" in err && err.name === "SyntaxError") {
|
|
1394
1670
|
const syntaxMessage = "message" in err && typeof err.message === "string" ? err.message : "Invalid JSON";
|
|
@@ -1412,6 +1688,9 @@ function isCommerceResult(r) {
|
|
|
1412
1688
|
function isAttestationResult(r) {
|
|
1413
1689
|
return r.valid === true && r.variant === "attestation";
|
|
1414
1690
|
}
|
|
1691
|
+
function isWire02Result(r) {
|
|
1692
|
+
return r.valid === true && r.variant === "wire-02";
|
|
1693
|
+
}
|
|
1415
1694
|
function setReceiptHeader(headers, receiptJws) {
|
|
1416
1695
|
headers.set(PEAC_RECEIPT_HEADER, receiptJws);
|
|
1417
1696
|
}
|
|
@@ -1453,6 +1732,16 @@ function setVaryPurposeHeader(headers) {
|
|
|
1453
1732
|
headers.set("Vary", PEAC_PURPOSE_HEADER);
|
|
1454
1733
|
}
|
|
1455
1734
|
}
|
|
1735
|
+
async function computePolicyDigestJcs(policy) {
|
|
1736
|
+
const hex = await jcsHash(policy);
|
|
1737
|
+
return `${HASH.prefix}${hex}`;
|
|
1738
|
+
}
|
|
1739
|
+
function checkPolicyBinding(receiptDigest, localDigest) {
|
|
1740
|
+
if (receiptDigest === void 0 || localDigest === void 0) {
|
|
1741
|
+
return "unavailable";
|
|
1742
|
+
}
|
|
1743
|
+
return verifyPolicyBinding(receiptDigest, localDigest);
|
|
1744
|
+
}
|
|
1456
1745
|
var DEFAULT_VERIFIER_LIMITS = {
|
|
1457
1746
|
max_receipt_bytes: VERIFIER_LIMITS.maxReceiptBytes,
|
|
1458
1747
|
max_jwks_bytes: VERIFIER_LIMITS.maxJwksBytes,
|
|
@@ -2728,6 +3017,6 @@ async function verifyAndFetchPointer(pointerHeader, fetchOptions) {
|
|
|
2728
3017
|
});
|
|
2729
3018
|
}
|
|
2730
3019
|
|
|
2731
|
-
export { CHECK_IDS, DEFAULT_NETWORK_SECURITY, DEFAULT_VERIFIER_LIMITS, IssueError, NON_DETERMINISTIC_ARTIFACT_KEYS, VerificationReportBuilder, buildFailureReport, buildSuccessReport, clearJWKSCache, computeReceiptDigest, createDefaultPolicy, createDigest, createEmptyReport, createReportBuilder, fetchDiscovery, fetchIssuerConfig, fetchJWKSSafe, fetchPointerSafe, fetchPointerWithDigest, fetchPolicyManifest, getJWKSCacheSize, getPurposeHeader, getReceiptHeader, getSSRFCapabilities, isAttestationResult, isBlockedIP, isCommerceResult, issue, issueJws, parseBodyProfile, parseDiscovery, parseHeaderProfile, parseIssuerConfig, parsePointerProfile, parsePolicyManifest, parseTransportProfile, reasonCodeToErrorCode, reasonCodeToSeverity, resetSSRFCapabilitiesCache, resolveJWKS, setPurposeAppliedHeader, setPurposeReasonHeader, setReceiptHeader, setVaryHeader, setVaryPurposeHeader, ssrfErrorToReasonCode, ssrfSafeFetch, verifyAndFetchPointer, verifyLocal, verifyReceipt, verifyReceiptCore };
|
|
3020
|
+
export { CHECK_IDS, DEFAULT_NETWORK_SECURITY, DEFAULT_VERIFIER_LIMITS, IssueError, NON_DETERMINISTIC_ARTIFACT_KEYS, VerificationReportBuilder, buildFailureReport, buildSuccessReport, checkPolicyBinding, clearJWKSCache, clearKidThumbprints, computePolicyDigestJcs, computeReceiptDigest, createDefaultPolicy, createDigest, createEmptyReport, createReportBuilder, fetchDiscovery, fetchIssuerConfig, fetchJWKSSafe, fetchPointerSafe, fetchPointerWithDigest, fetchPolicyManifest, getJWKSCacheSize, getKidThumbprintSize, getPurposeHeader, getReceiptHeader, getSSRFCapabilities, isAttestationResult, isBlockedIP, isCommerceResult, isWire02Result, issue, issueJws, issueWire02, parseBodyProfile, parseDiscovery, parseHeaderProfile, parseIssuerConfig, parsePointerProfile, parsePolicyManifest, parseTransportProfile, reasonCodeToErrorCode, reasonCodeToSeverity, resetSSRFCapabilitiesCache, resolveJWKS, setPurposeAppliedHeader, setPurposeReasonHeader, setReceiptHeader, setVaryHeader, setVaryPurposeHeader, ssrfErrorToReasonCode, ssrfSafeFetch, verifyAndFetchPointer, verifyLocal, verifyReceipt, verifyReceiptCore };
|
|
2732
3021
|
//# sourceMappingURL=index.mjs.map
|
|
2733
3022
|
//# sourceMappingURL=index.mjs.map
|