@peac/protocol 0.11.3 → 0.12.0-preview.2
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/dist/index.cjs +176 -59
- 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 +176 -63
- package/dist/index.mjs.map +1 -1
- package/dist/issue.d.ts +64 -2
- package/dist/issue.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-wire01.d.ts +45 -0
- package/dist/verify-local-wire01.d.ts.map +1 -0
- package/dist/verify-local.cjs +118 -59
- package/dist/verify-local.cjs.map +1 -1
- package/dist/verify-local.d.ts +94 -69
- package/dist/verify-local.d.ts.map +1 -1
- package/dist/verify-local.mjs +119 -61
- package/dist/verify-local.mjs.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -179,6 +179,52 @@ async function issueJws(options) {
|
|
|
179
179
|
const result = await issue(options);
|
|
180
180
|
return result.jws;
|
|
181
181
|
}
|
|
182
|
+
async function issueWire02(options) {
|
|
183
|
+
if (!schema.isCanonicalIss(options.iss)) {
|
|
184
|
+
throw new IssueError({
|
|
185
|
+
code: "E_ISS_NOT_CANONICAL",
|
|
186
|
+
category: "validation",
|
|
187
|
+
severity: "error",
|
|
188
|
+
retryable: false,
|
|
189
|
+
http_status: 400,
|
|
190
|
+
details: {
|
|
191
|
+
message: `iss is not in canonical form: "${options.iss}". Use an https://<origin> or did:<method> identifier.`
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
const jti = options.jti ?? uuidv7.uuidv7();
|
|
196
|
+
const iat = Math.floor(Date.now() / 1e3);
|
|
197
|
+
const claims = {
|
|
198
|
+
peac_version: "0.2",
|
|
199
|
+
kind: options.kind,
|
|
200
|
+
type: options.type,
|
|
201
|
+
iss: options.iss,
|
|
202
|
+
iat,
|
|
203
|
+
jti,
|
|
204
|
+
...options.sub !== void 0 && { sub: options.sub },
|
|
205
|
+
...options.pillars !== void 0 && { pillars: options.pillars },
|
|
206
|
+
...options.occurred_at !== void 0 && { occurred_at: options.occurred_at },
|
|
207
|
+
...options.purpose_declared !== void 0 && { purpose_declared: options.purpose_declared },
|
|
208
|
+
...options.policy !== void 0 && { policy: options.policy },
|
|
209
|
+
...options.extensions !== void 0 && { extensions: options.extensions }
|
|
210
|
+
};
|
|
211
|
+
const parseResult = schema.Wire02ClaimsSchema.safeParse(claims);
|
|
212
|
+
if (!parseResult.success) {
|
|
213
|
+
const firstIssue = parseResult.error.issues[0];
|
|
214
|
+
throw new IssueError({
|
|
215
|
+
code: "E_INVALID_FORMAT",
|
|
216
|
+
category: "validation",
|
|
217
|
+
severity: "error",
|
|
218
|
+
retryable: false,
|
|
219
|
+
http_status: 400,
|
|
220
|
+
details: {
|
|
221
|
+
message: `Wire 0.2 claims schema validation failed: ${firstIssue?.message ?? "unknown"}`
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
const jws = await crypto.signWire02(claims, options.privateKey, options.kid);
|
|
226
|
+
return { jws };
|
|
227
|
+
}
|
|
182
228
|
function parseIssuerConfig(json) {
|
|
183
229
|
let config;
|
|
184
230
|
if (typeof json === "string") {
|
|
@@ -1353,6 +1399,13 @@ var FORMAT_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
|
1353
1399
|
"CRYPTO_INVALID_ALG",
|
|
1354
1400
|
"CRYPTO_INVALID_KEY_LENGTH"
|
|
1355
1401
|
]);
|
|
1402
|
+
var JOSE_CODE_MAP = {
|
|
1403
|
+
CRYPTO_JWS_EMBEDDED_KEY: "E_JWS_EMBEDDED_KEY",
|
|
1404
|
+
CRYPTO_JWS_CRIT_REJECTED: "E_JWS_CRIT_REJECTED",
|
|
1405
|
+
CRYPTO_JWS_MISSING_KID: "E_JWS_MISSING_KID",
|
|
1406
|
+
CRYPTO_JWS_B64_REJECTED: "E_JWS_B64_REJECTED",
|
|
1407
|
+
CRYPTO_JWS_ZIP_REJECTED: "E_JWS_ZIP_REJECTED"
|
|
1408
|
+
};
|
|
1356
1409
|
var MAX_PARSE_ISSUES = 25;
|
|
1357
1410
|
function sanitizeParseIssues(issues) {
|
|
1358
1411
|
if (!Array.isArray(issues)) return void 0;
|
|
@@ -1362,7 +1415,7 @@ function sanitizeParseIssues(issues) {
|
|
|
1362
1415
|
}));
|
|
1363
1416
|
}
|
|
1364
1417
|
async function verifyLocal(jws, publicKey, options = {}) {
|
|
1365
|
-
const { issuer,
|
|
1418
|
+
const { issuer, subjectUri, maxClockSkew = 300, strictness = "strict", policyDigest } = options;
|
|
1366
1419
|
const now = options.now ?? Math.floor(Date.now() / 1e3);
|
|
1367
1420
|
try {
|
|
1368
1421
|
const result = await crypto.verify(jws, publicKey);
|
|
@@ -1373,6 +1426,20 @@ async function verifyLocal(jws, publicKey, options = {}) {
|
|
|
1373
1426
|
message: "Ed25519 signature verification failed"
|
|
1374
1427
|
};
|
|
1375
1428
|
}
|
|
1429
|
+
const accumulatedWarnings = [];
|
|
1430
|
+
if (result.header.typ === void 0) {
|
|
1431
|
+
if (strictness === "strict") {
|
|
1432
|
+
return {
|
|
1433
|
+
valid: false,
|
|
1434
|
+
code: "E_INVALID_FORMAT",
|
|
1435
|
+
message: "Missing JWS typ header: strict mode requires typ to be present"
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
accumulatedWarnings.push({
|
|
1439
|
+
code: schema.WARNING_TYP_MISSING,
|
|
1440
|
+
message: "JWS typ header is absent; accepted in interop mode"
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1376
1443
|
const constraintResult = schema.validateKernelConstraints(result.payload);
|
|
1377
1444
|
if (!constraintResult.valid) {
|
|
1378
1445
|
const v = constraintResult.violations[0];
|
|
@@ -1391,66 +1458,18 @@ async function verifyLocal(jws, publicKey, options = {}) {
|
|
|
1391
1458
|
details: { parse_code: pr.error.code, issues: sanitizeParseIssues(pr.error.issues) }
|
|
1392
1459
|
};
|
|
1393
1460
|
}
|
|
1394
|
-
if (
|
|
1395
|
-
|
|
1396
|
-
valid: false,
|
|
1397
|
-
code: "E_INVALID_ISSUER",
|
|
1398
|
-
message: `Issuer mismatch: expected "${issuer}", got "${pr.claims.iss}"`
|
|
1399
|
-
};
|
|
1461
|
+
if (pr.wireVersion === "0.2") {
|
|
1462
|
+
accumulatedWarnings.push(...pr.warnings);
|
|
1400
1463
|
}
|
|
1401
|
-
if (
|
|
1402
|
-
return {
|
|
1403
|
-
valid: false,
|
|
1404
|
-
code: "E_INVALID_AUDIENCE",
|
|
1405
|
-
message: `Audience mismatch: expected "${audience}", got "${pr.claims.aud}"`
|
|
1406
|
-
};
|
|
1407
|
-
}
|
|
1408
|
-
if (rid !== void 0 && pr.claims.rid !== rid) {
|
|
1409
|
-
return {
|
|
1410
|
-
valid: false,
|
|
1411
|
-
code: "E_INVALID_RECEIPT_ID",
|
|
1412
|
-
message: `Receipt ID mismatch: expected "${rid}", got "${pr.claims.rid}"`
|
|
1413
|
-
};
|
|
1414
|
-
}
|
|
1415
|
-
if (requireExp && pr.claims.exp === void 0) {
|
|
1416
|
-
return {
|
|
1417
|
-
valid: false,
|
|
1418
|
-
code: "E_MISSING_EXP",
|
|
1419
|
-
message: "Receipt missing required exp claim"
|
|
1420
|
-
};
|
|
1421
|
-
}
|
|
1422
|
-
if (pr.claims.iat > now + maxClockSkew) {
|
|
1423
|
-
return {
|
|
1424
|
-
valid: false,
|
|
1425
|
-
code: "E_NOT_YET_VALID",
|
|
1426
|
-
message: `Receipt not yet valid: issued at ${new Date(pr.claims.iat * 1e3).toISOString()}, now is ${new Date(now * 1e3).toISOString()}`
|
|
1427
|
-
};
|
|
1428
|
-
}
|
|
1429
|
-
if (pr.claims.exp !== void 0 && pr.claims.exp < now - maxClockSkew) {
|
|
1430
|
-
return {
|
|
1431
|
-
valid: false,
|
|
1432
|
-
code: "E_EXPIRED",
|
|
1433
|
-
message: `Receipt expired at ${new Date(pr.claims.exp * 1e3).toISOString()}`
|
|
1434
|
-
};
|
|
1435
|
-
}
|
|
1436
|
-
if (pr.variant === "commerce") {
|
|
1464
|
+
if (pr.wireVersion === "0.2") {
|
|
1437
1465
|
const claims = pr.claims;
|
|
1438
|
-
if (
|
|
1466
|
+
if (issuer !== void 0 && claims.iss !== issuer) {
|
|
1439
1467
|
return {
|
|
1440
1468
|
valid: false,
|
|
1441
|
-
code: "
|
|
1442
|
-
message: `
|
|
1469
|
+
code: "E_INVALID_ISSUER",
|
|
1470
|
+
message: `Issuer mismatch: expected "${issuer}", got "${claims.iss}"`
|
|
1443
1471
|
};
|
|
1444
1472
|
}
|
|
1445
|
-
return {
|
|
1446
|
-
valid: true,
|
|
1447
|
-
variant: "commerce",
|
|
1448
|
-
claims,
|
|
1449
|
-
kid: result.header.kid,
|
|
1450
|
-
policy_binding: "unavailable"
|
|
1451
|
-
};
|
|
1452
|
-
} else {
|
|
1453
|
-
const claims = pr.claims;
|
|
1454
1473
|
if (subjectUri !== void 0 && claims.sub !== subjectUri) {
|
|
1455
1474
|
return {
|
|
1456
1475
|
valid: false,
|
|
@@ -1458,16 +1477,90 @@ async function verifyLocal(jws, publicKey, options = {}) {
|
|
|
1458
1477
|
message: `Subject mismatch: expected "${subjectUri}", got "${claims.sub ?? "undefined"}"`
|
|
1459
1478
|
};
|
|
1460
1479
|
}
|
|
1480
|
+
if (claims.iat > now + maxClockSkew) {
|
|
1481
|
+
return {
|
|
1482
|
+
valid: false,
|
|
1483
|
+
code: "E_NOT_YET_VALID",
|
|
1484
|
+
message: `Receipt not yet valid: issued at ${new Date(claims.iat * 1e3).toISOString()}, now is ${new Date(now * 1e3).toISOString()}`
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
if (claims.kind === "evidence") {
|
|
1488
|
+
const skewResult = schema.checkOccurredAtSkew(claims.occurred_at, claims.iat, now, maxClockSkew);
|
|
1489
|
+
if (skewResult === "future_error") {
|
|
1490
|
+
return {
|
|
1491
|
+
valid: false,
|
|
1492
|
+
code: "E_OCCURRED_AT_FUTURE",
|
|
1493
|
+
message: `occurred_at is in the future beyond tolerance (${maxClockSkew}s)`
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
if (skewResult !== null) {
|
|
1497
|
+
accumulatedWarnings.push(skewResult);
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
if (!schema.REGISTERED_RECEIPT_TYPES.has(claims.type)) {
|
|
1501
|
+
accumulatedWarnings.push({
|
|
1502
|
+
code: schema.WARNING_TYPE_UNREGISTERED,
|
|
1503
|
+
message: "Receipt type is not in the recommended type registry",
|
|
1504
|
+
pointer: "/type"
|
|
1505
|
+
});
|
|
1506
|
+
}
|
|
1507
|
+
if (claims.extensions !== void 0) {
|
|
1508
|
+
for (const key of Object.keys(claims.extensions)) {
|
|
1509
|
+
if (!schema.REGISTERED_EXTENSION_GROUP_KEYS.has(key) && schema.isValidExtensionKey(key)) {
|
|
1510
|
+
const escapedKey = key.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
1511
|
+
accumulatedWarnings.push({
|
|
1512
|
+
code: schema.WARNING_UNKNOWN_EXTENSION,
|
|
1513
|
+
message: "Unknown extension key preserved without schema validation",
|
|
1514
|
+
pointer: `/extensions/${escapedKey}`
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
if (policyDigest !== void 0 && !kernel.HASH.pattern.test(policyDigest)) {
|
|
1520
|
+
return {
|
|
1521
|
+
valid: false,
|
|
1522
|
+
code: "E_INVALID_FORMAT",
|
|
1523
|
+
message: "policyDigest option must be in sha256:<64 lowercase hex> format"
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
const receiptPolicyDigest = claims.policy?.digest;
|
|
1527
|
+
const bindingStatus = receiptPolicyDigest === void 0 || policyDigest === void 0 ? "unavailable" : schema.verifyPolicyBinding(receiptPolicyDigest, policyDigest);
|
|
1528
|
+
if (bindingStatus === "failed") {
|
|
1529
|
+
return {
|
|
1530
|
+
valid: false,
|
|
1531
|
+
code: "E_POLICY_BINDING_FAILED",
|
|
1532
|
+
message: "Policy binding check failed: receipt policy digest does not match local policy",
|
|
1533
|
+
details: {
|
|
1534
|
+
receipt_policy_digest: receiptPolicyDigest,
|
|
1535
|
+
local_policy_digest: policyDigest,
|
|
1536
|
+
...claims.policy?.uri !== void 0 && { policy_uri: claims.policy.uri }
|
|
1537
|
+
}
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1461
1540
|
return {
|
|
1462
1541
|
valid: true,
|
|
1463
|
-
variant: "
|
|
1542
|
+
variant: "wire-02",
|
|
1464
1543
|
claims,
|
|
1465
1544
|
kid: result.header.kid,
|
|
1466
|
-
|
|
1545
|
+
wireVersion: "0.2",
|
|
1546
|
+
warnings: schema.sortWarnings(accumulatedWarnings),
|
|
1547
|
+
policy_binding: bindingStatus
|
|
1467
1548
|
};
|
|
1468
1549
|
}
|
|
1550
|
+
return {
|
|
1551
|
+
valid: false,
|
|
1552
|
+
code: "E_UNSUPPORTED_WIRE_VERSION",
|
|
1553
|
+
message: "Wire 0.1 receipts are not supported. Re-issue as Wire 0.2 using issueWire02()."
|
|
1554
|
+
};
|
|
1469
1555
|
} catch (err) {
|
|
1470
1556
|
if (isCryptoError(err)) {
|
|
1557
|
+
if (Object.prototype.hasOwnProperty.call(JOSE_CODE_MAP, err.code)) {
|
|
1558
|
+
return {
|
|
1559
|
+
valid: false,
|
|
1560
|
+
code: JOSE_CODE_MAP[err.code],
|
|
1561
|
+
message: err.message
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1471
1564
|
if (FORMAT_ERROR_CODES.has(err.code)) {
|
|
1472
1565
|
return {
|
|
1473
1566
|
valid: false,
|
|
@@ -1482,6 +1575,13 @@ async function verifyLocal(jws, publicKey, options = {}) {
|
|
|
1482
1575
|
message: err.message
|
|
1483
1576
|
};
|
|
1484
1577
|
}
|
|
1578
|
+
if (err.code === "CRYPTO_WIRE_VERSION_MISMATCH") {
|
|
1579
|
+
return {
|
|
1580
|
+
valid: false,
|
|
1581
|
+
code: "E_WIRE_VERSION_MISMATCH",
|
|
1582
|
+
message: err.message
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1485
1585
|
}
|
|
1486
1586
|
if (err !== null && typeof err === "object" && "name" in err && err.name === "SyntaxError") {
|
|
1487
1587
|
const syntaxMessage = "message" in err && typeof err.message === "string" ? err.message : "Invalid JSON";
|
|
@@ -1500,10 +1600,13 @@ async function verifyLocal(jws, publicKey, options = {}) {
|
|
|
1500
1600
|
}
|
|
1501
1601
|
}
|
|
1502
1602
|
function isCommerceResult(r) {
|
|
1503
|
-
return
|
|
1603
|
+
return false;
|
|
1504
1604
|
}
|
|
1505
1605
|
function isAttestationResult(r) {
|
|
1506
|
-
return
|
|
1606
|
+
return false;
|
|
1607
|
+
}
|
|
1608
|
+
function isWire02Result(r) {
|
|
1609
|
+
return r.valid === true && r.variant === "wire-02";
|
|
1507
1610
|
}
|
|
1508
1611
|
function setReceiptHeader(headers, receiptJws) {
|
|
1509
1612
|
headers.set(schema.PEAC_RECEIPT_HEADER, receiptJws);
|
|
@@ -1546,6 +1649,16 @@ function setVaryPurposeHeader(headers) {
|
|
|
1546
1649
|
headers.set("Vary", schema.PEAC_PURPOSE_HEADER);
|
|
1547
1650
|
}
|
|
1548
1651
|
}
|
|
1652
|
+
async function computePolicyDigestJcs(policy) {
|
|
1653
|
+
const hex = await crypto.jcsHash(policy);
|
|
1654
|
+
return `${kernel.HASH.prefix}${hex}`;
|
|
1655
|
+
}
|
|
1656
|
+
function checkPolicyBinding(receiptDigest, localDigest) {
|
|
1657
|
+
if (receiptDigest === void 0 || localDigest === void 0) {
|
|
1658
|
+
return "unavailable";
|
|
1659
|
+
}
|
|
1660
|
+
return schema.verifyPolicyBinding(receiptDigest, localDigest);
|
|
1661
|
+
}
|
|
1549
1662
|
var DEFAULT_VERIFIER_LIMITS = {
|
|
1550
1663
|
max_receipt_bytes: kernel.VERIFIER_LIMITS.maxReceiptBytes,
|
|
1551
1664
|
max_jwks_bytes: kernel.VERIFIER_LIMITS.maxJwksBytes,
|
|
@@ -2861,8 +2974,10 @@ exports.NON_DETERMINISTIC_ARTIFACT_KEYS = NON_DETERMINISTIC_ARTIFACT_KEYS;
|
|
|
2861
2974
|
exports.VerificationReportBuilder = VerificationReportBuilder;
|
|
2862
2975
|
exports.buildFailureReport = buildFailureReport;
|
|
2863
2976
|
exports.buildSuccessReport = buildSuccessReport;
|
|
2977
|
+
exports.checkPolicyBinding = checkPolicyBinding;
|
|
2864
2978
|
exports.clearJWKSCache = clearJWKSCache;
|
|
2865
2979
|
exports.clearKidThumbprints = clearKidThumbprints;
|
|
2980
|
+
exports.computePolicyDigestJcs = computePolicyDigestJcs;
|
|
2866
2981
|
exports.computeReceiptDigest = computeReceiptDigest;
|
|
2867
2982
|
exports.createDefaultPolicy = createDefaultPolicy;
|
|
2868
2983
|
exports.createDigest = createDigest;
|
|
@@ -2882,8 +2997,10 @@ exports.getSSRFCapabilities = getSSRFCapabilities;
|
|
|
2882
2997
|
exports.isAttestationResult = isAttestationResult;
|
|
2883
2998
|
exports.isBlockedIP = isBlockedIP;
|
|
2884
2999
|
exports.isCommerceResult = isCommerceResult;
|
|
3000
|
+
exports.isWire02Result = isWire02Result;
|
|
2885
3001
|
exports.issue = issue;
|
|
2886
3002
|
exports.issueJws = issueJws;
|
|
3003
|
+
exports.issueWire02 = issueWire02;
|
|
2887
3004
|
exports.parseBodyProfile = parseBodyProfile;
|
|
2888
3005
|
exports.parseDiscovery = parseDiscovery;
|
|
2889
3006
|
exports.parseHeaderProfile = parseHeaderProfile;
|