@blamejs/pki 0.6.13 → 0.6.15

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 (114) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/README.md +1 -1
  3. package/index.js +1 -1
  4. package/lib/acme.js +59 -815
  5. package/lib/asn1-der.js +7 -245
  6. package/lib/attrcert-sign.js +12 -260
  7. package/lib/attrcert-verify.js +1 -7
  8. package/lib/byte-reader.js +0 -8
  9. package/lib/byte-writer.js +0 -11
  10. package/lib/cbor-det.js +0 -54
  11. package/lib/cmc-build.js +15 -357
  12. package/lib/cmc-verify.js +6 -239
  13. package/lib/cmp-build.js +41 -317
  14. package/lib/cmp-session.js +73 -760
  15. package/lib/cmp-verify.js +32 -488
  16. package/lib/cms-compress.js +3 -32
  17. package/lib/cms-decrypt.js +29 -294
  18. package/lib/cms-encrypt.js +14 -234
  19. package/lib/cms-sign.js +29 -324
  20. package/lib/cms-verify.js +9 -500
  21. package/lib/composite-kem.js +2 -102
  22. package/lib/composite-sig.js +1 -77
  23. package/lib/constants.js +0 -249
  24. package/lib/crl-sign.js +9 -237
  25. package/lib/crl-verify.js +1 -10
  26. package/lib/crmf-sign.js +19 -198
  27. package/lib/crmf-verify.js +1 -12
  28. package/lib/csr-sign.js +3 -34
  29. package/lib/csr-verify.js +1 -12
  30. package/lib/ct.js +28 -253
  31. package/lib/edwards-point.js +10 -44
  32. package/lib/est.js +28 -764
  33. package/lib/framework-error.js +4 -182
  34. package/lib/guard-all.js +1 -58
  35. package/lib/guard-async.js +1 -36
  36. package/lib/guard-bytes.js +6 -650
  37. package/lib/guard-compress.js +5 -102
  38. package/lib/guard-crypto.js +1 -40
  39. package/lib/guard-der.js +2 -93
  40. package/lib/guard-encoding.js +2 -60
  41. package/lib/guard-header.js +1 -36
  42. package/lib/guard-identifier.js +7 -531
  43. package/lib/guard-intrinsic.js +1 -150
  44. package/lib/guard-json.js +10 -73
  45. package/lib/guard-limits.js +1 -77
  46. package/lib/guard-list.js +1 -49
  47. package/lib/guard-name.js +3 -209
  48. package/lib/guard-parsed.js +7 -335
  49. package/lib/guard-range.js +1 -71
  50. package/lib/guard-secret.js +1 -102
  51. package/lib/guard-text.js +1 -64
  52. package/lib/guard-time.js +1 -97
  53. package/lib/hpke.js +3 -105
  54. package/lib/http-digest.js +14 -142
  55. package/lib/http-retry-after.js +1 -47
  56. package/lib/http-transport.js +21 -131
  57. package/lib/inspect.js +30 -284
  58. package/lib/ip-utils.js +6 -47
  59. package/lib/jose.js +7 -142
  60. package/lib/key.js +2 -67
  61. package/lib/lint.js +9 -194
  62. package/lib/merkle.js +1 -27
  63. package/lib/mime.js +9 -82
  64. package/lib/ocsp-request-verify.js +1 -14
  65. package/lib/ocsp-verify.js +1 -82
  66. package/lib/ocsp.js +11 -169
  67. package/lib/oid.js +5 -337
  68. package/lib/path-validate.js +103 -1492
  69. package/lib/pbes2.js +2 -89
  70. package/lib/pkcs12-build.js +31 -379
  71. package/lib/pki-build.js +64 -290
  72. package/lib/rc2.js +2 -18
  73. package/lib/rfc3339.js +4 -27
  74. package/lib/scep.js +59 -233
  75. package/lib/schema-all.js +0 -122
  76. package/lib/schema-attrcert.js +4 -146
  77. package/lib/schema-c509.js +153 -802
  78. package/lib/schema-cmc.js +5 -220
  79. package/lib/schema-cmp.js +2 -223
  80. package/lib/schema-cms.js +13 -427
  81. package/lib/schema-crl.js +5 -68
  82. package/lib/schema-crmf.js +6 -167
  83. package/lib/schema-csr.js +1 -54
  84. package/lib/schema-csrattrs.js +2 -87
  85. package/lib/schema-engine.js +0 -152
  86. package/lib/schema-ocsp.js +0 -117
  87. package/lib/schema-pkcs12.js +2 -93
  88. package/lib/schema-pkcs8.js +3 -35
  89. package/lib/schema-pkix.js +24 -655
  90. package/lib/schema-smime.js +0 -54
  91. package/lib/schema-tsp.js +3 -99
  92. package/lib/schema-x509.js +4 -63
  93. package/lib/shbs.js +11 -95
  94. package/lib/sign-scheme.js +2 -97
  95. package/lib/sigstore.js +9 -169
  96. package/lib/sleep.js +2 -8
  97. package/lib/smime.js +15 -408
  98. package/lib/tls-cert-compress.js +0 -62
  99. package/lib/trust.js +19 -185
  100. package/lib/tsp-sign.js +16 -270
  101. package/lib/validator-all.js +1 -30
  102. package/lib/validator-attcert.js +2 -46
  103. package/lib/validator-cose.js +2 -153
  104. package/lib/validator-keydesc.js +1 -37
  105. package/lib/validator-sig.js +1 -46
  106. package/lib/validator-tls.js +1 -88
  107. package/lib/validator-tpm.js +19 -122
  108. package/lib/verify-seam.js +1 -24
  109. package/lib/webauthn-mds.js +3 -400
  110. package/lib/webauthn.js +12 -833
  111. package/lib/webcrypto.js +5 -485
  112. package/lib/x509-sign.js +4 -108
  113. package/package.json +1 -1
  114. package/sbom.cdx.json +6 -6
