@blamejs/pki 0.5.15 → 0.5.17

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.
Files changed (67) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/lib/acme.js +1 -0
  3. package/lib/asn1-der.js +1 -0
  4. package/lib/attrcert-sign.js +1 -0
  5. package/lib/cbor-det.js +1 -0
  6. package/lib/cmc-verify.js +1 -0
  7. package/lib/cmp-build.js +1 -0
  8. package/lib/cmp-verify.js +425 -82
  9. package/lib/cms-decrypt.js +58 -29
  10. package/lib/cms-encrypt.js +45 -24
  11. package/lib/cms-sign.js +3 -3
  12. package/lib/cms-verify.js +5 -1
  13. package/lib/constants.js +1 -0
  14. package/lib/crl-sign.js +1 -0
  15. package/lib/crmf-sign.js +1 -0
  16. package/lib/csr-sign.js +1 -0
  17. package/lib/ct.js +1 -0
  18. package/lib/est.js +1 -0
  19. package/lib/framework-error.js +1 -0
  20. package/lib/guard-all.js +2 -0
  21. package/lib/guard-async.js +14 -2
  22. package/lib/guard-bytes.js +183 -93
  23. package/lib/guard-compress.js +32 -12
  24. package/lib/guard-der.js +30 -7
  25. package/lib/guard-encoding.js +28 -12
  26. package/lib/guard-header.js +18 -7
  27. package/lib/guard-identifier.js +146 -79
  28. package/lib/guard-intrinsic.js +318 -0
  29. package/lib/guard-json.js +39 -12
  30. package/lib/guard-limits.js +15 -6
  31. package/lib/guard-name.js +72 -23
  32. package/lib/guard-parsed.js +71 -34
  33. package/lib/guard-range.js +17 -4
  34. package/lib/guard-secret.js +60 -2
  35. package/lib/guard-text.js +35 -6
  36. package/lib/guard-time.js +13 -4
  37. package/lib/hpke.js +6 -2
  38. package/lib/http-transport.js +1 -0
  39. package/lib/inspect.js +1 -0
  40. package/lib/jose.js +1 -0
  41. package/lib/key.js +2 -1
  42. package/lib/lint.js +1 -0
  43. package/lib/merkle.js +1 -0
  44. package/lib/ocsp.js +1 -0
  45. package/lib/oid.js +1 -0
  46. package/lib/path-validate.js +1 -0
  47. package/lib/pbes2.js +16 -8
  48. package/lib/pkcs12-build.js +2 -1
  49. package/lib/schema-all.js +1 -0
  50. package/lib/schema-c509.js +1 -0
  51. package/lib/schema-csrattrs.js +1 -0
  52. package/lib/schema-engine.js +1 -0
  53. package/lib/schema-pkcs8.js +1 -0
  54. package/lib/schema-smime.js +1 -0
  55. package/lib/schema-tsp.js +1 -0
  56. package/lib/schema-x509.js +1 -0
  57. package/lib/shbs.js +1 -0
  58. package/lib/sigstore.js +1 -0
  59. package/lib/smime.js +1 -0
  60. package/lib/tls-cert-compress.js +1 -0
  61. package/lib/trust.js +1 -0
  62. package/lib/tsp-sign.js +1 -0
  63. package/lib/webauthn.js +1 -0
  64. package/lib/webcrypto.js +11 -8
  65. package/lib/x509-sign.js +1 -0
  66. package/package.json +1 -1
  67. package/sbom.cdx.json +6 -6
