@blamejs/blamejs-shop 0.0.129 → 0.1.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/CHANGELOG.md +6 -0
- package/README.md +1 -1
- package/lib/admin.js +275 -9
- package/lib/affiliates.js +4 -3
- package/lib/analytics.js +3 -2
- package/lib/api-keys.js +1 -1
- package/lib/assembly-instructions.js +2 -1
- package/lib/auto-replenish.js +4 -3
- package/lib/backorder.js +2 -1
- package/lib/business-hours.js +8 -1
- package/lib/carrier-accounts.js +1 -1
- package/lib/carrier-rates.js +1 -1
- package/lib/cart-abandonment.js +3 -2
- package/lib/cart-bulk-ops.js +2 -1
- package/lib/cart-recovery.js +5 -4
- package/lib/cart.js +6 -2
- package/lib/catalog-drafts.js +1 -1
- package/lib/click-and-collect.js +3 -2
- package/lib/clickstream.js +4 -3
- package/lib/config.js +2 -1
- package/lib/cookie-consent.js +2 -1
- package/lib/credit-limits.js +2 -1
- package/lib/currency-display.js +2 -1
- package/lib/customer-activity.js +3 -2
- package/lib/customer-impersonation.js +3 -3
- package/lib/customer-merge.js +4 -3
- package/lib/customer-portal.js +4 -4
- package/lib/customer-risk-profile.js +2 -1
- package/lib/customer-segments.js +2 -1
- package/lib/customer-surveys.js +6 -3
- package/lib/delivery-estimate.js +2 -2
- package/lib/demand-forecast.js +2 -1
- package/lib/discount-analytics.js +2 -2
- package/lib/dunning.js +4 -1
- package/lib/email-warmup.js +6 -1
- package/lib/email.js +1 -8
- package/lib/error-log.js +3 -2
- package/lib/event-log.js +3 -2
- package/lib/fraud-screen.js +3 -1
- package/lib/fulfillment-sla.js +3 -1
- package/lib/index.js +11 -3
- package/lib/inventory-allocations.js +3 -0
- package/lib/inventory-snapshots.js +2 -1
- package/lib/invoice-renderer.js +2 -1
- package/lib/line-gift-wrap.js +6 -1
- package/lib/live-chat.js +2 -1
- package/lib/loyalty-redemption.js +2 -1
- package/lib/newsletter.js +6 -1
- package/lib/operator-activity-feed.js +4 -3
- package/lib/operator-sessions.js +7 -7
- package/lib/order-exchanges.js +1 -0
- package/lib/order-timeline.js +2 -1
- package/lib/payment-retries.js +2 -1
- package/lib/payment.js +5 -4
- package/lib/pixel-events.js +6 -5
- package/lib/preorder.js +2 -1
- package/lib/print-queue.js +2 -1
- package/lib/product-compare.js +2 -1
- package/lib/product-qa.js +2 -1
- package/lib/push-notifications.js +6 -5
- package/lib/recently-viewed.js +7 -2
- package/lib/recommendations.js +7 -2
- package/lib/referral-leaderboard.js +2 -1
- package/lib/refund-automation.js +1 -1
- package/lib/refund-policy.js +1 -1
- package/lib/reorder-reminders.js +2 -1
- package/lib/reorder-thresholds.js +2 -1
- package/lib/robots-config.js +1 -0
- package/lib/sales-reports.js +17 -14
- package/lib/sales-tax-filings.js +2 -1
- package/lib/save-for-later.js +2 -1
- package/lib/search-suggestions.js +1 -1
- package/lib/shipping-insurance.js +2 -1
- package/lib/shipping-labels.js +3 -2
- package/lib/shipping-zones.js +1 -0
- package/lib/shrinkage-report.js +9 -8
- package/lib/sms-dispatcher.js +6 -5
- package/lib/stock-alerts.js +1 -1
- package/lib/stock-receipts.js +2 -1
- package/lib/store-credit.js +2 -1
- package/lib/storefront-forms.js +1 -1
- package/lib/storefront.js +93 -112
- package/lib/subscription-analytics.js +7 -2
- package/lib/subscription-controls.js +9 -8
- package/lib/subscription-gifts.js +2 -1
- package/lib/subscriptions.js +2 -0
- package/lib/support-tickets.js +4 -4
- package/lib/tax-cert-renewals.js +2 -1
- package/lib/tax-remittance.js +2 -1
- package/lib/theme-assets.js +1 -1
- package/lib/vendor/MANIFEST.json +2 -2
- package/lib/vendor/blamejs/CHANGELOG.md +16 -0
- package/lib/vendor/blamejs/README.md +6 -4
- package/lib/vendor/blamejs/SECURITY.md +2 -0
- package/lib/vendor/blamejs/api-snapshot.json +255 -2
- package/lib/vendor/blamejs/index.js +1 -0
- package/lib/vendor/blamejs/lib/cose.js +284 -10
- package/lib/vendor/blamejs/lib/crypto.js +119 -0
- package/lib/vendor/blamejs/lib/did.js +416 -0
- package/lib/vendor/blamejs/lib/mdoc.js +122 -0
- package/lib/vendor/blamejs/lib/network-dnssec.js +328 -0
- package/lib/vendor/blamejs/lib/network.js +1 -0
- package/lib/vendor/blamejs/lib/vc.js +231 -33
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.12.41.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.42.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.43.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.44.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.45.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.46.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.47.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.48.json +22 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +47 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/cose.test.js +101 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-self-test.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/did.test.js +176 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dnssec.test.js +130 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mdoc.test.js +52 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vc.test.js +63 -0
- package/lib/vendor-invoices.js +1 -1
- package/lib/webhook-receiver.js +8 -2
- package/lib/webhook-subscriptions.js +1 -1
- package/lib/webhooks.js +6 -5
- package/lib/winback-campaigns.js +2 -1
- package/lib/wishlist-alerts.js +2 -1
- package/lib/wishlist-digest.js +2 -1
- package/package.json +1 -1
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.did
|
|
4
|
+
* @nav Crypto
|
|
5
|
+
* @title Decentralized Identifiers (DID)
|
|
6
|
+
*
|
|
7
|
+
* @intro
|
|
8
|
+
* Resolve W3C Decentralized Identifiers (DID Core 1.0, a W3C
|
|
9
|
+
* Recommendation) to verification keys — the missing link that lets a
|
|
10
|
+
* credential's issuer be named by a DID rather than a raw key. Resolve
|
|
11
|
+
* the issuer DID of a <code>b.vc</code> / <code>b.mdoc</code> /
|
|
12
|
+
* <code>b.scitt</code> credential to a <code>node:crypto</code>
|
|
13
|
+
* KeyObject, then hand that key to the verifier.
|
|
14
|
+
*
|
|
15
|
+
* Three methods are supported. <strong>did:key</strong> encodes a
|
|
16
|
+
* public key directly in the identifier (multicodec + base58btc
|
|
17
|
+
* multibase) and <strong>did:jwk</strong> encodes it as a base64url
|
|
18
|
+
* public JWK — both resolve deterministically and offline (Ed25519,
|
|
19
|
+
* P-256, P-384, and secp256k1 round-trip). <strong>did:web</strong>
|
|
20
|
+
* places the DID document at an HTTPS URL derived from the identifier;
|
|
21
|
+
* the network fetch is the operator's to make (the same
|
|
22
|
+
* operator-supplied-input stance as the rest of the framework), and
|
|
23
|
+
* <code>resolve</code> takes the fetched document and extracts its
|
|
24
|
+
* verification methods.
|
|
25
|
+
*
|
|
26
|
+
* <code>b.did.keyToDid(publicKey)</code> produces a did:key from a
|
|
27
|
+
* KeyObject (an issuer naming itself); <code>b.did.parse(did)</code>
|
|
28
|
+
* splits the identifier (and, for did:web, returns the HTTPS URL to
|
|
29
|
+
* fetch); <code>b.did.resolve(did, opts)</code> returns the DID
|
|
30
|
+
* document and its verification methods as KeyObjects. Verification
|
|
31
|
+
* methods expressed as <code>publicKeyMultibase</code> or
|
|
32
|
+
* <code>publicKeyJwk</code> are both understood.
|
|
33
|
+
*
|
|
34
|
+
* <strong>Maturity.</strong> DID Core 1.0 is a Recommendation, but the
|
|
35
|
+
* method specs are deployed-stable rather than Recommendations:
|
|
36
|
+
* did:key is a W3C CCG report and did:web is a registered DID method
|
|
37
|
+
* (mandated by the EU Digital Identity Wallet). They are widely
|
|
38
|
+
* deployed and interoperable today; pin the dependency deliberately.
|
|
39
|
+
*
|
|
40
|
+
* @card
|
|
41
|
+
* W3C DID resolution (did:key + did:jwk + did:web) → verification
|
|
42
|
+
* KeyObjects for the credential verifiers. did:key + did:jwk are
|
|
43
|
+
* deterministic + offline (Ed25519 / P-256 / P-384 / secp256k1);
|
|
44
|
+
* did:web parses an operator-fetched DID document. Composes
|
|
45
|
+
* node:crypto; no new dep.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
var nodeCrypto = require("node:crypto");
|
|
49
|
+
var safeJson = require("./safe-json");
|
|
50
|
+
var validateOpts = require("./validate-opts");
|
|
51
|
+
var { defineClass } = require("./framework-error");
|
|
52
|
+
|
|
53
|
+
var DidError = defineClass("DidError", { alwaysPermanent: true });
|
|
54
|
+
|
|
55
|
+
var B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
56
|
+
var B58_MAP = (function () {
|
|
57
|
+
var m = {};
|
|
58
|
+
for (var i = 0; i < B58_ALPHABET.length; i += 1) m[B58_ALPHABET[i]] = i;
|
|
59
|
+
return m;
|
|
60
|
+
})();
|
|
61
|
+
var MAX_MULTIBASE_CHARS = 1024; // allow:raw-byte-literal — bounded did:key multibase length (DoS cap)
|
|
62
|
+
var MAX_JWK_B64_CHARS = 8192; // allow:raw-byte-literal — bounded did:jwk encoded-JWK length (DoS cap)
|
|
63
|
+
|
|
64
|
+
// multicodec public-key codes (unsigned-varint) → curve descriptor.
|
|
65
|
+
// keyLen is the multicodec payload: Ed25519 raw 32; EC compressed point.
|
|
66
|
+
var MULTICODEC = {
|
|
67
|
+
0xed: { name: "Ed25519", kind: "okp" }, // ed25519-pub
|
|
68
|
+
0x1200: { name: "P-256", kind: "ec", curveOid: "1.2.840.10045.3.1.7" }, // allow:raw-byte-literal allow:raw-time-literal — p256-pub multicodec code + OID dotted-form
|
|
69
|
+
0x1201: { name: "P-384", kind: "ec", curveOid: "1.3.132.0.34" }, // allow:raw-byte-literal — p384-pub multicodec code
|
|
70
|
+
0xe7: { name: "secp256k1", kind: "ec", curveOid: "1.3.132.0.10" }, // secp256k1-pub
|
|
71
|
+
};
|
|
72
|
+
var NAME_TO_CODE = {};
|
|
73
|
+
Object.keys(MULTICODEC).forEach(function (c) { NAME_TO_CODE[MULTICODEC[c].name] = Number(c); });
|
|
74
|
+
|
|
75
|
+
// ---- base58btc (bounded) ----
|
|
76
|
+
|
|
77
|
+
function _b58decode(str) {
|
|
78
|
+
if (str.length > MAX_MULTIBASE_CHARS) {
|
|
79
|
+
throw new DidError("did/too-long", "did: multibase value exceeds the " + MAX_MULTIBASE_CHARS + "-char cap");
|
|
80
|
+
}
|
|
81
|
+
var bytes = [0];
|
|
82
|
+
for (var i = 0; i < str.length; i += 1) {
|
|
83
|
+
var v = B58_MAP[str[i]];
|
|
84
|
+
if (v === undefined) throw new DidError("did/bad-base58", "did: invalid base58btc character '" + str[i] + "'");
|
|
85
|
+
var carry = v;
|
|
86
|
+
for (var j = 0; j < bytes.length; j += 1) {
|
|
87
|
+
carry += bytes[j] * 58;
|
|
88
|
+
bytes[j] = carry & 0xff;
|
|
89
|
+
carry >>= 8; // allow:raw-byte-literal — base-256 carry
|
|
90
|
+
}
|
|
91
|
+
while (carry > 0) { bytes.push(carry & 0xff); carry >>= 8; } // allow:raw-byte-literal — base-256 carry
|
|
92
|
+
}
|
|
93
|
+
// Leading '1's are leading zero bytes.
|
|
94
|
+
for (var k = 0; k < str.length && str[k] === "1"; k += 1) bytes.push(0);
|
|
95
|
+
return Buffer.from(bytes.reverse());
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function _b58encode(buf) {
|
|
99
|
+
var digits = [0];
|
|
100
|
+
for (var i = 0; i < buf.length; i += 1) {
|
|
101
|
+
var carry = buf[i];
|
|
102
|
+
for (var j = 0; j < digits.length; j += 1) {
|
|
103
|
+
carry += digits[j] << 8; // allow:raw-byte-literal — base-256 shift
|
|
104
|
+
digits[j] = carry % 58;
|
|
105
|
+
carry = (carry / 58) | 0;
|
|
106
|
+
}
|
|
107
|
+
while (carry > 0) { digits.push(carry % 58); carry = (carry / 58) | 0; }
|
|
108
|
+
}
|
|
109
|
+
var out = "";
|
|
110
|
+
for (var z = 0; z < buf.length && buf[z] === 0; z += 1) out += "1";
|
|
111
|
+
for (var d = digits.length - 1; d >= 0; d -= 1) out += B58_ALPHABET[digits[d]];
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Read an unsigned LEB128 varint (multicodec code). Bounded to 4 bytes.
|
|
116
|
+
function _readVarint(buf) {
|
|
117
|
+
var value = 0, shift = 0, len = 0;
|
|
118
|
+
for (var i = 0; i < buf.length && i < 4; i += 1) { // allow:raw-byte-literal — multicodec varint ≤ 4 bytes
|
|
119
|
+
var b = buf[i];
|
|
120
|
+
value |= (b & 0x7f) << shift;
|
|
121
|
+
len += 1;
|
|
122
|
+
if ((b & 0x80) === 0) return { value: value >>> 0, length: len };
|
|
123
|
+
shift += 7; // allow:raw-byte-literal — 7 bits per varint byte
|
|
124
|
+
}
|
|
125
|
+
throw new DidError("did/bad-multicodec", "did: multicodec varint did not terminate");
|
|
126
|
+
}
|
|
127
|
+
function _encodeVarint(code) {
|
|
128
|
+
var out = [];
|
|
129
|
+
var n = code;
|
|
130
|
+
do { var b = n & 0x7f; n >>>= 7; if (n > 0) b |= 0x80; out.push(b); } while (n > 0); // allow:raw-byte-literal — LEB128 7-bit groups
|
|
131
|
+
return Buffer.from(out);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ---- key <-> bytes ----
|
|
135
|
+
|
|
136
|
+
var ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex"); // RFC 8410 Ed25519 SubjectPublicKeyInfo header
|
|
137
|
+
|
|
138
|
+
function _keyObjectFromMulticodec(code, keyBytes) {
|
|
139
|
+
var desc = MULTICODEC[code];
|
|
140
|
+
if (!desc) throw new DidError("did/unsupported-key", "did: unsupported multicodec key code 0x" + code.toString(16)); // allow:raw-byte-literal — hex radix
|
|
141
|
+
if (desc.kind === "okp") {
|
|
142
|
+
if (keyBytes.length !== 32) { // allow:raw-byte-literal — Ed25519 public key is 32 bytes
|
|
143
|
+
throw new DidError("did/bad-key", "did: Ed25519 key must be 32 bytes (got " + keyBytes.length + ")");
|
|
144
|
+
}
|
|
145
|
+
return nodeCrypto.createPublicKey({ key: Buffer.concat([ED25519_SPKI_PREFIX, keyBytes]), format: "der", type: "spki" });
|
|
146
|
+
}
|
|
147
|
+
// EC: keyBytes is a compressed point (0x02/0x03 + X). Build an SPKI and
|
|
148
|
+
// let node decompress.
|
|
149
|
+
if (keyBytes.length < 2 || (keyBytes[0] !== 0x02 && keyBytes[0] !== 0x03)) {
|
|
150
|
+
throw new DidError("did/bad-key", "did: EC key must be a compressed point (0x02/0x03 prefix)");
|
|
151
|
+
}
|
|
152
|
+
var algid = _ecAlgId(desc.curveOid);
|
|
153
|
+
var bitstr = Buffer.concat([Buffer.from([0x03, keyBytes.length + 1, 0x00]), keyBytes]);
|
|
154
|
+
var body = Buffer.concat([algid, bitstr]);
|
|
155
|
+
var spki = Buffer.concat([Buffer.from([0x30, body.length]), body]); // allow:raw-byte-literal — SEQUENCE tag; single-byte DER length holds for these curves
|
|
156
|
+
try { return nodeCrypto.createPublicKey({ key: spki, format: "der", type: "spki" }); }
|
|
157
|
+
catch (e) { throw new DidError("did/bad-key", "did: could not import EC key: " + ((e && e.message) || e)); }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// AlgorithmIdentifier SEQUENCE { id-ecPublicKey, namedCurve OID }.
|
|
161
|
+
function _ecAlgId(curveOid) {
|
|
162
|
+
var idEcPublicKey = Buffer.from("06072a8648ce3d0201", "hex"); // allow:raw-byte-literal allow:raw-time-literal — DER OID for id-ecPublicKey
|
|
163
|
+
var curve = _oidDer(curveOid);
|
|
164
|
+
var inner = Buffer.concat([idEcPublicKey, curve]);
|
|
165
|
+
return Buffer.concat([Buffer.from([0x30, inner.length]), inner]);
|
|
166
|
+
}
|
|
167
|
+
function _oidDer(dotted) {
|
|
168
|
+
var parts = dotted.split(".").map(Number);
|
|
169
|
+
var bytes = [parts[0] * 40 + parts[1]]; // allow:raw-byte-literal — X.690 first-arc encoding
|
|
170
|
+
for (var i = 2; i < parts.length; i += 1) {
|
|
171
|
+
var arc = parts[i], stack = [];
|
|
172
|
+
do { stack.unshift(arc & 0x7f); arc >>>= 7; } while (arc > 0); // allow:raw-byte-literal — base-128 OID arc
|
|
173
|
+
for (var j = 0; j < stack.length - 1; j += 1) stack[j] |= 0x80; // allow:raw-byte-literal — continuation bit
|
|
174
|
+
bytes = bytes.concat(stack);
|
|
175
|
+
}
|
|
176
|
+
return Buffer.concat([Buffer.from([0x06, bytes.length]), Buffer.from(bytes)]);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Compressed point + curve name from an EC KeyObject's JWK.
|
|
180
|
+
function _compressedPoint(jwk) {
|
|
181
|
+
var x = Buffer.from(jwk.x, "base64url");
|
|
182
|
+
var y = Buffer.from(jwk.y, "base64url");
|
|
183
|
+
return Buffer.concat([Buffer.from([(y[y.length - 1] & 1) ? 0x03 : 0x02]), x]);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* @primitive b.did.parse
|
|
188
|
+
* @signature b.did.parse(did)
|
|
189
|
+
* @since 0.12.41
|
|
190
|
+
* @status experimental
|
|
191
|
+
* @related b.did.resolve, b.did.keyToDid
|
|
192
|
+
*
|
|
193
|
+
* Split a DID string into its method and method-specific id. For
|
|
194
|
+
* <code>did:web</code> the HTTPS URL of the DID document is also
|
|
195
|
+
* returned (host[:port][:path] → <code>https://host/path/did.json</code>,
|
|
196
|
+
* or <code>/.well-known/did.json</code> with no path).
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* b.did.parse("did:web:example.com:issuers:42");
|
|
200
|
+
* // → { method: "web", id: "example.com:issuers:42", url: "https://example.com/issuers/42/did.json" }
|
|
201
|
+
*/
|
|
202
|
+
function parse(did) {
|
|
203
|
+
if (typeof did !== "string" || did.indexOf("did:") !== 0) {
|
|
204
|
+
throw new DidError("did/bad-did", "did.parse: not a DID (must start with 'did:')");
|
|
205
|
+
}
|
|
206
|
+
var rest = did.slice(4);
|
|
207
|
+
var colon = rest.indexOf(":");
|
|
208
|
+
if (colon <= 0) throw new DidError("did/bad-did", "did.parse: DID is missing a method-specific id");
|
|
209
|
+
var method = rest.slice(0, colon);
|
|
210
|
+
var id = rest.slice(colon + 1);
|
|
211
|
+
var out = { method: method, id: id };
|
|
212
|
+
if (method === "web") out.url = _didWebUrl(id);
|
|
213
|
+
return out;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function _didWebUrl(id) {
|
|
217
|
+
// did-method-web §read: ':' separates segments (→ '/'); only the host
|
|
218
|
+
// may carry a percent-encoded port (%3A → ':'). Path segments are kept
|
|
219
|
+
// verbatim — NOT percent-decoded — so an escaped reserved char (e.g.
|
|
220
|
+
// %3F) stays path data rather than becoming URL control syntax, and a
|
|
221
|
+
// malformed escape never throws a raw URIError.
|
|
222
|
+
var segs = id.split(":");
|
|
223
|
+
var host = segs[0].replace(/%3[Aa]/g, ":");
|
|
224
|
+
if (!host) throw new DidError("did/bad-did", "did:web: missing host");
|
|
225
|
+
var path = segs.slice(1);
|
|
226
|
+
var base = "https://" + host;
|
|
227
|
+
return path.length ? base + "/" + path.join("/") + "/did.json" : base + "/.well-known/did.json";
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* @primitive b.did.keyToDid
|
|
232
|
+
* @signature b.did.keyToDid(publicKey, opts?)
|
|
233
|
+
* @since 0.12.41
|
|
234
|
+
* @status experimental
|
|
235
|
+
* @related b.did.resolve
|
|
236
|
+
*
|
|
237
|
+
* Encode a public key (a <code>node:crypto</code> KeyObject or PEM) as a
|
|
238
|
+
* DID — the inverse of resolution, for an issuer that names itself by
|
|
239
|
+
* its key. Defaults to <code>did:key</code> (multicodec + base58btc);
|
|
240
|
+
* pass <code>opts.method = "jwk"</code> for <code>did:jwk</code>
|
|
241
|
+
* (base64url-encoded public JWK). Ed25519, P-256, P-384, and secp256k1
|
|
242
|
+
* are supported.
|
|
243
|
+
*
|
|
244
|
+
* @opts
|
|
245
|
+
* {
|
|
246
|
+
* method: string, // "key" (default) | "jwk"
|
|
247
|
+
* }
|
|
248
|
+
*
|
|
249
|
+
* @example
|
|
250
|
+
* var did = b.did.keyToDid(issuerPublicKey); // → "did:key:z6Mk…"
|
|
251
|
+
* var dj = b.did.keyToDid(issuerPublicKey, { method: "jwk" }); // → "did:jwk:eyJr…"
|
|
252
|
+
*/
|
|
253
|
+
function keyToDid(publicKey, opts) {
|
|
254
|
+
var key = (publicKey && typeof publicKey === "object" && publicKey.asymmetricKeyType)
|
|
255
|
+
? publicKey : nodeCrypto.createPublicKey(publicKey);
|
|
256
|
+
var jwk = key.export({ format: "jwk" });
|
|
257
|
+
if (opts && opts.method === "jwk") {
|
|
258
|
+
// did:jwk — base64url(UTF-8(JSON of the PUBLIC JWK)). Strip any
|
|
259
|
+
// private member defensively (a public KeyObject has none, but a
|
|
260
|
+
// caller could pass a private key by mistake).
|
|
261
|
+
var pub = {};
|
|
262
|
+
Object.keys(jwk).forEach(function (k) { if (k !== "d") pub[k] = jwk[k]; });
|
|
263
|
+
// Gate on the same kty/crv allowlist resolution enforces, so a
|
|
264
|
+
// produced did:jwk always round-trips (no generate-succeeds /
|
|
265
|
+
// resolve-fails RSA-style identifiers).
|
|
266
|
+
_jwkToKey(pub);
|
|
267
|
+
return "did:jwk:" + Buffer.from(JSON.stringify(pub), "utf8").toString("base64url");
|
|
268
|
+
}
|
|
269
|
+
var code, payload;
|
|
270
|
+
if (jwk.kty === "OKP" && jwk.crv === "Ed25519") {
|
|
271
|
+
code = NAME_TO_CODE["Ed25519"];
|
|
272
|
+
payload = Buffer.from(jwk.x, "base64url");
|
|
273
|
+
} else if (jwk.kty === "EC") {
|
|
274
|
+
var name = jwk.crv === "P-256" ? "P-256" : jwk.crv === "P-384" ? "P-384" : jwk.crv === "secp256k1" ? "secp256k1" : null;
|
|
275
|
+
if (!name) throw new DidError("did/unsupported-key", "did.keyToDid: unsupported EC curve '" + jwk.crv + "'");
|
|
276
|
+
code = NAME_TO_CODE[name];
|
|
277
|
+
payload = _compressedPoint(jwk);
|
|
278
|
+
} else {
|
|
279
|
+
throw new DidError("did/unsupported-key", "did.keyToDid: unsupported key type '" + jwk.kty + "/" + jwk.crv + "'");
|
|
280
|
+
}
|
|
281
|
+
return "did:key:z" + _b58encode(Buffer.concat([_encodeVarint(code), payload]));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* @primitive b.did.resolve
|
|
286
|
+
* @signature b.did.resolve(did, opts?)
|
|
287
|
+
* @since 0.12.41
|
|
288
|
+
* @status experimental
|
|
289
|
+
* @compliance soc2
|
|
290
|
+
* @related b.did.parse, b.vc.verify, b.mdoc.verifyIssuerSigned
|
|
291
|
+
*
|
|
292
|
+
* Resolve a DID to its document and verification methods (each with a
|
|
293
|
+
* <code>node:crypto</code> public KeyObject ready for a verifier).
|
|
294
|
+
* <code>did:key</code> and <code>did:jwk</code> resolve deterministically
|
|
295
|
+
* and offline. <code>did:web</code> requires the operator to supply the fetched DID
|
|
296
|
+
* document as <code>opts.document</code> (the network fetch is the
|
|
297
|
+
* operator's; the URL to fetch is on <code>b.did.parse(did).url</code>).
|
|
298
|
+
*
|
|
299
|
+
* @opts
|
|
300
|
+
* {
|
|
301
|
+
* document: object, // did:web — the fetched did.json (required for did:web)
|
|
302
|
+
* }
|
|
303
|
+
*
|
|
304
|
+
* @example
|
|
305
|
+
* var r = b.did.resolve("did:key:z6Mk…");
|
|
306
|
+
* var key = r.verificationMethods[0].publicKey; // → KeyObject for b.vc.verify / b.mdoc / b.scitt
|
|
307
|
+
*/
|
|
308
|
+
function resolve(did, opts) {
|
|
309
|
+
opts = opts || {};
|
|
310
|
+
validateOpts.requireObject(opts, "did.resolve", DidError);
|
|
311
|
+
validateOpts(opts, ["document"], "did.resolve");
|
|
312
|
+
var parsed = parse(did);
|
|
313
|
+
|
|
314
|
+
if (parsed.method === "key") {
|
|
315
|
+
if (parsed.id[0] !== "z") {
|
|
316
|
+
throw new DidError("did/bad-did", "did:key: method-specific id must be base58btc multibase (start with 'z')");
|
|
317
|
+
}
|
|
318
|
+
var raw = _b58decode(parsed.id.slice(1));
|
|
319
|
+
var vh = _readVarint(raw);
|
|
320
|
+
var key = _keyObjectFromMulticodec(vh.value, raw.slice(vh.length));
|
|
321
|
+
var vmId = did + "#" + parsed.id;
|
|
322
|
+
var vm = { id: vmId, controller: did, type: MULTICODEC[vh.value].name, publicKey: key };
|
|
323
|
+
var doc = {
|
|
324
|
+
"@context": ["https://www.w3.org/ns/did/v1"],
|
|
325
|
+
id: did,
|
|
326
|
+
verificationMethod: [{ id: vmId, controller: did, type: "Multikey", publicKeyMultibase: parsed.id }],
|
|
327
|
+
assertionMethod: [vmId], authentication: [vmId],
|
|
328
|
+
};
|
|
329
|
+
return { didDocument: doc, verificationMethods: [vm] };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (parsed.method === "jwk") {
|
|
333
|
+
if (parsed.id.length > MAX_JWK_B64_CHARS) {
|
|
334
|
+
throw new DidError("did/too-long", "did:jwk: encoded JWK exceeds the " + MAX_JWK_B64_CHARS + "-char cap");
|
|
335
|
+
}
|
|
336
|
+
var jwkJson = Buffer.from(parsed.id, "base64url").toString("utf8");
|
|
337
|
+
var jwk;
|
|
338
|
+
try { jwk = safeJson.parse(jwkJson, { maxBytes: MAX_JWK_B64_CHARS }); } catch (_e) {
|
|
339
|
+
throw new DidError("did/bad-jwk", "did:jwk: method-specific id is not base64url-encoded JSON");
|
|
340
|
+
}
|
|
341
|
+
if (!jwk || typeof jwk !== "object" || Array.isArray(jwk)) {
|
|
342
|
+
throw new DidError("did/bad-jwk", "did:jwk: decoded value is not a JWK object");
|
|
343
|
+
}
|
|
344
|
+
var jwkKey = _jwkToKey(jwk); // kty/crv allowlisted
|
|
345
|
+
var jwkVmId = did + "#0";
|
|
346
|
+
var jwkVm = { id: jwkVmId, controller: did, type: "JsonWebKey2020", publicKey: jwkKey };
|
|
347
|
+
var jwkDoc = {
|
|
348
|
+
"@context": ["https://www.w3.org/ns/did/v1"],
|
|
349
|
+
id: did,
|
|
350
|
+
verificationMethod: [{ id: jwkVmId, controller: did, type: "JsonWebKey2020", publicKeyJwk: jwk }],
|
|
351
|
+
assertionMethod: [jwkVmId], authentication: [jwkVmId],
|
|
352
|
+
};
|
|
353
|
+
return { didDocument: jwkDoc, verificationMethods: [jwkVm] };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (parsed.method === "web") {
|
|
357
|
+
if (!opts.document || typeof opts.document !== "object") {
|
|
358
|
+
throw new DidError("did/document-required",
|
|
359
|
+
"did:web: the DID document must be fetched by the operator and passed as opts.document (GET " + parsed.url + ")");
|
|
360
|
+
}
|
|
361
|
+
var docW = opts.document;
|
|
362
|
+
if (docW.id !== did) {
|
|
363
|
+
throw new DidError("did/document-mismatch", "did:web: document id '" + docW.id + "' does not match the requested DID");
|
|
364
|
+
}
|
|
365
|
+
return { didDocument: docW, verificationMethods: _extractVerificationMethods(docW) };
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
throw new DidError("did/unsupported-method", "did.resolve: unsupported DID method '" + parsed.method + "' (did:key and did:web only)");
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Import a publicKeyJwk after allowlisting its kty/crv — a DID document
|
|
372
|
+
// is untrusted input, so an unexpected key type (RSA / oct / unknown
|
|
373
|
+
// curve) is refused before it reaches node:crypto rather than blindly
|
|
374
|
+
// imported (the DID-context equivalent of the JWT alg/kty cross-check;
|
|
375
|
+
// there is no single verification `alg` in a DID document).
|
|
376
|
+
function _jwkToKey(jwk) {
|
|
377
|
+
var ok = (jwk.kty === "OKP" && jwk.crv === "Ed25519") ||
|
|
378
|
+
(jwk.kty === "EC" && (jwk.crv === "P-256" || jwk.crv === "P-384" || jwk.crv === "secp256k1"));
|
|
379
|
+
if (!ok) {
|
|
380
|
+
throw new DidError("did/unsupported-key",
|
|
381
|
+
"did: verificationMethod publicKeyJwk has unsupported kty/crv (" + jwk.kty + "/" + jwk.crv + ")");
|
|
382
|
+
}
|
|
383
|
+
try { return nodeCrypto.createPublicKey({ key: jwk, format: "jwk" }); }
|
|
384
|
+
catch (e) { throw new DidError("did/bad-key", "did: verificationMethod publicKeyJwk is invalid: " + ((e && e.message) || e)); }
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Extract verification methods from a DID document → KeyObjects.
|
|
388
|
+
function _extractVerificationMethods(doc) {
|
|
389
|
+
var vms = Array.isArray(doc.verificationMethod) ? doc.verificationMethod : [];
|
|
390
|
+
var out = [];
|
|
391
|
+
for (var i = 0; i < vms.length; i += 1) {
|
|
392
|
+
var vm = vms[i];
|
|
393
|
+
if (!vm || typeof vm !== "object") continue;
|
|
394
|
+
var key = null;
|
|
395
|
+
if (typeof vm.publicKeyMultibase === "string" && vm.publicKeyMultibase[0] === "z") {
|
|
396
|
+
var raw = _b58decode(vm.publicKeyMultibase.slice(1));
|
|
397
|
+
var vh = _readVarint(raw);
|
|
398
|
+
key = _keyObjectFromMulticodec(vh.value, raw.slice(vh.length));
|
|
399
|
+
} else if (vm.publicKeyJwk && typeof vm.publicKeyJwk === "object") {
|
|
400
|
+
key = _jwkToKey(vm.publicKeyJwk);
|
|
401
|
+
} else {
|
|
402
|
+
continue; // unknown key encoding — skip rather than guess
|
|
403
|
+
}
|
|
404
|
+
out.push({ id: vm.id, controller: vm.controller, type: vm.type, publicKey: key });
|
|
405
|
+
}
|
|
406
|
+
if (!out.length) throw new DidError("did/no-keys", "did: document has no resolvable verification methods");
|
|
407
|
+
return out;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
module.exports = {
|
|
411
|
+
parse: parse,
|
|
412
|
+
keyToDid: keyToDid,
|
|
413
|
+
resolve: resolve,
|
|
414
|
+
MULTICODEC: MULTICODEC,
|
|
415
|
+
DidError: DidError,
|
|
416
|
+
};
|
|
@@ -251,17 +251,138 @@ async function verifyIssuerSigned(issuerSigned, opts) {
|
|
|
251
251
|
_verifyChain(chain, anchors, at);
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
+
// The device key (MSO deviceKeyInfo.deviceKey, a COSE_Key) binds the
|
|
255
|
+
// holder — surfaced for b.mdoc.verifyDeviceAuth.
|
|
256
|
+
var deviceKeyInfo = _mapGet(mso, "deviceKeyInfo");
|
|
257
|
+
var deviceKey = deviceKeyInfo ? _mapGet(deviceKeyInfo, "deviceKey") : undefined;
|
|
258
|
+
|
|
254
259
|
return {
|
|
255
260
|
docType: docType,
|
|
256
261
|
version: _mapGet(mso, "version"),
|
|
257
262
|
digestAlgorithm: digestAlgName,
|
|
258
263
|
validityInfo: { validFrom: new Date(validFromMs), validUntil: new Date(validUntilMs) },
|
|
259
264
|
namespaces: out,
|
|
265
|
+
deviceKey: deviceKey,
|
|
260
266
|
signerCert: signerCert.toString(),
|
|
261
267
|
alg: verified.alg,
|
|
262
268
|
};
|
|
263
269
|
}
|
|
264
270
|
|
|
271
|
+
/**
|
|
272
|
+
* @primitive b.mdoc.verifyDeviceAuth
|
|
273
|
+
* @signature b.mdoc.verifyDeviceAuth(opts)
|
|
274
|
+
* @since 0.12.46
|
|
275
|
+
* @status experimental
|
|
276
|
+
* @compliance gdpr, soc2
|
|
277
|
+
* @related b.mdoc.verifyIssuerSigned, b.cose.verify
|
|
278
|
+
*
|
|
279
|
+
* Verify the device-authentication half of an ISO 18013-5 mdoc (§9.1.3,
|
|
280
|
+
* signature variant) — the proof that the holder controls the device key
|
|
281
|
+
* the issuer bound into the MSO, which stops a captured issuer-signed
|
|
282
|
+
* document from being replayed by anyone else. The device's COSE_Sign1
|
|
283
|
+
* (<code>deviceSigned.deviceAuth.deviceSignature</code>) is verified over
|
|
284
|
+
* the detached DeviceAuthentication structure
|
|
285
|
+
* (<code>["DeviceAuthentication", SessionTranscript, DocType,
|
|
286
|
+
* DeviceNameSpacesBytes]</code>) with the device key from the issuer-signed
|
|
287
|
+
* MSO (<code>verifyIssuerSigned(...).deviceKey</code>). The
|
|
288
|
+
* <code>sessionTranscript</code> binds the proof to this exact exchange
|
|
289
|
+
* and is supplied by the operator (the presentation protocol — e.g.
|
|
290
|
+
* OpenID4VP — defines it). The MAC variant (<code>deviceMac</code> /
|
|
291
|
+
* COSE_Mac0, used in proximity flows with a reader ephemeral key) is not
|
|
292
|
+
* yet supported and is refused with <code>mdoc/device-mac-unsupported</code>.
|
|
293
|
+
*
|
|
294
|
+
* @opts
|
|
295
|
+
* {
|
|
296
|
+
* deviceKey: object, // COSE_Key (from verifyIssuerSigned().deviceKey) or a KeyObject / PEM
|
|
297
|
+
* deviceSigned: object, // the DeviceSigned structure (CBOR bytes or decoded)
|
|
298
|
+
* docType: string, // the document type (must match the issuer-signed docType)
|
|
299
|
+
* sessionTranscript: any, // the SessionTranscript (CBOR bytes or decoded) bound by the protocol
|
|
300
|
+
* algorithms: string[], // required — accepted COSE alg names (ES256/384/512, EdDSA)
|
|
301
|
+
* maxBytes: number, // forwarded to b.cbor.decode
|
|
302
|
+
* maxDepth: number,
|
|
303
|
+
* }
|
|
304
|
+
*
|
|
305
|
+
* @example
|
|
306
|
+
* var issuer = await b.mdoc.verifyIssuerSigned(issuerSignedBytes, { algorithms: ["ES256"] });
|
|
307
|
+
* var dev = await b.mdoc.verifyDeviceAuth({ deviceKey: issuer.deviceKey, deviceSigned: deviceSignedBytes, docType: issuer.docType, sessionTranscript: transcript, algorithms: ["ES256"] });
|
|
308
|
+
* // → { docType, alg, deviceNamespaces }
|
|
309
|
+
*/
|
|
310
|
+
async function verifyDeviceAuth(opts) {
|
|
311
|
+
validateOpts.requireObject(opts, "mdoc.verifyDeviceAuth", MdocError);
|
|
312
|
+
validateOpts(opts, ["deviceKey", "deviceSigned", "docType", "sessionTranscript", "algorithms", "maxBytes", "maxDepth"], "mdoc.verifyDeviceAuth");
|
|
313
|
+
if (!Array.isArray(opts.algorithms) || opts.algorithms.length === 0) {
|
|
314
|
+
throw new MdocError("mdoc/algorithms-required", "mdoc.verifyDeviceAuth: opts.algorithms is required");
|
|
315
|
+
}
|
|
316
|
+
if (typeof opts.docType !== "string" || !opts.docType) {
|
|
317
|
+
throw new MdocError("mdoc/bad-input", "mdoc.verifyDeviceAuth: opts.docType is required");
|
|
318
|
+
}
|
|
319
|
+
if (opts.sessionTranscript === undefined || opts.sessionTranscript === null) {
|
|
320
|
+
throw new MdocError("mdoc/no-session-transcript", "mdoc.verifyDeviceAuth: opts.sessionTranscript is required (the protocol-bound transcript)");
|
|
321
|
+
}
|
|
322
|
+
var decodeOpts = { allowedTags: ALLOWED_TAGS, maxBytes: opts.maxBytes, maxDepth: opts.maxDepth };
|
|
323
|
+
|
|
324
|
+
// Device key → KeyObject. Accept a COSE_Key (Map/object) via importKey,
|
|
325
|
+
// or an already-loaded KeyObject / PEM.
|
|
326
|
+
var deviceKeyObj;
|
|
327
|
+
if (opts.deviceKey && typeof opts.deviceKey === "object" && typeof opts.deviceKey.asymmetricKeyType === "string") {
|
|
328
|
+
deviceKeyObj = opts.deviceKey;
|
|
329
|
+
} else if (opts.deviceKey instanceof Map || (opts.deviceKey && typeof opts.deviceKey === "object")) {
|
|
330
|
+
deviceKeyObj = cose.importKey(opts.deviceKey);
|
|
331
|
+
} else if (typeof opts.deviceKey === "string") {
|
|
332
|
+
deviceKeyObj = opts.deviceKey; // PEM, resolved by b.cose
|
|
333
|
+
} else {
|
|
334
|
+
throw new MdocError("mdoc/no-device-key", "mdoc.verifyDeviceAuth: opts.deviceKey is required (a COSE_Key or KeyObject)");
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
var ds = (Buffer.isBuffer(opts.deviceSigned) || opts.deviceSigned instanceof Uint8Array)
|
|
338
|
+
? cbor.decode(_bytes(opts.deviceSigned, "deviceSigned"), decodeOpts) : opts.deviceSigned;
|
|
339
|
+
var deviceNameSpaces = _mapGet(ds, "nameSpaces");
|
|
340
|
+
var deviceAuth = _mapGet(ds, "deviceAuth");
|
|
341
|
+
if (!deviceNameSpaces || !deviceAuth) {
|
|
342
|
+
throw new MdocError("mdoc/malformed", "mdoc.verifyDeviceAuth: deviceSigned must have nameSpaces + deviceAuth");
|
|
343
|
+
}
|
|
344
|
+
if (!(deviceNameSpaces instanceof cbor.Tag) || deviceNameSpaces.tag !== TAG_ENCODED_CBOR) {
|
|
345
|
+
throw new MdocError("mdoc/malformed", "mdoc.verifyDeviceAuth: deviceSigned.nameSpaces must be a Tag-24 DeviceNameSpacesBytes");
|
|
346
|
+
}
|
|
347
|
+
var deviceSignature = _mapGet(deviceAuth, "deviceSignature");
|
|
348
|
+
if (!deviceSignature) {
|
|
349
|
+
if (_mapGet(deviceAuth, "deviceMac") !== undefined) {
|
|
350
|
+
throw new MdocError("mdoc/device-mac-unsupported",
|
|
351
|
+
"mdoc.verifyDeviceAuth: the MAC variant (deviceMac / COSE_Mac0) is not supported — only deviceSignature");
|
|
352
|
+
}
|
|
353
|
+
throw new MdocError("mdoc/no-device-signature", "mdoc.verifyDeviceAuth: deviceAuth has no deviceSignature");
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
var st = (Buffer.isBuffer(opts.sessionTranscript) || opts.sessionTranscript instanceof Uint8Array)
|
|
357
|
+
? cbor.decode(_bytes(opts.sessionTranscript, "sessionTranscript"), decodeOpts) : opts.sessionTranscript;
|
|
358
|
+
|
|
359
|
+
// DeviceAuthentication (ISO 18013-5 §9.1.3.4); the detached payload is
|
|
360
|
+
// its Tag-24-wrapped CBOR.
|
|
361
|
+
var deviceAuthentication = ["DeviceAuthentication", st, opts.docType, deviceNameSpaces];
|
|
362
|
+
var deviceAuthBytes = cbor.encode(new cbor.Tag(TAG_ENCODED_CBOR, cbor.encode(deviceAuthentication)));
|
|
363
|
+
|
|
364
|
+
var coseBytes = Array.isArray(deviceSignature) ? cbor.encode(deviceSignature) : _bytes(deviceSignature, "deviceSignature");
|
|
365
|
+
var out = await cose.verify(coseBytes, {
|
|
366
|
+
algorithms: opts.algorithms,
|
|
367
|
+
keyResolver: function () { return deviceKeyObj; },
|
|
368
|
+
externalPayload: deviceAuthBytes,
|
|
369
|
+
maxBytes: opts.maxBytes,
|
|
370
|
+
maxDepth: opts.maxDepth,
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
var deviceNamespaces = {};
|
|
374
|
+
try {
|
|
375
|
+
var dns = cbor.decode(deviceNameSpaces.value, decodeOpts);
|
|
376
|
+
if (dns instanceof Map) {
|
|
377
|
+
dns.forEach(function (items, ns) {
|
|
378
|
+
deviceNamespaces[ns] = items instanceof Map ? Object.fromEntries(items) : items;
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
} catch (_e) { /* device-released namespaces are optional + advisory */ }
|
|
382
|
+
|
|
383
|
+
return { docType: opts.docType, alg: out.alg, deviceNamespaces: deviceNamespaces };
|
|
384
|
+
}
|
|
385
|
+
|
|
265
386
|
// Verify the leaf (chain[0]) chains to a supplied anchor and every cert
|
|
266
387
|
// is valid at `at`. Intermediates in the x5chain are consulted.
|
|
267
388
|
function _verifyChain(chainDer, anchorsPem, at) {
|
|
@@ -300,6 +421,7 @@ function _assertValidAt(cert, atMs) {
|
|
|
300
421
|
|
|
301
422
|
module.exports = {
|
|
302
423
|
verifyIssuerSigned: verifyIssuerSigned,
|
|
424
|
+
verifyDeviceAuth: verifyDeviceAuth,
|
|
303
425
|
DIGEST_ALGS: DIGEST_ALGS,
|
|
304
426
|
MdocError: MdocError,
|
|
305
427
|
};
|