package/lib/key.js CHANGED
@@ -18,16 +18,6 @@
18
18
  * @spec RFC 5958, RFC 8018, RFC 5280
19
19
  * @card Export / import PKCS#8 and SPKI keys and encrypt a private key under RFC 8018 PBES2.
20
20
  */
21
- //
22
- // PBES2 is composed from lib/pbes2.js (the single PBKDF2 + AES-CBC home shared with pki.cms), bound to the key
23
- // namespace through the `_err` error FACTORY + the "key" domain prefix so every reject keeps a key/* code.
24
- // A MAC-less PBES2-CBC decrypt is not a padding oracle (RFC 8018 sec. 8): every post-derivation failure
25
- // (a bad PKCS#7 pad, or a valid pad that does not re-parse as a PrivateKeyInfo) collapses into one uniform
26
- // key/decrypt-failed; the structural pre-derivation faults (a non-PBKDF2 KDF, a non-AES-CBC scheme, an
27
- // over-cap salt / iteration count, a wrong-length IV, a malformed parameter SEQUENCE) stay distinct and are
28
- // thrown before any key derivation, so they leak nothing password-dependent. Unencrypted export / import
29
- // and publicFromPrivate delegate to the WebCrypto / node key engines and re-serialize no
30
- // AlgorithmIdentifier. A re-encode is exactly where a params-absent-vs-NULL divergence sneaks back in.
31
21
 
32
22
  var nodeCrypto = require("crypto");
33
23
  var asn1 = require("./asn1-der");
@@ -44,23 +34,15 @@ var subtle = webcrypto.webcrypto.subtle;
44
34
  var KeyError = frameworkError.KeyError;
45
35
  var PemError = frameworkError.PemError;
46
36
  function O(n) { return oid.byName(n); }
47
- // The guard / pbes2 error convention: a (code, msg, cause) factory, never the KeyError class, because pbes2
48
- // and the guards invoke it as E(code, msg) with no `new` (a class there crashes on the error path).
49
37
  function _err(code, msg, cause) { return new KeyError(code, msg, cause); }
50
38
 
51
- // The operator-facing node cipher name <-> the AES-CBC registry name the PBES2 home speaks.
52
39
  var CIPHER_NAME = { "aes-128-cbc": "aes128-CBC", "aes-192-cbc": "aes192-CBC", "aes-256-cbc": "aes256-CBC" };
53
40
 
