@blamejs/pki 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.
@@ -0,0 +1,542 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) blamejs contributors
3
+ "use strict";
4
+ /**
5
+ * @module pki.webcrypto
6
+ * @nav Core
7
+ * @title WebCrypto
8
+ * @order 50
9
+ * @featured true
10
+ * @slug webcrypto
11
+ *
12
+ * @intro
13
+ * A zero-dependency W3C Web Cryptography API (`Crypto` / `SubtleCrypto`
14
+ * / `CryptoKey`) built directly on Node's native `node:crypto`. It is
15
+ * the toolkit's injectable crypto engine, presented in the standard
16
+ * WebCrypto shape so operators — and every higher structure (X.509,
17
+ * CMS, OCSP) — reach for one familiar surface.
18
+ *
19
+ * Unlike the browser's built-in `crypto.subtle`, this engine is
20
+ * **PQC-first without being PQC-only**: the FIPS 204 ML-DSA and FIPS
21
+ * 205 SLH-DSA signature suites sit alongside the full classical set PKI
22
+ * still runs on — RSASSA-PKCS1-v1_5, RSA-PSS, RSA-OAEP, ECDSA, ECDH,
23
+ * Ed25519 / Ed448, AES-GCM / CBC / KW, HMAC, HKDF, PBKDF2, and the SHA
24
+ * family (including legacy SHA-1 for old certificates and signatures).
25
+ * FIPS 203 ML-KEM key generation and encoding are available; KEM
26
+ * encapsulation follows once Node exposes it. Because it is
27
+ * OpenSSL-backed, every key and signature it emits is interoperable
28
+ * with OpenSSL, NSS, and other PKI implementations.
29
+ *
30
+ * @card
31
+ * A zero-dep, PQC-first W3C WebCrypto (`SubtleCrypto`) engine over
32
+ * `node:crypto` — ML-DSA + SLH-DSA signatures alongside the full
33
+ * classical algorithm set.
34
+ */
35
+
36
+ var nodeCrypto = require("node:crypto");
37
+ var frameworkError = require("./framework-error");
38
+
39
+ // Single-owner error class — co-located with its module (framework-error
40
+ // stays the cross-module home; this is webcrypto-private).
41
+ var WebCryptoError = frameworkError.defineClass("WebCryptoError");
42
+
43
+ var MAX_RANDOM_BYTES = 65536;
44
+
45
+ // ---- value helpers ---------------------------------------------------
46
+
47
+ function _toBuf(data, who) {
48
+ if (Buffer.isBuffer(data)) return data;
49
+ if (data instanceof ArrayBuffer) return Buffer.from(data);
50
+ if (ArrayBuffer.isView(data)) return Buffer.from(data.buffer, data.byteOffset, data.byteLength);
51
+ throw new WebCryptoError("webcrypto/data", (who || "input") + ": expected BufferSource (ArrayBuffer / TypedArray / Buffer)");
52
+ }
53
+
54
+ function _toArrayBuffer(buf) {
55
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
56
+ }
57
+
58
+ function _b64urlToBuf(s) { return Buffer.from(String(s), "base64url"); }
59
+ function _bufToB64url(buf) { return Buffer.from(buf).toString("base64url"); }
60
+
61
+ function _normalizeAlg(algorithm, who) {
62
+ var alg = (typeof algorithm === "string") ? { name: algorithm } : algorithm;
63
+ if (!alg || typeof alg.name !== "string") {
64
+ throw new WebCryptoError("webcrypto/syntax", (who || "operation") + ": algorithm must be a string or an object with a name");
65
+ }
66
+ var out = {};
67
+ for (var k in alg) { if (Object.prototype.hasOwnProperty.call(alg, k)) out[k] = alg[k]; }
68
+ out.name = alg.name.toUpperCase();
69
+ return out;
70
+ }
71
+
72
+ // WebCrypto hash name -> node digest name. SHA-1 is retained for
73
+ // backwards compatibility with legacy certificates and signatures.
74
+ var HASH_NODE = {
75
+ "SHA-1": "sha1",
76
+ "SHA-256": "sha256",
77
+ "SHA-384": "sha384",
78
+ "SHA-512": "sha512",
79
+ "SHA3-256": "sha3-256",
80
+ "SHA3-384": "sha3-384",
81
+ "SHA3-512": "sha3-512",
82
+ };
83
+
84
+ function _hashNode(h, who) {
85
+ var name = (typeof h === "string") ? h : (h && h.name);
86
+ var node = HASH_NODE[String(name).toUpperCase()];
87
+ if (!node) throw new WebCryptoError("webcrypto/not-supported", (who || "operation") + ": unsupported hash " + JSON.stringify(name));
88
+ return node;
89
+ }
90
+
91
+ // WebCrypto namedCurve -> node namedCurve.
92
+ var CURVE_NODE = { "P-256": "prime256v1", "P-384": "secp384r1", "P-521": "secp521r1" };
93
+ var CURVE_FIELD_BYTES = { "P-256": 32, "P-384": 48, "P-521": 66 };
94
+
95
+ var ML_DSA_NODE = { "ML-DSA-44": "ml-dsa-44", "ML-DSA-65": "ml-dsa-65", "ML-DSA-87": "ml-dsa-87" };
96
+ var ML_KEM_NODE = { "ML-KEM-512": "ml-kem-512", "ML-KEM-768": "ml-kem-768", "ML-KEM-1024": "ml-kem-1024" };
97
+
98
+ // FIPS 205 SLH-DSA — stateless hash-based signatures. All twelve
99
+ // parameter sets Node exposes; signing is one-shot (null algorithm), the
100
+ // same shape as ML-DSA / EdDSA. KEM encapsulation for ML-KEM is not yet
101
+ // in Node's API, so ML-KEM here is key-generation / encoding only.
102
+ var SLH_DSA_NODE = {};
103
+ ["sha2-128s", "sha2-128f", "sha2-192s", "sha2-192f", "sha2-256s", "sha2-256f",
104
+ "shake-128s", "shake-128f", "shake-192s", "shake-192f", "shake-256s", "shake-256f"
105
+ ].forEach(function (s) { SLH_DSA_NODE["SLH-DSA-" + s.toUpperCase()] = "slh-dsa-" + s; });
106
+
107
+ // ---- CryptoKey -------------------------------------------------------
108
+
109
+ /**
110
+ * @primitive pki.webcrypto.CryptoKey
111
+ * @signature new pki.webcrypto.CryptoKey(type, extractable, algorithm, usages, handle)
112
+ * @since 0.1.0
113
+ * @status stable
114
+ *
115
+ * Opaque handle to key material, matching the W3C `CryptoKey` shape:
116
+ * `{ type, extractable, algorithm, usages }`. The underlying
117
+ * `node:crypto` KeyObject is non-enumerable and never serialized —
118
+ * extract material only through `subtle.exportKey`, and only when the key
119
+ * was created `extractable`. Instances are produced by
120
+ * `subtle.generateKey` / `subtle.importKey`; the constructor is rarely
121
+ * called directly.
122
+ *
123
+ * @example
124
+ * var kp = await pki.webcrypto.subtle.generateKey({ name: "Ed25519" }, true, ["sign", "verify"]);
125
+ * kp.publicKey.type; // "public"
126
+ * kp.publicKey.algorithm; // { name: "Ed25519" }
127
+ */
128
+ function CryptoKey(type, extractable, algorithm, usages, handle) {
129
+ this.type = type;
130
+ this.extractable = !!extractable;
131
+ this.algorithm = algorithm;
132
+ this.usages = usages ? usages.slice() : [];
133
+ Object.defineProperty(this, "_handle", { value: handle, enumerable: false });
134
+ }
135
+
136
+ function _requireUsage(key, usage) {
137
+ if (key.usages.indexOf(usage) === -1) {
138
+ throw new WebCryptoError("webcrypto/invalid-access", "key is not permitted for '" + usage + "' (usages: " + key.usages.join(",") + ")");
139
+ }
140
+ }
141
+
142
+ // ---- SubtleCrypto ----------------------------------------------------
143
+
144
+ function SubtleCrypto() {}
145
+
146
+ SubtleCrypto.prototype.digest = async function digest(algorithm, data) {
147
+ var node = _hashNode(_normalizeAlg(algorithm, "digest").name, "digest");
148
+ var h = nodeCrypto.createHash(node);
149
+ h.update(_toBuf(data, "digest"));
150
+ return _toArrayBuffer(h.digest());
151
+ };
152
+
153
+ SubtleCrypto.prototype.generateKey = async function generateKey(algorithm, extractable, keyUsages) {
154
+ var alg = _normalizeAlg(algorithm, "generateKey");
155
+ var usages = keyUsages || [];
156
+ var name = alg.name;
157
+
158
+ // Symmetric keys.
159
+ if (name === "AES-GCM" || name === "AES-CBC" || name === "AES-CTR" || name === "AES-KW") {
160
+ var bits = alg.length;
161
+ if (bits !== 128 && bits !== 192 && bits !== 256) throw new WebCryptoError("webcrypto/syntax", name + ": length must be 128/192/256");
162
+ var secret = nodeCrypto.createSecretKey(nodeCrypto.randomBytes(bits / 8));
163
+ return new CryptoKey("secret", extractable, { name: name, length: bits }, usages, secret);
164
+ }
165
+ if (name === "HMAC") {
166
+ var hnode = _hashNode(alg.hash, "generateKey HMAC");
167
+ var lenBits = alg.length || (nodeCrypto.createHash(hnode).digest().length * 8);
168
+ var hkey = nodeCrypto.createSecretKey(nodeCrypto.randomBytes(lenBits / 8));
169
+ return new CryptoKey("secret", extractable, { name: name, hash: { name: (typeof alg.hash === "string" ? alg.hash : alg.hash.name) }, length: lenBits }, usages, hkey);
170
+ }
171
+
172
+ // Asymmetric key pairs.
173
+ var pair = _generateKeyPair(alg);
174
+ var pubAlg = pair.algorithm;
175
+ var privUsages = usages.filter(function (u) { return u === "sign" || u === "deriveKey" || u === "deriveBits" || u === "decrypt" || u === "unwrapKey" || u === "decapsulateKey" || u === "decapsulateBits"; });
176
+ var pubUsages = usages.filter(function (u) { return u === "verify" || u === "encrypt" || u === "wrapKey" || u === "encapsulateKey" || u === "encapsulateBits"; });
177
+ return {
178
+ privateKey: new CryptoKey("private", extractable, pubAlg, privUsages, pair.privateKey),
179
+ publicKey: new CryptoKey("public", true, pubAlg, pubUsages, pair.publicKey),
180
+ };
181
+ };
182
+
183
+ function _generateKeyPair(alg) {
184
+ var name = alg.name, kp, algorithm;
185
+ if (name === "RSASSA-PKCS1-V1_5" || name === "RSA-PSS" || name === "RSA-OAEP") {
186
+ kp = nodeCrypto.generateKeyPairSync("rsa", {
187
+ modulusLength: alg.modulusLength || 2048,
188
+ publicExponent: alg.publicExponent ? _bufToBigIntNum(alg.publicExponent) : 65537,
189
+ });
190
+ algorithm = { name: name, modulusLength: alg.modulusLength || 2048, publicExponent: alg.publicExponent, hash: _hashObj(alg.hash) };
191
+ } else if (name === "ECDSA" || name === "ECDH") {
192
+ var curve = alg.namedCurve;
193
+ if (!CURVE_NODE[curve]) throw new WebCryptoError("webcrypto/not-supported", name + ": unsupported curve " + JSON.stringify(curve));
194
+ kp = nodeCrypto.generateKeyPairSync("ec", { namedCurve: CURVE_NODE[curve] });
195
+ algorithm = { name: name, namedCurve: curve };
196
+ } else if (name === "ED25519" || name === "ED448" || name === "X25519" || name === "X448") {
197
+ kp = nodeCrypto.generateKeyPairSync(name.toLowerCase());
198
+ algorithm = { name: (name === "ED25519" ? "Ed25519" : name === "ED448" ? "Ed448" : name) };
199
+ } else if (ML_DSA_NODE[name]) {
200
+ kp = nodeCrypto.generateKeyPairSync(ML_DSA_NODE[name]);
201
+ algorithm = { name: name };
202
+ } else if (ML_KEM_NODE[name]) {
203
+ kp = nodeCrypto.generateKeyPairSync(ML_KEM_NODE[name]);
204
+ algorithm = { name: name };
205
+ } else if (SLH_DSA_NODE[name]) {
206
+ kp = nodeCrypto.generateKeyPairSync(SLH_DSA_NODE[name]);
207
+ algorithm = { name: name };
208
+ } else {
209
+ throw new WebCryptoError("webcrypto/not-supported", "generateKey: unsupported algorithm " + JSON.stringify(name));
210
+ }
211
+ return { publicKey: kp.publicKey, privateKey: kp.privateKey, algorithm: algorithm };
212
+ }
213
+
214
+ function _hashObj(h) { if (!h) return undefined; return { name: (typeof h === "string" ? h : h.name) }; }
215
+ function _bufToBigIntNum(exp) { var b = _toBuf(exp, "publicExponent"); return Number(BigInt("0x" + b.toString("hex"))); }
216
+
217
+ SubtleCrypto.prototype.sign = async function sign(algorithm, key, data) {
218
+ var alg = _normalizeAlg(algorithm, "sign");
219
+ _requireUsage(key, "sign");
220
+ var buf = _toBuf(data, "sign");
221
+ var name = alg.name;
222
+ if (name === "RSASSA-PKCS1-V1_5") {
223
+ return _toArrayBuffer(nodeCrypto.sign(_hashNode(key.algorithm.hash, "sign"), buf, key._handle));
224
+ }
225
+ if (name === "RSA-PSS") {
226
+ return _toArrayBuffer(nodeCrypto.sign(_hashNode(key.algorithm.hash, "sign"), buf, {
227
+ key: key._handle, padding: nodeCrypto.constants.RSA_PKCS1_PSS_PADDING,
228
+ saltLength: (typeof alg.saltLength === "number" ? alg.saltLength : nodeCrypto.constants.RSA_PSS_SALTLEN_DIGEST),
229
+ }));
230
+ }
231
+ if (name === "ECDSA") {
232
+ return _toArrayBuffer(nodeCrypto.sign(_hashNode(alg.hash, "sign"), buf, { key: key._handle, dsaEncoding: "ieee-p1363" }));
233
+ }
234
+ if (name === "ED25519" || name === "ED448" || ML_DSA_NODE[name] || SLH_DSA_NODE[name]) {
235
+ return _toArrayBuffer(nodeCrypto.sign(null, buf, key._handle));
236
+ }
237
+ if (name === "HMAC") {
238
+ var hm = nodeCrypto.createHmac(_hashNode(key.algorithm.hash, "sign"), key._handle);
239
+ hm.update(buf);
240
+ return _toArrayBuffer(hm.digest());
241
+ }
242
+ throw new WebCryptoError("webcrypto/not-supported", "sign: unsupported algorithm " + JSON.stringify(name));
243
+ };
244
+
245
+ SubtleCrypto.prototype.verify = async function verify(algorithm, key, signature, data) {
246
+ var alg = _normalizeAlg(algorithm, "verify");
247
+ _requireUsage(key, "verify");
248
+ var sig = _toBuf(signature, "verify");
249
+ var buf = _toBuf(data, "verify");
250
+ var name = alg.name;
251
+ if (name === "RSASSA-PKCS1-V1_5") {
252
+ return nodeCrypto.verify(_hashNode(key.algorithm.hash, "verify"), buf, key._handle, sig);
253
+ }
254
+ if (name === "RSA-PSS") {
255
+ return nodeCrypto.verify(_hashNode(key.algorithm.hash, "verify"), buf, {
256
+ key: key._handle, padding: nodeCrypto.constants.RSA_PKCS1_PSS_PADDING,
257
+ saltLength: (typeof alg.saltLength === "number" ? alg.saltLength : nodeCrypto.constants.RSA_PSS_SALTLEN_DIGEST),
258
+ }, sig);
259
+ }
260
+ if (name === "ECDSA") {
261
+ return nodeCrypto.verify(_hashNode(alg.hash, "verify"), buf, { key: key._handle, dsaEncoding: "ieee-p1363" }, sig);
262
+ }
263
+ if (name === "ED25519" || name === "ED448" || ML_DSA_NODE[name] || SLH_DSA_NODE[name]) {
264
+ return nodeCrypto.verify(null, buf, key._handle, sig);
265
+ }
266
+ if (name === "HMAC") {
267
+ var hm = nodeCrypto.createHmac(_hashNode(key.algorithm.hash, "verify"), key._handle);
268
+ hm.update(buf);
269
+ return nodeCrypto.timingSafeEqual(hm.digest(), sig);
270
+ }
271
+ throw new WebCryptoError("webcrypto/not-supported", "verify: unsupported algorithm " + JSON.stringify(name));
272
+ };
273
+
274
+ SubtleCrypto.prototype.encrypt = async function encrypt(algorithm, key, data) {
275
+ var alg = _normalizeAlg(algorithm, "encrypt");
276
+ _requireUsage(key, "encrypt");
277
+ var buf = _toBuf(data, "encrypt");
278
+ var name = alg.name;
279
+ if (name === "RSA-OAEP") {
280
+ return _toArrayBuffer(nodeCrypto.publicEncrypt({
281
+ key: key._handle, padding: nodeCrypto.constants.RSA_PKCS1_OAEP_PADDING,
282
+ oaepHash: _hashNode(key.algorithm.hash, "encrypt"),
283
+ oaepLabel: alg.label ? _toBuf(alg.label, "encrypt label") : undefined,
284
+ }, buf));
285
+ }
286
+ if (name === "AES-GCM") {
287
+ var iv = _toBuf(alg.iv, "AES-GCM iv");
288
+ var cipher = nodeCrypto.createCipheriv("aes-" + key.algorithm.length + "-gcm", _secretBytes(key), iv, { authTagLength: (alg.tagLength || 128) / 8 });
289
+ if (alg.additionalData) cipher.setAAD(_toBuf(alg.additionalData, "AES-GCM aad"));
290
+ var ct = Buffer.concat([cipher.update(buf), cipher.final()]);
291
+ return _toArrayBuffer(Buffer.concat([ct, cipher.getAuthTag()]));
292
+ }
293
+ if (name === "AES-CBC") {
294
+ var c2 = nodeCrypto.createCipheriv("aes-" + key.algorithm.length + "-cbc", _secretBytes(key), _toBuf(alg.iv, "AES-CBC iv"));
295
+ return _toArrayBuffer(Buffer.concat([c2.update(buf), c2.final()]));
296
+ }
297
+ if (name === "AES-CTR") {
298
+ var c3 = nodeCrypto.createCipheriv("aes-" + key.algorithm.length + "-ctr", _secretBytes(key), _toBuf(alg.counter, "AES-CTR counter"));
299
+ return _toArrayBuffer(Buffer.concat([c3.update(buf), c3.final()]));
300
+ }
301
+ throw new WebCryptoError("webcrypto/not-supported", "encrypt: unsupported algorithm " + JSON.stringify(name));
302
+ };
303
+
304
+ SubtleCrypto.prototype.decrypt = async function decrypt(algorithm, key, data) {
305
+ var alg = _normalizeAlg(algorithm, "decrypt");
306
+ _requireUsage(key, "decrypt");
307
+ var buf = _toBuf(data, "decrypt");
308
+ var name = alg.name;
309
+ if (name === "RSA-OAEP") {
310
+ return _toArrayBuffer(nodeCrypto.privateDecrypt({
311
+ key: key._handle, padding: nodeCrypto.constants.RSA_PKCS1_OAEP_PADDING,
312
+ oaepHash: _hashNode(key.algorithm.hash, "decrypt"),
313
+ oaepLabel: alg.label ? _toBuf(alg.label, "decrypt label") : undefined,
314
+ }, buf));
315
+ }
316
+ if (name === "AES-GCM") {
317
+ var tagLen = (alg.tagLength || 128) / 8;
318
+ var iv = _toBuf(alg.iv, "AES-GCM iv");
319
+ var ct = buf.subarray(0, buf.length - tagLen);
320
+ var tag = buf.subarray(buf.length - tagLen);
321
+ var d = nodeCrypto.createDecipheriv("aes-" + key.algorithm.length + "-gcm", _secretBytes(key), iv, { authTagLength: tagLen });
322
+ if (alg.additionalData) d.setAAD(_toBuf(alg.additionalData, "AES-GCM aad"));
323
+ d.setAuthTag(tag);
324
+ return _toArrayBuffer(Buffer.concat([d.update(ct), d.final()]));
325
+ }
326
+ if (name === "AES-CBC") {
327
+ var d2 = nodeCrypto.createDecipheriv("aes-" + key.algorithm.length + "-cbc", _secretBytes(key), _toBuf(alg.iv, "AES-CBC iv"));
328
+ return _toArrayBuffer(Buffer.concat([d2.update(buf), d2.final()]));
329
+ }
330
+ if (name === "AES-CTR") {
331
+ var d3 = nodeCrypto.createDecipheriv("aes-" + key.algorithm.length + "-ctr", _secretBytes(key), _toBuf(alg.counter, "AES-CTR counter"));
332
+ return _toArrayBuffer(Buffer.concat([d3.update(buf), d3.final()]));
333
+ }
334
+ throw new WebCryptoError("webcrypto/not-supported", "decrypt: unsupported algorithm " + JSON.stringify(name));
335
+ };
336
+
337
+ function _secretBytes(key) { return key._handle.export(); }
338
+
339
+ SubtleCrypto.prototype.deriveBits = async function deriveBits(algorithm, key, length) {
340
+ var alg = _normalizeAlg(algorithm, "deriveBits");
341
+ _requireUsage(key, "deriveBits");
342
+ var name = alg.name;
343
+ if (name === "ECDH" || name === "X25519" || name === "X448") {
344
+ var secret = nodeCrypto.diffieHellman({ privateKey: key._handle, publicKey: alg.public._handle });
345
+ return _toArrayBuffer(length == null ? secret : secret.subarray(0, length / 8));
346
+ }
347
+ if (name === "HKDF") {
348
+ var derived = nodeCrypto.hkdfSync(_hashNode(alg.hash, "HKDF"), _secretBytes(key), _toBuf(alg.salt, "HKDF salt"), _toBuf(alg.info || Buffer.alloc(0), "HKDF info"), length / 8);
349
+ return derived instanceof ArrayBuffer ? derived : _toArrayBuffer(Buffer.from(derived));
350
+ }
351
+ if (name === "PBKDF2") {
352
+ var out = nodeCrypto.pbkdf2Sync(_secretBytes(key), _toBuf(alg.salt, "PBKDF2 salt"), alg.iterations, length / 8, _hashNode(alg.hash, "PBKDF2"));
353
+ return _toArrayBuffer(out);
354
+ }
355
+ throw new WebCryptoError("webcrypto/not-supported", "deriveBits: unsupported algorithm " + JSON.stringify(name));
356
+ };
357
+
358
+ SubtleCrypto.prototype.deriveKey = async function deriveKey(algorithm, baseKey, derivedKeyType, extractable, keyUsages) {
359
+ var dk = _normalizeAlg(derivedKeyType, "deriveKey");
360
+ var bits = dk.length || (dk.name.indexOf("AES") === 0 ? dk.length : 256);
361
+ var raw = await this.deriveBits(algorithm, baseKey, bits);
362
+ return this.importKey("raw", raw, dk, extractable, keyUsages);
363
+ };
364
+
365
+ SubtleCrypto.prototype.wrapKey = async function wrapKey(format, key, wrappingKey, wrapAlgorithm) {
366
+ var exported = await this.exportKey(format, key);
367
+ var bytes = (format === "jwk") ? Buffer.from(JSON.stringify(exported)) : Buffer.from(exported);
368
+ var alg = _normalizeAlg(wrapAlgorithm, "wrapKey");
369
+ _requireUsage(wrappingKey, "wrapKey");
370
+ if (alg.name === "AES-KW") {
371
+ var c = nodeCrypto.createCipheriv("aes" + wrappingKey.algorithm.length + "-wrap", _secretBytes(wrappingKey), Buffer.from("A6A6A6A6A6A6A6A6", "hex"));
372
+ return _toArrayBuffer(Buffer.concat([c.update(bytes), c.final()]));
373
+ }
374
+ // Delegate to a content-encryption algorithm (RSA-OAEP / AES-GCM).
375
+ var wrapKeyClone = _cloneWithUsage(wrappingKey, "encrypt");
376
+ return this.encrypt(wrapAlgorithm, wrapKeyClone, bytes);
377
+ };
378
+
379
+ SubtleCrypto.prototype.unwrapKey = async function unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgorithm, unwrappedKeyAlgorithm, extractable, keyUsages) {
380
+ var alg = _normalizeAlg(unwrapAlgorithm, "unwrapKey");
381
+ var bytes;
382
+ if (alg.name === "AES-KW") {
383
+ _requireUsage(unwrappingKey, "unwrapKey");
384
+ var d = nodeCrypto.createDecipheriv("aes" + unwrappingKey.algorithm.length + "-wrap", _secretBytes(unwrappingKey), Buffer.from("A6A6A6A6A6A6A6A6", "hex"));
385
+ bytes = Buffer.concat([d.update(_toBuf(wrappedKey, "unwrapKey")), d.final()]);
386
+ } else {
387
+ var unwrapKeyClone = _cloneWithUsage(unwrappingKey, "decrypt");
388
+ bytes = Buffer.from(await this.decrypt(unwrapAlgorithm, unwrapKeyClone, wrappedKey));
389
+ }
390
+ var keyData = (format === "jwk") ? JSON.parse(bytes.toString()) : bytes;
391
+ return this.importKey(format, keyData, unwrappedKeyAlgorithm, extractable, keyUsages);
392
+ };
393
+
394
+ function _cloneWithUsage(key, usage) {
395
+ var k = new CryptoKey(key.type, key.extractable, key.algorithm, key.usages.concat([usage]), key._handle);
396
+ return k;
397
+ }
398
+
399
+ SubtleCrypto.prototype.importKey = async function importKey(format, keyData, algorithm, extractable, keyUsages) {
400
+ var alg = _normalizeAlg(algorithm, "importKey");
401
+ var usages = keyUsages || [];
402
+ var name = alg.name;
403
+
404
+ if (format === "raw") {
405
+ // Symmetric raw material, or a raw public key for EC/OKP.
406
+ if (name === "AES-GCM" || name === "AES-CBC" || name === "AES-CTR" || name === "AES-KW" || name === "HMAC" || name === "HKDF" || name === "PBKDF2") {
407
+ var raw = _toBuf(keyData, "importKey raw");
408
+ var secret = nodeCrypto.createSecretKey(raw);
409
+ var symAlg = (name === "HMAC") ? { name: name, hash: _hashObj(alg.hash), length: raw.length * 8 } : { name: name, length: raw.length * 8 };
410
+ return new CryptoKey("secret", extractable, symAlg, usages, secret);
411
+ }
412
+ // Raw public keys are imported via JWK reconstruction below.
413
+ return _importRawPublic(name, alg, _toBuf(keyData, "importKey raw"), extractable, usages);
414
+ }
415
+
416
+ if (format === "spki") {
417
+ var pub = nodeCrypto.createPublicKey({ key: _toBuf(keyData, "importKey spki"), format: "der", type: "spki" });
418
+ return new CryptoKey("public", true, _algFromImport(name, alg, pub), usages, pub);
419
+ }
420
+ if (format === "pkcs8") {
421
+ var priv = nodeCrypto.createPrivateKey({ key: _toBuf(keyData, "importKey pkcs8"), format: "der", type: "pkcs8" });
422
+ return new CryptoKey("private", extractable, _algFromImport(name, alg, priv), usages, priv);
423
+ }
424
+ if (format === "jwk") {
425
+ var jwk = keyData;
426
+ if (jwk.kty === "oct") {
427
+ var kbuf = _b64urlToBuf(jwk.k);
428
+ var s2 = nodeCrypto.createSecretKey(kbuf);
429
+ var a2 = (name === "HMAC") ? { name: name, hash: _hashObj(alg.hash), length: kbuf.length * 8 } : { name: name, length: kbuf.length * 8 };
430
+ return new CryptoKey("secret", extractable, a2, usages, s2);
431
+ }
432
+ var isPrivate = Object.prototype.hasOwnProperty.call(jwk, "d");
433
+ var ko = isPrivate ? nodeCrypto.createPrivateKey({ key: jwk, format: "jwk" }) : nodeCrypto.createPublicKey({ key: jwk, format: "jwk" });
434
+ return new CryptoKey(isPrivate ? "private" : "public", isPrivate ? extractable : true, _algFromImport(name, alg, ko), usages, ko);
435
+ }
436
+ throw new WebCryptoError("webcrypto/not-supported", "importKey: unsupported format " + JSON.stringify(format));
437
+ };
438
+
439
+ function _importRawPublic(name, alg, raw, extractable, usages) {
440
+ if (name === "ED25519" || name === "ED448" || name === "X25519" || name === "X448") {
441
+ var jwk = { kty: "OKP", crv: (name === "ED25519" ? "Ed25519" : name === "ED448" ? "Ed448" : name === "X25519" ? "X25519" : "X448"), x: _bufToB64url(raw) };
442
+ var ko = nodeCrypto.createPublicKey({ key: jwk, format: "jwk" });
443
+ return new CryptoKey("public", true, { name: alg.name === "ED25519" ? "Ed25519" : alg.name }, usages, ko);
444
+ }
445
+ if (name === "ECDSA" || name === "ECDH") {
446
+ var fb = CURVE_FIELD_BYTES[alg.namedCurve];
447
+ if (!fb || raw[0] !== 0x04 || raw.length !== 1 + 2 * fb) throw new WebCryptoError("webcrypto/data", "importKey raw EC: expected an uncompressed point for " + alg.namedCurve);
448
+ var ecjwk = { kty: "EC", crv: alg.namedCurve, x: _bufToB64url(raw.subarray(1, 1 + fb)), y: _bufToB64url(raw.subarray(1 + fb)) };
449
+ var eck = nodeCrypto.createPublicKey({ key: ecjwk, format: "jwk" });
450
+ return new CryptoKey("public", true, { name: name, namedCurve: alg.namedCurve }, usages, eck);
451
+ }
452
+ throw new WebCryptoError("webcrypto/not-supported", "importKey raw: unsupported public-key algorithm " + JSON.stringify(name));
453
+ }
454
+
455
+ function _algFromImport(name, alg, keyObject) {
456
+ if (name === "ECDSA" || name === "ECDH") return { name: name, namedCurve: alg.namedCurve || _curveFromKey(keyObject) };
457
+ if (name === "RSASSA-PKCS1-V1_5" || name === "RSA-PSS" || name === "RSA-OAEP") return { name: name, hash: _hashObj(alg.hash) };
458
+ if (name === "ED25519") return { name: "Ed25519" };
459
+ if (name === "ED448") return { name: "Ed448" };
460
+ return { name: alg.name };
461
+ }
462
+
463
+ function _curveFromKey(ko) {
464
+ try { var jwk = ko.export({ format: "jwk" }); for (var k in CURVE_NODE) { if (jwk.crv === k) return k; } } catch (_e) { /* best-effort */ }
465
+ return undefined;
466
+ }
467
+
468
+ /**
469
+ * @primitive pki.webcrypto.subtle
470
+ * @signature await pki.webcrypto.subtle.exportKey(format, key)
471
+ * @since 0.1.0
472
+ * @status stable
473
+ * @related pki.webcrypto.CryptoKey
474
+ *
475
+ * Export a `CryptoKey` to `spki` (public), `pkcs8` (private), `jwk`
476
+ * (either), or `raw` (symmetric, or an uncompressed EC / OKP public
477
+ * point). Throws unless the key was created `extractable`.
478
+ *
479
+ * @example
480
+ * var spki = await pki.webcrypto.subtle.exportKey("spki", keyPair.publicKey);
481
+ */
482
+ SubtleCrypto.prototype.exportKey = async function exportKey(format, key) {
483
+ if (!key.extractable) throw new WebCryptoError("webcrypto/invalid-access", "key is not extractable");
484
+ if (format === "jwk") return key._handle.export({ format: "jwk" });
485
+ if (key.type === "secret") {
486
+ var raw = key._handle.export();
487
+ if (format === "raw") return _toArrayBuffer(raw);
488
+ throw new WebCryptoError("webcrypto/not-supported", "exportKey: secret keys support 'raw' / 'jwk' only");
489
+ }
490
+ if (format === "spki") return _toArrayBuffer(key._handle.export({ format: "der", type: "spki" }));
491
+ if (format === "pkcs8") return _toArrayBuffer(key._handle.export({ format: "der", type: "pkcs8" }));
492
+ if (format === "raw") return _toArrayBuffer(_rawPublic(key));
493
+ throw new WebCryptoError("webcrypto/not-supported", "exportKey: unsupported format " + JSON.stringify(format));
494
+ };
495
+
496
+ function _rawPublic(key) {
497
+ var jwk = key._handle.export({ format: "jwk" });
498
+ if (jwk.kty === "OKP") return _b64urlToBuf(jwk.x);
499
+ if (jwk.kty === "EC") return Buffer.concat([Buffer.from([0x04]), _b64urlToBuf(jwk.x), _b64urlToBuf(jwk.y)]);
500
+ throw new WebCryptoError("webcrypto/not-supported", "exportKey raw: unsupported key type " + jwk.kty);
501
+ }
502
+
503
+ // ---- Crypto ----------------------------------------------------------
504
+
505
+ /**
506
+ * @primitive pki.webcrypto
507
+ * @signature pki.webcrypto.getRandomValues(typedArray) / pki.webcrypto.subtle
508
+ * @since 0.1.0
509
+ * @status stable
510
+ * @related pki.webcrypto.subtle
511
+ *
512
+ * A ready `Crypto` instance (the shape of `globalThis.crypto`) exposing
513
+ * `getRandomValues`, `randomUUID`, and `subtle`. Construct additional
514
+ * instances with `new pki.WebCrypto.Crypto()`.
515
+ *
516
+ * @example
517
+ * var iv = pki.webcrypto.getRandomValues(new Uint8Array(12));
518
+ */
519
+ function Crypto() {
520
+ this.subtle = new SubtleCrypto();
521
+ }
522
+
523
+ Crypto.prototype.getRandomValues = function getRandomValues(typedArray) {
524
+ if (!ArrayBuffer.isView(typedArray) || typedArray instanceof Float32Array || typedArray instanceof Float64Array || typedArray instanceof DataView) {
525
+ throw new WebCryptoError("webcrypto/data", "getRandomValues: expected an integer TypedArray");
526
+ }
527
+ if (typedArray.byteLength > MAX_RANDOM_BYTES) {
528
+ throw new WebCryptoError("webcrypto/data", "getRandomValues: byteLength exceeds " + MAX_RANDOM_BYTES);
529
+ }
530
+ nodeCrypto.randomFillSync(Buffer.from(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength));
531
+ return typedArray;
532
+ };
533
+
534
+ Crypto.prototype.randomUUID = function randomUUID() { return nodeCrypto.randomUUID(); };
535
+
536
+ module.exports = {
537
+ webcrypto: new Crypto(),
538
+ Crypto: Crypto,
539
+ SubtleCrypto: SubtleCrypto,
540
+ CryptoKey: CryptoKey,
541
+ WebCryptoError: WebCryptoError,
542
+ };