package/lib/pbes2.js CHANGED
@@ -133,13 +133,21 @@ function parsePbkdf2Params(paramsDer, opts, E, prefix, strictPrf) {
133
133
 
134
134
  // AES-CBC content encryption / decryption (PKCS#7 pad). decrypt's final() throws on bad pad, and the caller
135
135
  // collapses that into its uniform decrypt-failed verdict.
136
- function cbcEncrypt(key, iv, plaintext, keyBits) {
136
+ // cbcEncrypt(key, iv, plaintext, keyBits, E, code) -> the ciphertext.
137
+ // Takes the caller's typed error for the same reason cbcDecrypt does: the cipher runs through the
138
+ // shared guard, whose one throw has to keep the calling domain's own code.
139
+ function cbcEncrypt(key, iv, plaintext, keyBits, E, code) {
137
140
  var c = nodeCrypto.createCipheriv("aes-" + keyBits + "-cbc", key, iv);
138
- return Buffer.concat([c.update(plaintext), c.final()]);
141
+ return guard.secret.cipherFinish(c, plaintext, E, code, "a content-encryption intermediate");
139
142
  }
140
- function cbcDecrypt(key, iv, ct, keyBits) {
143
+ // cbcDecrypt(key, iv, ct, keyBits, E, code) -> the recovered plaintext.
144
+ // Takes the caller's typed error the way rc2.cbcDecrypt does, so a refusal keeps its own domain.
145
+ function cbcDecrypt(key, iv, ct, keyBits, E, code) {
141
146
  var d = nodeCrypto.createDecipheriv("aes-" + keyBits + "-cbc", key, iv);
142
- return Buffer.concat([d.update(ct), d.final()]);
147
+ // A wrong key fails inside final(), on the padding check, and update() has by then produced every
148
+ // block but the last. Through the guard so that partial plaintext is cleared on both exits rather
149
+ // than abandoned readable on exactly the path an attacker induces.
150
+ return guard.secret.cipherFinish(d, ct, E, code, "the partially recovered plaintext");
143
151
  }
144
152
 
145
153
  // Higher-level PBES2 AES-CBC encrypt: derive an AES key from PRE-FORMATTED password bytes (the caller owns
@@ -160,11 +168,11 @@ function pbes2Encrypt(pwBytes, plaintext, opts, E, prefix) {
160
168
  // is not cleared: the caller formatted it and may still own the buffer.
161
169
  var key = nodeCrypto.pbkdf2Sync(pwBytes, salt, iterations, keyBits / 8, prfNode);
162
170
  try {
163
- return { algId: pbes2AlgId(salt, iterations, prf, cipherName, iv), ct: cbcEncrypt(key, iv, plaintext, keyBits) };
171
+ return { algId: pbes2AlgId(salt, iterations, prf, cipherName, iv), ct: cbcEncrypt(key, iv, plaintext, keyBits, E, prefix + "/bad-input") };
164
172
  } finally {
165
173
  // E is the caller's error FACTORY, not a class; guard-secret delegates its only throw to
166
- // guard-bytes, which constructs. yields the error the factory returns, so the
167
- // currency is compatible -- and the throw is unreachable here regardless, because a freshly
174
+ // guard-bytes, which constructs. `new` on a factory yields the object the factory returns, so
175
+ // the currency is compatible -- and the throw is unreachable here regardless, because a freshly
168
176
  // allocated pbkdf2 buffer cannot be detached.
169
177
  guard.secret.zeroize(key, E, prefix + "/bad-input", "the password-derived encryption key");
170
178
  }
@@ -208,7 +216,7 @@ function pbes2Decrypt(pwBytes, params, ciphertext, opts, E, prefix, budget) {
208
216
  if (budget.rounds < 0) throw E(prefix + "/iteration-limit", "the aggregate PBKDF2 key-derivation work exceeds the budget (a hostile many-element input)");
209
217
  }
210
218
  var dk = nodeCrypto.pbkdf2Sync(pwBytes, pb.salt, pb.iterations, keyBits / 8, pb.prfNode);
211
- try { return cbcDecrypt(dk, iv, ciphertext, keyBits); }
219
+ try { return cbcDecrypt(dk, iv, ciphertext, keyBits, E, prefix + "/decrypt-failed"); }
212
220
  catch (_e) { throw E(prefix + "/decrypt-failed", "decryption failed"); }
213
221
  finally { guard.secret.zeroize(dk, E, prefix + "/bad-input", "the password-derived decryption key"); }
214
222
  }
@@ -5,6 +5,7 @@
5
5
  * @module pki.pkcs12
6
6
  * @nav Signing
7
7
  * @title PKCS#12
8
+ * @fullname PKCS#12 keystores (.p12 and .pfx): build and open
8
9
  * @intro The PKCS#12 (.p12 / .pfx) producing side (RFC 7292, RFC 9579). `pki.pkcs12.build` assembles a
9
10
  * password-integrity PFX -- key, certificate, CRL, and secret bags (shrouded keys and cert safes encrypted
10
11
  * under RFC 8018 PBES2) wrapped in an AuthenticatedSafe, protected by either a classic Appendix B HMAC or
@@ -848,7 +849,7 @@ function _decryptLegacyPbe(ea, ct, password, opts, budget) {
848
849
  try {
849
850
  if (scheme.rc2) return rc2.cbcDecrypt(keyM, scheme.rc2, iv, ct, _err, "pkcs12/decrypt-failed");
850
851
  var d = nodeCrypto.createDecipheriv(scheme.cipher, keyM, iv);
851
- return Buffer.concat([d.update(ct), d.final()]);
852
+ return guard.secret.cipherFinish(d, ct, Pkcs12Error, "pkcs12/bad-input", "the recovered safe contents");
852
853
  } catch (_e) { throw _err("pkcs12/decrypt-failed", "decryption failed"); }
853
854
  finally { guard.secret.zeroize(keyM, Pkcs12Error, "pkcs12/bad-input", "the password-derived decryption key"); }
854
855
  }
package/lib/schema-all.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * @module pki.schema
6
6
  * @nav Schema
7
7
  * @title Schema
8
+ * @fullname Format detection: parse DER without knowing the format first
8
9
  * @order 10
9
10
  * @featured true
10
11
  * @slug schema
@@ -5,6 +5,7 @@
5
5
  * @module pki.schema.c509
6
6
  * @nav Schema
7
7
  * @title C509
8
+ * @fullname C509 compact certificates (CBOR-encoded X.509)
8
9
  * @intro C509 CBOR-encoded certificates (draft-ietf-cose-cbor-encoded-cert). A compact CBOR
9
10
  * re-encoding of an X.509 v3 certificate: a deterministic-CBOR array of exactly 11 elements
10
11
  * (10 TBS fields + the issuer signature). Two modes: c509CertificateType 2 is a natively-signed
@@ -5,6 +5,7 @@
5
5
  * @module pki.schema.csrattrs
6
6
  * @nav Schema
7
7
  * @title CSR Attributes
8
+ * @fullname CSR attributes: what an EST server asks an enrollee to include
8
9
  * @order 185
9
10
  * @slug csrattrs
10
11
  *
@@ -5,6 +5,7 @@
5
5
  * @module pki.schema.engine
6
6
  * @nav Schema
7
7
  * @title Schema engine
8
+ * @fullname ASN.1 schema engine: one definition drives encode and decode
8
9
  * @order 60
9
10
  *
10
11
  * @intro
@@ -5,6 +5,7 @@
5
5
  * @module pki.schema.pkcs8
6
6
  * @nav Schema
7
7
  * @title PKCS#8
8
+ * @fullname PKCS#8 private keys, encrypted and unencrypted
8
9
  * @order 140
9
10
  * @slug pkcs8
10
11
  *
@@ -5,6 +5,7 @@
5
5
  * @module pki.schema.smime
6
6
  * @nav Schema
7
7
  * @title S/MIME (ESS)
8
+ * @fullname S/MIME signed and encrypted mail, with ESS security labels
8
9
  * @order 180
9
10
  * @slug smime
10
11
  *
package/lib/schema-tsp.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * @module pki.schema.tsp
6
6
  * @nav Schema
7
7
  * @title TSP
8
+ * @fullname RFC 3161 timestamps (TSP): request, sign and verify
8
9
  * @order 170
9
10
  * @slug tsp
10
11
  *
@@ -5,6 +5,7 @@
5
5
  * @module pki.schema.x509
6
6
  * @nav Schema
7
7
  * @title X.509
8
+ * @fullname X.509 certificates: parse, sign and inspect
8
9
  * @order 100
9
10
  * @featured true
10
11
  * @slug x509
package/lib/shbs.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * @module pki.shbs
6
6
  * @nav Signatures
7
7
  * @title Stateful hash-based
8
+ * @fullname Stateful hash-based signatures: LMS and XMSS
8
9
  * @intro Stateful hash-based signature VERIFICATION -- HSS/LMS (RFC 8554),
9
10
  * carried in X.509 by RFC 9802 and in CMS by RFC 9708, profiled by NIST
10
11
  * SP 800-208. Verify only, by deliberate design: stateful hash-based signing
package/lib/sigstore.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * @module pki.sigstore
6
6
  * @nav Supply chain
7
7
  * @title Sigstore
8
+ * @fullname Sigstore bundles: keyless signing and provenance
8
9
  * @intro Zero-dependency verifier for a Sigstore bundle, the exact artifact
9
10
  * `npm publish --provenance` produces and the npm registry serves at its
10
11
  * attestations API. A bundle is a keyless (Fulcio) signature over a DSSE-wrapped
package/lib/smime.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * @module pki.smime
6
6
  * @nav Messaging
7
7
  * @title S/MIME
8
+ * @fullname S/MIME signed and encrypted mail
8
9
  * @order 175
9
10
  * @slug smime
10
11
  *
@@ -5,6 +5,7 @@
5
5
  * @module pki.tls
6
6
  * @nav Transparency
7
7
  * @title TLS
8
+ * @fullname TLS certificate compression (RFC 8879)
8
9
  * @order 215
9
10
  * @slug tls
10
11
  *
package/lib/trust.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * @module pki.trust
6
6
  * @nav Path validation
7
7
  * @title Trust-store ingestion
8
+ * @fullname Trust stores: load and pin root certificate anchors
8
9
  * @order 230
9
10
  * @slug trust
10
11
  *
package/lib/tsp-sign.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * @module pki.tsp
6
6
  * @nav Signing
7
7
  * @title Timestamps
8
+ * @fullname RFC 3161 trusted timestamps: request, sign and verify
8
9
  * @intro Create an RFC 3161 timestamp token. A TimeStampToken IS a CMS SignedData whose
9
10
  * encapsulated content is a `TSTInfo` (the timestamped message imprint + trusted time), so
10
11
  * `sign(messageImprint, tsa, opts)` builds the `TSTInfo`, attaches the RFC 3161 sec. 2.4.2
package/lib/webauthn.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * @module pki.webauthn
6
6
  * @nav Validation
7
7
  * @title WebAuthn
8
+ * @fullname WebAuthn: verify registration attestation and assertions
8
9
  * @intro Trust evaluation of a W3C WebAuthn (Level 3) / passkey attestation: parse
9
10
  * the attestation object + authenticatorData, decode the COSE credential public
10
11
  * key, and verify each defined attestation-statement format (packed, tpm,
package/lib/webcrypto.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * @module pki.webcrypto
6
6
  * @nav Core
7
7
  * @title WebCrypto
8
+ * @fullname WebCrypto: a SubtleCrypto surface over node:crypto
8
9
  * @order 50
9
10
  * @featured true
10
11
  * @slug webcrypto
@@ -470,7 +471,7 @@ SubtleCrypto.prototype.encrypt = async function encrypt(algorithm, key, data) {
470
471
  return _withSecretBytes(key, function (kb) {
471
472
  var cipher = nodeCrypto.createCipheriv("aes-" + key.algorithm.length + "-gcm", kb, iv, { authTagLength: (alg.tagLength || 128) / 8 });
472
473
  if (aad) cipher.setAAD(aad);
473
- var ct = Buffer.concat([cipher.update(buf), cipher.final()]);
474
+ var ct = guard.secret.cipherFinish(cipher, buf, WebCryptoError, "webcrypto/operation", "an AES-GCM encrypt intermediate");
474
475
  return Buffer.concat([ct, cipher.getAuthTag()]);
475
476
  });
476
477
  }, "encrypt"));
@@ -480,7 +481,7 @@ SubtleCrypto.prototype.encrypt = async function encrypt(algorithm, key, data) {
480
481
  return _toArrayBuffer(_runCipher(function () {
481
482
  return _withSecretBytes(key, function (kb) {
482
483
  var c2 = nodeCrypto.createCipheriv("aes-" + key.algorithm.length + "-cbc", kb, cbcIv);
483
- return Buffer.concat([c2.update(buf), c2.final()]);
484
+ return guard.secret.cipherFinish(c2, buf, WebCryptoError, "webcrypto/operation", "an AES-CBC encrypt intermediate");
484
485
  });
485
486
  }, "encrypt"));
486
487
  }
@@ -490,7 +491,7 @@ SubtleCrypto.prototype.encrypt = async function encrypt(algorithm, key, data) {
490
491
  return _toArrayBuffer(_runCipher(function () {
491
492
  return _withSecretBytes(key, function (kb) {
492
493
  var c3 = nodeCrypto.createCipheriv("aes-" + key.algorithm.length + "-ctr", kb, ctrCounter);
493
- return Buffer.concat([c3.update(buf), c3.final()]);
494
+ return guard.secret.cipherFinish(c3, buf, WebCryptoError, "webcrypto/operation", "an AES-CTR encrypt intermediate");
494
495
  });
495
496
  }, "encrypt"));
496
497
  }
@@ -528,7 +529,9 @@ SubtleCrypto.prototype.decrypt = async function decrypt(algorithm, key, data) {
528
529
  var d = nodeCrypto.createDecipheriv("aes-" + key.algorithm.length + "-gcm", kb, iv, { authTagLength: tagLen });
529
530
  if (gcmAad) d.setAAD(gcmAad);
530
531
  d.setAuthTag(tag);
531
- return Buffer.concat([d.update(ct), d.final()]);
532
+ // The recovered plaintext exists in full before final() judges the tag, so a forged message
533
+ // would otherwise abandon a readable copy on the throw path.
534
+ return guard.secret.cipherFinish(d, ct, WebCryptoError, "webcrypto/operation", "the recovered AES-GCM plaintext");
532
535
  });
533
536
  }, "decrypt"));