54
- // import inference: the algorithm OIDs that name exactly one WebCrypto algorithm and need no parameters --
55
- // the Edwards/Montgomery curves and the FIPS post-quantum ML-DSA, ML-KEM, and SLH-DSA (all signing-only or
56
- // agreement-only, so unambiguous). RSA (sign vs OAEP) and EC (ECDSA vs ECDH) are deliberately absent. Each
57
- // is ambiguous, so import fails closed there and asks for opts.algorithm.
58
41
  var INFER_ALG = {};
59
42
  ["Ed25519", "Ed448", "X25519", "X448"].forEach(function (n) { INFER_ALG[O(n)] = { name: n }; });
60
43
  [["id-ml-dsa-44", "ML-DSA-44"], ["id-ml-dsa-65", "ML-DSA-65"], ["id-ml-dsa-87", "ML-DSA-87"],
61
44
  ["id-ml-kem-512", "ML-KEM-512"], ["id-ml-kem-768", "ML-KEM-768"], ["id-ml-kem-1024", "ML-KEM-1024"]
62
45
  ].forEach(function (r) { INFER_ALG[O(r[0])] = { name: r[1] }; });
63
- // SLH-DSA (FIPS 205) -- all twelve parameter sets; the WebCrypto name is the OID name minus "id-", uppercased.
64
46
  ["sha2-128s", "sha2-128f", "sha2-192s", "sha2-192f", "sha2-256s", "sha2-256f",
65
47
  "shake-128s", "shake-128f", "shake-192s", "shake-192f", "shake-256s", "shake-256f"
66
48
  ].forEach(function (s) { INFER_ALG[O("id-slh-dsa-" + s)] = { name: ("SLH-DSA-" + s).toUpperCase() }; });
@@ -68,7 +50,6 @@ var INFER_ALG = {};
68
50
  function _isCryptoKey(x) { return webcrypto.isCryptoKeyLike(x); }
69
51
  function _algName(a) { return typeof a === "string" ? a : (a && a.name); }
70
52
 
