@blamejs/pki 0.4.2 → 0.4.3

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/lib/webcrypto.js CHANGED
@@ -20,8 +20,9 @@
20
20
  * **PQC-first without being PQC-only**: the FIPS 204 ML-DSA and FIPS
21
21
  * 205 SLH-DSA signature suites sit alongside the full classical set PKI
22
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).
23
+ * Ed25519 / Ed448, AES-GCM / CBC / KW, HMAC, HKDF, PBKDF2, the SHA family
24
+ * (including legacy SHA-1 for old certificates and signatures), and the
25
+ * SHAKE128 / SHAKE256 extendable-output functions as message digests.
25
26
  * FIPS 203 ML-KEM key generation, encoding, certificate/PKCS#8 import,
26
27
  * and encapsulation/decapsulation (encapsulateBits / decapsulateBits over
27
28
  * Node's crypto.encapsulate/decapsulate) are all available.
@@ -100,6 +101,21 @@ var HASH_NODE = {
100
101
  "SHA3-512": "sha3-512",
101
102
  };
102
103
 
104
+ // FIPS 202 extendable-output functions, as message digests. Deliberately a
105
+ // SEPARATE table from HASH_NODE rather than rows added to it: an XOF's output
106
+ // length is a parameter rather than a property of its name, and node:crypto
107
+ // refuses an XOF at every other HASH_NODE consumer (signature, HMAC, HKDF,
108
+ // PBKDF2, OAEP). Widening the shared table would turn those consumers' typed
109
+ // "unsupported hash" rejection into a raw error from the underlying library.
110
+ // The length is fixed by the name, not chosen by the caller: RFC 8702 sec. 4
111
+ // requires the output length of SHAKE128 or SHAKE256 used as a message digest
112
+ // to be 32 or 64 bytes respectively, and RFC 9814 sec. 4 and
113
+ // draft-ietf-lamps-cms-composite-sigs sec. 3 restate it for their profiles.
114
+ var XOF_NODE = {
115
+ "SHAKE128": { node: "shake128", length: 32 },
116
+ "SHAKE256": { node: "shake256", length: 64 },
117
+ };
118
+
103
119
  function _hashNode(h, who) {
104
120
  var name = (typeof h === "string") ? h : (h && h.name);
105
121
  var node = HASH_NODE[String(name).toUpperCase()];
@@ -218,8 +234,14 @@ function _requireAlgMatch(alg, key, who) {
218
234
  function SubtleCrypto() {}
219
235
 
220
236
  SubtleCrypto.prototype.digest = async function digest(algorithm, data) {
221
- var node = _hashNode(_normalizeAlg(algorithm, "digest").name, "digest");
222
- var h = nodeCrypto.createHash(node);
237
+ var name = _normalizeAlg(algorithm, "digest").name;
238
+ var xof = XOF_NODE[String(name).toUpperCase()];
239
+ // An XOF is always constructed with its output length stated. Omitting it
240
+ // makes node emit a deprecation warning and fall back to a 32-byte squeeze,
241
+ // which for SHAKE256 would silently produce half the required digest.
242
+ var h = xof
243
+ ? nodeCrypto.createHash(xof.node, { outputLength: xof.length })
244
+ : nodeCrypto.createHash(_hashNode(name, "digest"));
223
245
  h.update(_toBuf(data, "digest"));
224
246
  return _toArrayBuffer(h.digest());
225
247
  };
@@ -256,11 +278,15 @@ SubtleCrypto.prototype.generateKey = async function generateKey(algorithm, extra
256
278
  function _generateKeyPair(alg) {
257
279
  var name = alg.name, kp, algorithm;
258
280
  if (name === "RSASSA-PKCS1-V1_5" || name === "RSA-PSS" || name === "RSA-OAEP") {
281
+ // Resolve the hash BEFORE generating. An RSA keygen is the expensive step here, and a hash
282
+ // this engine cannot use yields a key that can never sign, so the refusal has to come first
283
+ // or the caller pays for a keypair only to be told the request was unusable.
284
+ var rsaHash = _hashObj(alg.hash, "generateKey " + name);
259
285
  kp = nodeCrypto.generateKeyPairSync("rsa", {
260
286
  modulusLength: alg.modulusLength || 2048,
261
287
  publicExponent: alg.publicExponent ? _bufToBigIntNum(alg.publicExponent) : 65537,
262
288
  });
263
- algorithm = { name: _stdName(name), modulusLength: alg.modulusLength || 2048, publicExponent: alg.publicExponent, hash: _hashObj(alg.hash) };
289
+ algorithm = { name: _stdName(name), modulusLength: alg.modulusLength || 2048, publicExponent: alg.publicExponent, hash: rsaHash };
264
290
  } else if (name === "ECDSA" || name === "ECDH") {
265
291
  var curve = alg.namedCurve;
266
292
  if (!CURVE_NODE[curve]) throw new WebCryptoError("webcrypto/not-supported", name + ": unsupported curve " + JSON.stringify(curve));
@@ -284,7 +310,13 @@ function _generateKeyPair(alg) {
284
310
  return { publicKey: kp.publicKey, privateKey: kp.privateKey, algorithm: algorithm };
285
311
  }
286
312
 
287
- function _hashObj(h) { if (!h) return undefined; return { name: (typeof h === "string" ? h : h.name) }; }
313
+ // The hash recorded on a CryptoKey's algorithm. The name is RESOLVED here, so an
314
+ // entry point refuses a hash this engine cannot use instead of minting a key that
315
+ // only fails at its first sign / verify / wrap -- after the caller has already paid
316
+ // for the key generation. Resolution runs through _hashNode itself rather than a
317
+ // second table, so what an entry point accepts cannot drift from what the
318
+ // operations support.
319
+ function _hashObj(h, who) { if (!h) return undefined; var n = (typeof h === "string" ? h : h.name); _hashNode(n, who || "importKey"); return { name: n }; }
288
320
  // The node key argument for an ML-DSA sign/verify: the bare handle, or a
289
321
  // { key, context } when the caller supplies an ML-DSA context. FIPS 204 bounds the
290
322
  // context to 0..255 bytes; a longer one is a DataError at the API boundary, not a
@@ -849,7 +881,7 @@ SubtleCrypto.prototype.importKey = async function importKey(format, keyData, alg
849
881
  var raw = _toBuf(keyData, "importKey raw");
850
882
  _assertAesImportLen(name, raw.length);
851
883
  var secret = nodeCrypto.createSecretKey(raw);
852
- var symAlg = (name === "HMAC") ? { name: name, hash: _hashObj(alg.hash), length: raw.length * 8 } : { name: name, length: raw.length * 8 };
884
+ var symAlg = (name === "HMAC") ? { name: name, hash: _hashObj(alg.hash, "importKey raw HMAC"), length: raw.length * 8 } : { name: name, length: raw.length * 8 };
853
885
  return new CryptoKey("secret", extractable, symAlg, usages, secret);
854
886
  }
855
887
  // Raw public keys are imported via JWK reconstruction below.
@@ -879,7 +911,7 @@ SubtleCrypto.prototype.importKey = async function importKey(format, keyData, alg
879
911
  var kbuf = _b64urlToBuf(jwk.k, "JWK oct key material");
880
912
  _assertAesImportLen(name, kbuf.length);
881
913
  var s2 = nodeCrypto.createSecretKey(kbuf);
882
- var a2 = (name === "HMAC") ? { name: name, hash: _hashObj(alg.hash), length: kbuf.length * 8 } : { name: name, length: kbuf.length * 8 };
914
+ var a2 = (name === "HMAC") ? { name: name, hash: _hashObj(alg.hash, "importKey jwk HMAC"), length: kbuf.length * 8 } : { name: name, length: kbuf.length * 8 };
883
915
  return new CryptoKey("secret", extractable, a2, usages, s2);
884
916
  }
885
917
  var isPrivate = Object.prototype.hasOwnProperty.call(jwk, "d");
@@ -946,7 +978,7 @@ function _algFromImport(name, alg, keyObject) {
946
978
  }
947
979
  return { name: name, namedCurve: actualCurve };
948
980
  }
949
- if (name === "RSASSA-PKCS1-V1_5" || name === "RSA-PSS" || name === "RSA-OAEP") return { name: _stdName(name), hash: _hashObj(alg.hash) };
981
+ if (name === "RSASSA-PKCS1-V1_5" || name === "RSA-PSS" || name === "RSA-OAEP") return { name: _stdName(name), hash: _hashObj(alg.hash, "importKey " + name) };
950
982
  if (name === "ED25519" || name === "ED448") return { name: _stdName(name) };
951
983
  return { name: alg.name };
952
984
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/pki",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
package/sbom.cdx.json CHANGED
@@ -2,10 +2,10 @@
2
2
  "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
3
3
  "bomFormat": "CycloneDX",
4
4
  "specVersion": "1.5",
5
- "serialNumber": "urn:uuid:484717d3-b574-44fc-ba53-e13d70ea5f85",
5
+ "serialNumber": "urn:uuid:1c291ecb-db3e-4569-b3aa-70bb7803b9f3",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-08-08T05:35:44.402Z",
8
+ "timestamp": "2026-08-08T07:58:59.469Z",
9
9
  "lifecycles": [
10
10
  {
11
11
  "phase": "build"
@@ -19,14 +19,14 @@
19
19
  }
20
20
  ],
21
21
  "component": {
22
- "bom-ref": "@blamejs/pki@0.4.2",
22
+ "bom-ref": "@blamejs/pki@0.4.3",
23
23
  "type": "application",
24
24
  "name": "pki",
25
- "version": "0.4.2",
25
+ "version": "0.4.3",
26
26
  "scope": "required",
27
27
  "author": "blamejs contributors",
28
28
  "description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
29
- "purl": "pkg:npm/%40blamejs/pki@0.4.2",
29
+ "purl": "pkg:npm/%40blamejs/pki@0.4.3",
30
30
  "properties": [],
31
31
  "externalReferences": [
32
32
  {
@@ -54,7 +54,7 @@
54
54
  "components": [],
55
55
  "dependencies": [
56
56
  {
57
- "ref": "@blamejs/pki@0.4.2",
57
+ "ref": "@blamejs/pki@0.4.3",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]