534
537
  }
@@ -537,7 +540,7 @@ SubtleCrypto.prototype.decrypt = async function decrypt(algorithm, key, data) {
537
540
  return _toArrayBuffer(_runCipher(function () {
538
541
  return _withSecretBytes(key, function (kb) {
539
542
  var d2 = nodeCrypto.createDecipheriv("aes-" + key.algorithm.length + "-cbc", kb, cbcIv2);
540
- return Buffer.concat([d2.update(buf), d2.final()]);
543
+ return guard.secret.cipherFinish(d2, buf, WebCryptoError, "webcrypto/operation", "the recovered AES-CBC plaintext");
541
544
  });
542
545
  }, "decrypt"));
543
546
  }
@@ -547,7 +550,7 @@ SubtleCrypto.prototype.decrypt = async function decrypt(algorithm, key, data) {
547
550
  return _toArrayBuffer(_runCipher(function () {
548
551
  return _withSecretBytes(key, function (kb) {
549
552
  var d3 = nodeCrypto.createDecipheriv("aes-" + key.algorithm.length + "-ctr", kb, ctrCounter2);
550
- return Buffer.concat([d3.update(buf), d3.final()]);
553
+ return guard.secret.cipherFinish(d3, buf, WebCryptoError, "webcrypto/operation", "the recovered AES-CTR plaintext");
551
554
  });
552
555
  }, "decrypt"));
553
556
  }
