@peac/protocol 0.11.3 → 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/dist/index.cjs +213 -12
- 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 +213 -16
- package/dist/index.mjs.map +1 -1
- package/dist/issue.d.ts +60 -1
- 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.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/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 https:// origin or did: 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,16 @@ function sanitizeParseIssues(issues) {
|
|
|
1362
1415
|
}));
|
|
1363
1416
|
}
|
|
1364
1417
|
async function verifyLocal(jws, publicKey, options = {}) {
|
|
1365
|
-
const {
|
|
1418
|
+
const {
|
|
1419
|
+
issuer,
|
|
1420
|
+
audience,
|
|
1421
|
+
subjectUri,
|
|
1422
|
+
rid,
|
|
1423
|
+
requireExp = false,
|
|
1424
|
+
maxClockSkew = 300,
|
|
1425
|
+
strictness = "strict",
|
|
1426
|
+
policyDigest
|
|
1427
|
+
} = options;
|
|
1366
1428
|
const now = options.now ?? Math.floor(Date.now() / 1e3);
|
|
1367
1429
|
try {
|
|
1368
1430
|
const result = await crypto.verify(jws, publicKey);
|
|
@@ -1373,6 +1435,20 @@ async function verifyLocal(jws, publicKey, options = {}) {
|
|
|
1373
1435
|
message: "Ed25519 signature verification failed"
|
|
1374
1436
|
};
|
|
1375
1437
|
}
|
|
1438
|
+
const accumulatedWarnings = [];
|
|
1439
|
+
if (result.header.typ === void 0) {
|
|
1440
|
+
if (strictness === "strict") {
|
|
1441
|
+
return {
|
|
1442
|
+
valid: false,
|
|
1443
|
+
code: "E_INVALID_FORMAT",
|
|
1444
|
+
message: "Missing JWS typ header: strict mode requires typ to be present"
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
accumulatedWarnings.push({
|
|
1448
|
+
code: schema.WARNING_TYP_MISSING,
|
|
1449
|
+
message: "JWS typ header is absent; accepted in interop mode"
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1376
1452
|
const constraintResult = schema.validateKernelConstraints(result.payload);
|
|
1377
1453
|
if (!constraintResult.valid) {
|
|
1378
1454
|
const v = constraintResult.violations[0];
|
|
@@ -1391,46 +1467,136 @@ async function verifyLocal(jws, publicKey, options = {}) {
|
|
|
1391
1467
|
details: { parse_code: pr.error.code, issues: sanitizeParseIssues(pr.error.issues) }
|
|
1392
1468
|
};
|
|
1393
1469
|
}
|
|
1394
|
-
if (
|
|
1470
|
+
if (pr.wireVersion === "0.2") {
|
|
1471
|
+
accumulatedWarnings.push(...pr.warnings);
|
|
1472
|
+
}
|
|
1473
|
+
if (pr.wireVersion === "0.2") {
|
|
1474
|
+
const claims = pr.claims;
|
|
1475
|
+
if (issuer !== void 0 && claims.iss !== issuer) {
|
|
1476
|
+
return {
|
|
1477
|
+
valid: false,
|
|
1478
|
+
code: "E_INVALID_ISSUER",
|
|
1479
|
+
message: `Issuer mismatch: expected "${issuer}", got "${claims.iss}"`
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
if (subjectUri !== void 0 && claims.sub !== subjectUri) {
|
|
1483
|
+
return {
|
|
1484
|
+
valid: false,
|
|
1485
|
+
code: "E_INVALID_SUBJECT",
|
|
1486
|
+
message: `Subject mismatch: expected "${subjectUri}", got "${claims.sub ?? "undefined"}"`
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
if (claims.iat > now + maxClockSkew) {
|
|
1490
|
+
return {
|
|
1491
|
+
valid: false,
|
|
1492
|
+
code: "E_NOT_YET_VALID",
|
|
1493
|
+
message: `Receipt not yet valid: issued at ${new Date(claims.iat * 1e3).toISOString()}, now is ${new Date(now * 1e3).toISOString()}`
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
if (claims.kind === "evidence") {
|
|
1497
|
+
const skewResult = schema.checkOccurredAtSkew(claims.occurred_at, claims.iat, now, maxClockSkew);
|
|
1498
|
+
if (skewResult === "future_error") {
|
|
1499
|
+
return {
|
|
1500
|
+
valid: false,
|
|
1501
|
+
code: "E_OCCURRED_AT_FUTURE",
|
|
1502
|
+
message: `occurred_at is in the future beyond tolerance (${maxClockSkew}s)`
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
if (skewResult !== null) {
|
|
1506
|
+
accumulatedWarnings.push(skewResult);
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
if (!schema.REGISTERED_RECEIPT_TYPES.has(claims.type)) {
|
|
1510
|
+
accumulatedWarnings.push({
|
|
1511
|
+
code: schema.WARNING_TYPE_UNREGISTERED,
|
|
1512
|
+
message: "Receipt type is not in the recommended type registry",
|
|
1513
|
+
pointer: "/type"
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
if (claims.extensions !== void 0) {
|
|
1517
|
+
for (const key of Object.keys(claims.extensions)) {
|
|
1518
|
+
if (!schema.REGISTERED_EXTENSION_GROUP_KEYS.has(key) && schema.isValidExtensionKey(key)) {
|
|
1519
|
+
const escapedKey = key.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
1520
|
+
accumulatedWarnings.push({
|
|
1521
|
+
code: schema.WARNING_UNKNOWN_EXTENSION,
|
|
1522
|
+
message: "Unknown extension key preserved without schema validation",
|
|
1523
|
+
pointer: `/extensions/${escapedKey}`
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
if (policyDigest !== void 0 && !kernel.HASH.pattern.test(policyDigest)) {
|
|
1529
|
+
return {
|
|
1530
|
+
valid: false,
|
|
1531
|
+
code: "E_INVALID_FORMAT",
|
|
1532
|
+
message: "policyDigest option must be in sha256:<64 lowercase hex> format"
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
const receiptPolicyDigest = claims.policy?.digest;
|
|
1536
|
+
const bindingStatus = receiptPolicyDigest === void 0 || policyDigest === void 0 ? "unavailable" : schema.verifyPolicyBinding(receiptPolicyDigest, policyDigest);
|
|
1537
|
+
if (bindingStatus === "failed") {
|
|
1538
|
+
return {
|
|
1539
|
+
valid: false,
|
|
1540
|
+
code: "E_POLICY_BINDING_FAILED",
|
|
1541
|
+
message: "Policy binding check failed: receipt policy digest does not match local policy",
|
|
1542
|
+
details: {
|
|
1543
|
+
receipt_policy_digest: receiptPolicyDigest,
|
|
1544
|
+
local_policy_digest: policyDigest,
|
|
1545
|
+
...claims.policy?.uri !== void 0 && { policy_uri: claims.policy.uri }
|
|
1546
|
+
}
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
return {
|
|
1550
|
+
valid: true,
|
|
1551
|
+
variant: "wire-02",
|
|
1552
|
+
claims,
|
|
1553
|
+
kid: result.header.kid,
|
|
1554
|
+
wireVersion: "0.2",
|
|
1555
|
+
warnings: schema.sortWarnings(accumulatedWarnings),
|
|
1556
|
+
policy_binding: bindingStatus
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
const w01 = pr.claims;
|
|
1560
|
+
if (issuer !== void 0 && w01.iss !== issuer) {
|
|
1395
1561
|
return {
|
|
1396
1562
|
valid: false,
|
|
1397
1563
|
code: "E_INVALID_ISSUER",
|
|
1398
|
-
message: `Issuer mismatch: expected "${issuer}", got "${
|
|
1564
|
+
message: `Issuer mismatch: expected "${issuer}", got "${w01.iss}"`
|
|
1399
1565
|
};
|
|
1400
1566
|
}
|
|
1401
|
-
if (audience !== void 0 &&
|
|
1567
|
+
if (audience !== void 0 && w01.aud !== audience) {
|
|
1402
1568
|
return {
|
|
1403
1569
|
valid: false,
|
|
1404
1570
|
code: "E_INVALID_AUDIENCE",
|
|
1405
|
-
message: `Audience mismatch: expected "${audience}", got "${
|
|
1571
|
+
message: `Audience mismatch: expected "${audience}", got "${w01.aud}"`
|
|
1406
1572
|
};
|
|
1407
1573
|
}
|
|
1408
|
-
if (rid !== void 0 &&
|
|
1574
|
+
if (rid !== void 0 && w01.rid !== rid) {
|
|
1409
1575
|
return {
|
|
1410
1576
|
valid: false,
|
|
1411
1577
|
code: "E_INVALID_RECEIPT_ID",
|
|
1412
|
-
message: `Receipt ID mismatch: expected "${rid}", got "${
|
|
1578
|
+
message: `Receipt ID mismatch: expected "${rid}", got "${w01.rid}"`
|
|
1413
1579
|
};
|
|
1414
1580
|
}
|
|
1415
|
-
if (requireExp &&
|
|
1581
|
+
if (requireExp && w01.exp === void 0) {
|
|
1416
1582
|
return {
|
|
1417
1583
|
valid: false,
|
|
1418
1584
|
code: "E_MISSING_EXP",
|
|
1419
1585
|
message: "Receipt missing required exp claim"
|
|
1420
1586
|
};
|
|
1421
1587
|
}
|
|
1422
|
-
if (
|
|
1588
|
+
if (w01.iat > now + maxClockSkew) {
|
|
1423
1589
|
return {
|
|
1424
1590
|
valid: false,
|
|
1425
1591
|
code: "E_NOT_YET_VALID",
|
|
1426
|
-
message: `Receipt not yet valid: issued at ${new Date(
|
|
1592
|
+
message: `Receipt not yet valid: issued at ${new Date(w01.iat * 1e3).toISOString()}, now is ${new Date(now * 1e3).toISOString()}`
|
|
1427
1593
|
};
|
|
1428
1594
|
}
|
|
1429
|
-
if (
|
|
1595
|
+
if (w01.exp !== void 0 && w01.exp < now - maxClockSkew) {
|
|
1430
1596
|
return {
|
|
1431
1597
|
valid: false,
|
|
1432
1598
|
code: "E_EXPIRED",
|
|
1433
|
-
message: `Receipt expired at ${new Date(
|
|
1599
|
+
message: `Receipt expired at ${new Date(w01.exp * 1e3).toISOString()}`
|
|
1434
1600
|
};
|
|
1435
1601
|
}
|
|
1436
1602
|
if (pr.variant === "commerce") {
|
|
@@ -1447,6 +1613,8 @@ async function verifyLocal(jws, publicKey, options = {}) {
|
|
|
1447
1613
|
variant: "commerce",
|
|
1448
1614
|
claims,
|
|
1449
1615
|
kid: result.header.kid,
|
|
1616
|
+
wireVersion: "0.1",
|
|
1617
|
+
warnings: [],
|
|
1450
1618
|
policy_binding: "unavailable"
|
|
1451
1619
|
};
|
|
1452
1620
|
} else {
|
|
@@ -1463,11 +1631,20 @@ async function verifyLocal(jws, publicKey, options = {}) {
|
|
|
1463
1631
|
variant: "attestation",
|
|
1464
1632
|
claims,
|
|
1465
1633
|
kid: result.header.kid,
|
|
1634
|
+
wireVersion: "0.1",
|
|
1635
|
+
warnings: [],
|
|
1466
1636
|
policy_binding: "unavailable"
|
|
1467
1637
|
};
|
|
1468
1638
|
}
|
|
1469
1639
|
} catch (err) {
|
|
1470
1640
|
if (isCryptoError(err)) {
|
|
1641
|
+
if (Object.prototype.hasOwnProperty.call(JOSE_CODE_MAP, err.code)) {
|
|
1642
|
+
return {
|
|
1643
|
+
valid: false,
|
|
1644
|
+
code: JOSE_CODE_MAP[err.code],
|
|
1645
|
+
message: err.message
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1471
1648
|
if (FORMAT_ERROR_CODES.has(err.code)) {
|
|
1472
1649
|
return {
|
|
1473
1650
|
valid: false,
|
|
@@ -1482,6 +1659,13 @@ async function verifyLocal(jws, publicKey, options = {}) {
|
|
|
1482
1659
|
message: err.message
|
|
1483
1660
|
};
|
|
1484
1661
|
}
|
|
1662
|
+
if (err.code === "CRYPTO_WIRE_VERSION_MISMATCH") {
|
|
1663
|
+
return {
|
|
1664
|
+
valid: false,
|
|
1665
|
+
code: "E_WIRE_VERSION_MISMATCH",
|
|
1666
|
+
message: err.message
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1485
1669
|
}
|
|
1486
1670
|
if (err !== null && typeof err === "object" && "name" in err && err.name === "SyntaxError") {
|
|
1487
1671
|
const syntaxMessage = "message" in err && typeof err.message === "string" ? err.message : "Invalid JSON";
|
|
@@ -1505,6 +1689,9 @@ function isCommerceResult(r) {
|
|
|
1505
1689
|
function isAttestationResult(r) {
|
|
1506
1690
|
return r.valid === true && r.variant === "attestation";
|
|
1507
1691
|
}
|
|
1692
|
+
function isWire02Result(r) {
|
|
1693
|
+
return r.valid === true && r.variant === "wire-02";
|
|
1694
|
+
}
|
|
1508
1695
|
function setReceiptHeader(headers, receiptJws) {
|
|
1509
1696
|
headers.set(schema.PEAC_RECEIPT_HEADER, receiptJws);
|
|
1510
1697
|
}
|
|
@@ -1546,6 +1733,16 @@ function setVaryPurposeHeader(headers) {
|
|
|
1546
1733
|
headers.set("Vary", schema.PEAC_PURPOSE_HEADER);
|
|
1547
1734
|
}
|
|
1548
1735
|
}
|
|
1736
|
+
async function computePolicyDigestJcs(policy) {
|
|
1737
|
+
const hex = await crypto.jcsHash(policy);
|
|
1738
|
+
return `${kernel.HASH.prefix}${hex}`;
|
|
1739
|
+
}
|
|
1740
|
+
function checkPolicyBinding(receiptDigest, localDigest) {
|
|
1741
|
+
if (receiptDigest === void 0 || localDigest === void 0) {
|
|
1742
|
+
return "unavailable";
|
|
1743
|
+
}
|
|
1744
|
+
return schema.verifyPolicyBinding(receiptDigest, localDigest);
|
|
1745
|
+
}
|
|
1549
1746
|
var DEFAULT_VERIFIER_LIMITS = {
|
|
1550
1747
|
max_receipt_bytes: kernel.VERIFIER_LIMITS.maxReceiptBytes,
|
|
1551
1748
|
max_jwks_bytes: kernel.VERIFIER_LIMITS.maxJwksBytes,
|
|
@@ -2861,8 +3058,10 @@ exports.NON_DETERMINISTIC_ARTIFACT_KEYS = NON_DETERMINISTIC_ARTIFACT_KEYS;
|
|
|
2861
3058
|
exports.VerificationReportBuilder = VerificationReportBuilder;
|
|
2862
3059
|
exports.buildFailureReport = buildFailureReport;
|
|
2863
3060
|
exports.buildSuccessReport = buildSuccessReport;
|
|
3061
|
+
exports.checkPolicyBinding = checkPolicyBinding;
|
|
2864
3062
|
exports.clearJWKSCache = clearJWKSCache;
|
|
2865
3063
|
exports.clearKidThumbprints = clearKidThumbprints;
|
|
3064
|
+
exports.computePolicyDigestJcs = computePolicyDigestJcs;
|
|
2866
3065
|
exports.computeReceiptDigest = computeReceiptDigest;
|
|
2867
3066
|
exports.createDefaultPolicy = createDefaultPolicy;
|
|
2868
3067
|
exports.createDigest = createDigest;
|
|
@@ -2882,8 +3081,10 @@ exports.getSSRFCapabilities = getSSRFCapabilities;
|
|
|
2882
3081
|
exports.isAttestationResult = isAttestationResult;
|
|
2883
3082
|
exports.isBlockedIP = isBlockedIP;
|
|
2884
3083
|
exports.isCommerceResult = isCommerceResult;
|
|
3084
|
+
exports.isWire02Result = isWire02Result;
|
|
2885
3085
|
exports.issue = issue;
|
|
2886
3086
|
exports.issueJws = issueJws;
|
|
3087
|
+
exports.issueWire02 = issueWire02;
|
|
2887
3088
|
exports.parseBodyProfile = parseBodyProfile;
|
|
2888
3089
|
exports.parseDiscovery = parseDiscovery;
|
|
2889
3090
|
exports.parseHeaderProfile = parseHeaderProfile;
|