71
- // A private-key input (CryptoKey | DER Buffer | 'PRIVATE KEY' PEM) -> the raw PKCS#8 PrivateKeyInfo DER.
72
53
  async function _toPrivateKeyDer(input) {
73
54
  if (_isCryptoKey(input)) {
74
55
  if (input.type !== "private") throw _err("key/bad-input", "a private CryptoKey is required (got a " + input.type + " key)");
@@ -107,9 +88,6 @@ async function _toPrivateKeyDer(input) {
107
88
  * var enc = await pki.key.encrypt(der, "s3cr3t", { pem: true });
108
89
  * var back = await pki.key.decrypt(enc, "s3cr3t");
109
90
  */
110
- // Every option pki.key.encrypt reads. `iterations` here answers a different question from
111
- // `maxIterations` on decrypt. This side chooses a PBKDF2 count, and that side caps the one it
112
- // will honor. Passing either name to the other verb is silently nothing, so both are refused.
113
91
  var _ENCRYPT_OPTS = { cipher: 1, iterations: 1, pem: 1, prf: 1, salt: 1 };
114
92
 
115
93
  async function encrypt(privateKey, password, opts) {
@@ -118,8 +96,6 @@ async function encrypt(privateKey, password, opts) {
118
96
  "pki.key.encrypt has an unknown option (the PBKDF2 count here is `iterations`; " +
119
97
  "`maxIterations` is the decrypt-side cap): ");
120
98
  var der = await _toPrivateKeyDer(privateKey);
121
- // RFC 5958 sec. 3: the plaintext is the DER PrivateKeyInfo -- validate it IS one (the strict parser
122
- // rejects BER / non-canonical / trailing bytes) before encrypting, so we never wrap opaque input.
123
99
  try { pkcs8.parse(der); } catch (e) { throw _err("key/bad-input", "the private key is not a well-formed PKCS#8 PrivateKeyInfo", e); }
124
100
  var cipherName = CIPHER_NAME[opts.cipher || "aes-256-cbc"];
125
101
  if (!cipherName) throw _err("key/bad-input", "unsupported cipher " + JSON.stringify(opts.cipher) + " (aes-128-cbc / aes-192-cbc / aes-256-cbc)");
@@ -129,11 +105,6 @@ async function encrypt(privateKey, password, opts) {
129
105
  var iterations = pbes2.assertIterations(opts.iterations == null ? 600000 : opts.iterations, _err, "key");
130
106
  var salt = opts.salt != null ? pbes2.assertSalt(guard.bytes.view(opts.salt, KeyError, "key/bad-input", "salt"), _err, "key") : nodeCrypto.randomBytes(16);
131
107
  var iv = nodeCrypto.randomBytes(16);
132
- // The derived key protects a PRIVATE KEY, so it is cleared as soon as the encryption is done. The
133
- // caller's password is untouched -- passwordBytes returns a supplied Buffer as-is.
134
- // A string / Uint8Array password is encoded into a buffer THIS toolkit allocated -- a credential
135
- // copy -- so it is cleared once the derivation has consumed it. A caller-supplied Buffer is
136
- // borrowed and left intact.
137
108
  var pwK = pbes2.passwordBytesOwned(password, _err, "key");
138
109
  var dk;
139
110
  try { dk = nodeCrypto.pbkdf2Sync(pwK.bytes, salt, iterations, keyBits / 8, prfNode); }
@@ -141,7 +112,7 @@ async function encrypt(privateKey, password, opts) {
141
112
  try {
142
113
  var ciphertext = pbes2.cbcEncrypt(dk, iv, der, keyBits, KeyError, "key/bad-input");
143
114
  var epki = b.sequence([pbes2.pbes2AlgId(salt, iterations, prf, cipherName, iv), b.octetString(ciphertext)]);
144
- pkcs8.parseEncrypted(epki); // self-check: the produced EncryptedPrivateKeyInfo re-parses
115
+ pkcs8.parseEncrypted(epki);
145
116
  return opts.pem ? pkcs8.pemEncode(epki, "ENCRYPTED PRIVATE KEY") : epki;
146
117
  } finally {
147
118
  guard.secret.zeroize(dk, KeyError, "key/bad-input", "the password-derived encryption key");
@@ -175,8 +146,6 @@ async function encrypt(privateKey, password, opts) {
175
146
  * var enc = await pki.key.encrypt(await pki.key.export(pair.privateKey), "s3cr3t", { pem: true });
176
147
  * var der = await pki.key.decrypt(enc, "s3cr3t");
177
148
  */
178
- // Every option pki.key.decrypt reads. `maxIterations` is the cap this side honors, not the
179
- // `iterations` the encrypt side chooses; see the note above _ENCRYPT_OPTS.
180
149
  var _DECRYPT_OPTS = { maxIterations: 1, pem: 1 };
181
150
 
182
151
  async function decrypt(encrypted, password, opts) {
@@ -184,9 +153,6 @@ async function decrypt(encrypted, password, opts) {
184
153
  guard.identifier.assertKnownKeys(opts, _DECRYPT_OPTS, _err, "key/bad-input",
185
154
  "pki.key.decrypt has an unknown option (the PBKDF2 cap here is `maxIterations`; " +
186
155
  "`iterations` is the encrypt-side count): ");
187
- // opts.maxIterations is a CALLER authoring bound (a downward-only cap): validate it config-time so an
188
- // invalid value is a key/bad-input, never a NaN that silently disables the DoS cap and never mis-coded as
189
- // a fault in the attacker-controlled input parameters.
190
156
  if (opts.maxIterations != null && (typeof opts.maxIterations !== "number" || !isFinite(opts.maxIterations) || opts.maxIterations < 1 || Math.floor(opts.maxIterations) !== opts.maxIterations)) {
191
157
  throw _err("key/bad-input", "maxIterations must be a positive integer");
192
158
  }
@@ -201,12 +167,6 @@ async function decrypt(encrypted, password, opts) {
201
167
  }
202
168
 
203
169
  function _decryptPbes2(encAlg, ciphertext, password, opts) {
204
- // The shared PBES2 decrypt home (strict params + the uniform decrypt-failed on a wrong key / bad pad). The
205
- // plaintext re-parse below is the MAC-less PBES2-CBC integrity check (RFC 8018 sec. 8): a bad pad and a
206
- // valid pad whose plaintext is not a PrivateKeyInfo are indistinguishable, and it MUST run (never skipped).
207
- // A string / Uint8Array password is encoded into a buffer THIS toolkit allocated -- a credential
208
- // copy -- so it is cleared once the derivation has consumed it. A caller-supplied Buffer is
209
- // borrowed and left intact.
210
170
  var pwD = pbes2.passwordBytesOwned(password, _err, "key");
211
171
  var plaintext;
212
172
  try { plaintext = pbes2.pbes2Decrypt(pwD.bytes, encAlg.parameters, ciphertext, opts, _err, "key"); }
@@ -242,16 +202,10 @@ function _decryptPbes2(encAlg, ciphertext, password, opts) {
242
202
  * var pair = await pki.key.generate("Ed25519");
243
203
  * var spkiPem = await pki.key.export(pair.publicKey, { format: "pem" });
244
204
  */
245
- // Every option pki.key.export reads. Adding one here is the only way to make it accepted.
246
205
  var _EXPORT_OPTS = { format: 1, label: 1 };
247
206
 
248
207
  async function export_(key, opts) {
249
208
  opts = guard.identifier.optionsObject(opts, _err, "key/bad-input", "pki.key.export options");
250
- // This verb does NOT encrypt, and `password` is the option a caller reaches for when they
251
- // think it does. Ignoring it writes the private key in the clear while the call site reads
252
- // as though it were protected. Every other unknown option here costs a setting that does
253
- // nothing; this one costs a plaintext key on disk. `pki.key.encrypt(key, password, opts)`
254
- // is the verb that encrypts; export serializes whatever it is handed.
255
209
  guard.identifier.assertKnownKeys(opts, _EXPORT_OPTS, _err, "key/bad-input",
256
210
  "pki.key.export has an unknown option. It serializes only. To protect a private key, call " +
257
211
  "pki.key.encrypt(key, password) first and export its result. The unknown option was: ");
@@ -291,7 +245,6 @@ async function export_(key, opts) {
291
245
  * var spki = await pki.key.export(pair.publicKey);
292
246
  * var pub = await pki.key.import(spki); // Ed25519 -- algorithm inferred
293
247
  */
294
- // Every option pki.key.import reads.
295
248
  var _IMPORT_OPTS = { algorithm: 1, extractable: 1, password: 1, usages: 1 };
296
249
 
297
250
  async function import_(input, opts) {
@@ -305,9 +258,6 @@ async function import_(input, opts) {
305
258
  }
306
259
  var algorithm = opts.algorithm != null ? opts.algorithm : _inferAlgorithm(detected);
307
260
  var isPublic = detected.format === "spki";
308
- // Checked at this verb's own door rather than left for the engine to discover: a non-list reached
309
- // the CryptoKey copy, where `Array.prototype.slice` on a number yields an empty list, so the key
310
- // was minted permitted for NOTHING instead of the call naming the argument that was wrong.
311
261
  if (opts.usages != null && !Array.isArray(opts.usages)) throw _err("key/bad-input", "opts.usages must be an array of key-usage strings");
312
262
  var usages = opts.usages || _importUsages(_algName(algorithm), isPublic);
313
263
  var extractable = opts.extractable != null ? opts.extractable : false;
@@ -339,8 +289,6 @@ async function import_(input, opts) {
339
289
  * var pair = await pki.key.generate("Ed25519");
340
290
  * var pkcs8 = await pki.key.export(pair.privateKey);
341
291
  */
342
- // Every option pki.key.generate reads. `extractable` follows the WebCrypto spelling; a
343
- // misspelling would leave a key extractable by default, which is the permissive direction.
344
292
  var _GENERATE_OPTS = { extractable: 1, usages: 1 };
345
293
 
346
294
  async function generate(algorithm, opts) {
@@ -348,7 +296,6 @@ async function generate(algorithm, opts) {
348
296
  guard.identifier.assertKnownKeys(opts, _GENERATE_OPTS, _err, "key/bad-input",
349
297
  "pki.key.generate has an unknown option (the WebCrypto spelling is `extractable`): ");
350
298
  var extractable = opts.extractable != null ? opts.extractable : true;
351
- // The same door as import: a non-list is named here rather than becoming an empty usage set.
352
299
  if (opts.usages != null && !Array.isArray(opts.usages)) throw _err("key/bad-input", "opts.usages must be an array of key-usage strings");
353
300
  var usages = opts.usages || _generateUsages(_algName(algorithm));
354
301
  var pair;
@@ -377,7 +324,6 @@ async function generate(algorithm, opts) {
377
324
  * var pair = await pki.key.generate("Ed25519");
378
325
  * var spki = await pki.key.publicFromPrivate(await pki.key.export(pair.privateKey));
379
326
  */
380
- // Every option pki.key.publicFromPrivate reads.
381
327
  var _PUBLIC_FROM_PRIVATE_OPTS = { pem: 1 };
382
328
 
383
329
  async function publicFromPrivate(privateKey, opts) {
@@ -393,11 +339,7 @@ async function publicFromPrivate(privateKey, opts) {
393
339
  return opts.pem ? pkix.pemEncode(spki, "PUBLIC KEY", PemError) : spki;
394
340
  }
395
341
 
396
- // ---- import helpers --------------------------------------------------------
397
342
 
398
- // Auto-detect a key input by structure (works for a raw DER Buffer or a label-agnostic PEM): a PrivateKeyInfo
399
- // leads with an INTEGER version; an SPKI is SEQ{ algId, BIT STRING }; an EncryptedPrivateKeyInfo is
400
- // SEQ{ algId, OCTET STRING }. Returns { format, der }.
401
343
  function _detectKeyInput(input) {
402
344
  var der = pkix.coerceToDer(input, { pemLabel: null, PemError: PemError, ErrorClass: KeyError, prefix: "key" });
403
345
  var root;
@@ -409,13 +351,9 @@ function _detectKeyInput(input) {
409
351
  throw _err("key/bad-input", "the input is not a recognized PKCS#8, SPKI, or EncryptedPrivateKeyInfo");
410
352
  }
411
353
 
412
- // The AlgorithmIdentifier OID from a detected key: privateKeyAlgorithm (PKCS#8) or algorithm (SPKI).
413
354
  function _readAlgOid(detected) {
414
355
  try {
415
356
  if (detected.format === "pkcs8") return pkcs8.parse(detected.der).privateKeyAlgorithm.oid;
416
- // SPKI: SEQUENCE { algorithm AlgorithmIdentifier SEQUENCE { OID, ... }, subjectPublicKey BIT STRING }.
417
- // Validate the algorithm SEQUENCE shape before dereferencing its OID -- a malformed SPKI must be a typed
418
- // reject, never a raw children[] fault.
419
357
  var alg = asn1.decode(detected.der).children[0];
420
358
  if (!alg || alg.tagClass !== "universal" || alg.tagNumber !== asn1.TAGS.SEQUENCE || !alg.children || !alg.children.length) {
421
359
  throw _err("key/bad-input", "malformed SubjectPublicKeyInfo algorithm identifier");
@@ -435,15 +373,12 @@ function _inferAlgorithm(detected) {
435
373
  " (RSA and EC are ambiguous between signing and key agreement -- pass opts.algorithm)");
436
374
  }
437
375
 
438
- // Default key usages by algorithm CLASS. The name is normalized to upper case first: WebCrypto matches
439
- // algorithm names case-insensitively, so a caller passing { name: "x25519" } / "ml-kem-768" must land on the
440
- // same usage class as the canonical spelling rather than fall through to the signing default.
441
376
  function _importUsages(name, isPublic) {
442
377
  var n = String(name == null ? "" : name).toUpperCase();
443
378
  if (n === "X25519" || n === "X448" || n === "ECDH") return isPublic ? [] : ["deriveBits", "deriveKey"];
444
379
  if (n.indexOf("ML-KEM") === 0) return isPublic ? ["encapsulateBits"] : ["decapsulateBits"];
445
380
  if (n === "RSA-OAEP") return isPublic ? ["encrypt"] : ["decrypt"];
446
- return isPublic ? ["verify"] : ["sign"]; // signing default (Ed / ECDSA / RSA-* sign / ML-DSA / SLH-DSA)
381
+ return isPublic ? ["verify"] : ["sign"];
447
382
  }
448
383
 
449
384
  function _generateUsages(name) {