@@ -874,7 +877,7 @@ SubtleCrypto.prototype.wrapKey = async function wrapKey(format, key, wrappingKey
874
877
  try {
875
878
  return _withSecretBytes(wrappingKey, function (wkBytes) {
876
879
  var c = nodeCrypto.createCipheriv("aes" + wrappingKey.algorithm.length + "-wrap", wkBytes, Buffer.from("A6A6A6A6A6A6A6A6", "hex"));
877
- return _toArrayBuffer(Buffer.concat([c.update(bytes), c.final()]));
880
+ return _toArrayBuffer(guard.secret.cipherFinish(c, bytes, WebCryptoError, "webcrypto/operation", "an AES-KW wrap intermediate"));
878
881
  });
879
882
  } catch (e) { throw new WebCryptoError("webcrypto/operation", "wrapKey: AES-KW key wrap failed", e); }
880
883
  }
@@ -919,7 +922,7 @@ SubtleCrypto.prototype.unwrapKey = async function unwrapKey(format, wrappedKey,
919
922
  try {
920
923
  bytes = _withSecretBytes(unwrappingKey, function (kwBytes) {
921
924
  var d = nodeCrypto.createDecipheriv("aes" + unwrappingKey.algorithm.length + "-wrap", kwBytes, Buffer.from("A6A6A6A6A6A6A6A6", "hex"));
922
- return Buffer.concat([d.update(wrapped), d.final()]);
925
+ return guard.secret.cipherFinish(d, wrapped, WebCryptoError, "webcrypto/operation", "the unwrapped key material");
923
926
  });
924
927
  } catch (e) { throw new WebCryptoError("webcrypto/operation", "unwrapKey: AES-KW key unwrap failed (integrity or length)", e); }
925
928
  } else {
package/lib/x509-sign.js CHANGED
@@ -6,6 +6,7 @@
6
6
  * @module pki.x509
7
7
  * @nav Signing
8
8
  * @title Certificates
9
+ * @fullname X.509 certificates: parse, sign and inspect
9
10
  * @intro The X.509 certificate-issuance producing side. `pki.x509.sign` builds a `TBSCertificate`,
10
11
  * signs it, and emits a `Certificate` (RFC 5280 sec. 4) that `pki.schema.x509.parse`,
11
12
  * `pki.path.validate`, and OpenSSL all accept, self-signed or CA-signed, over any signature
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/pki",
3
- "version": "0.5.15",
3
+ "version": "0.5.17",
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:75f14a7c-3b79-4cbc-8db6-fb4987becceb",
5
+ "serialNumber": "urn:uuid:b8b3d2cf-001e-4a5b-8644-a627ed3ce3c5",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-08-19T13:58:32.723Z",
8
+ "timestamp": "2026-08-20T14:50:41.972Z",
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.5.15",
22
+ "bom-ref": "@blamejs/pki@0.5.17",
23
23
  "type": "application",
24
24
  "name": "pki",
25
- "version": "0.5.15",
25
+ "version": "0.5.17",
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.5.15",
29
+ "purl": "pkg:npm/%40blamejs/pki@0.5.17",
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.5.15",
57
+ "ref": "@blamejs/pki@0.5.17",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]