@blamejs/pki 0.8.30 → 0.8.48

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 (87) hide show
  1. package/CHANGELOG.md +411 -0
  2. package/README.md +67 -16
  3. package/bin/pki.js +716 -30
  4. package/examples/issue-a-certificate.js +90 -0
  5. package/examples/post-quantum.js +66 -0
  6. package/examples/read-a-certificate.js +56 -0
  7. package/examples/revoke-and-check.js +86 -0
  8. package/examples/sign-and-verify-cms.js +63 -0
  9. package/index.js +18 -5
  10. package/lib/acme.js +223 -74
  11. package/lib/alt-sig.js +441 -0
  12. package/lib/asn1-der.js +48 -25
  13. package/lib/attrcert-sign.js +27 -19
  14. package/lib/cbor-det.js +24 -14
  15. package/lib/cmc-build.js +9 -2
  16. package/lib/cmc-verify.js +14 -3
  17. package/lib/cmp-build.js +17 -5
  18. package/lib/cmp-session.js +24 -11
  19. package/lib/cmp-verify.js +28 -19
  20. package/lib/cms-compress.js +3 -1
  21. package/lib/cms-decrypt.js +102 -7
  22. package/lib/cms-encrypt.js +59 -7
  23. package/lib/cms-sign.js +31 -9
  24. package/lib/cms-verify.js +22 -18
  25. package/lib/composite-kem.js +30 -18
  26. package/lib/composite-sig.js +29 -14
  27. package/lib/constants.js +28 -0
  28. package/lib/crl-sign.js +111 -19
  29. package/lib/crmf-sign.js +167 -15
  30. package/lib/csr-sign.js +117 -6
  31. package/lib/ct.js +939 -48
  32. package/lib/est.js +29 -16
  33. package/lib/framework-error.js +12 -0
  34. package/lib/guard-async.js +1 -1
  35. package/lib/guard-bytes.js +103 -20
  36. package/lib/guard-crypto.js +205 -3
  37. package/lib/guard-encoding.js +6 -1
  38. package/lib/guard-identifier.js +55 -8
  39. package/lib/guard-intrinsic.js +43 -0
  40. package/lib/guard-json.js +122 -9
  41. package/lib/guard-limits.js +12 -1
  42. package/lib/guard-list.js +63 -8
  43. package/lib/guard-name.js +167 -20
  44. package/lib/guard-range.js +43 -5
  45. package/lib/guard-text.js +41 -0
  46. package/lib/guard-verdict.js +39 -8
  47. package/lib/hpke.js +688 -42
  48. package/lib/http-transport.js +156 -4
  49. package/lib/identity-match.js +4 -4
  50. package/lib/inspect.js +448 -2
  51. package/lib/jose.js +93 -25
  52. package/lib/key.js +18 -13
  53. package/lib/lint.js +165 -1
  54. package/lib/merkle.js +233 -27
  55. package/lib/ocsp.js +27 -14
  56. package/lib/oid.js +14 -4
  57. package/lib/path-validate.js +143 -61
  58. package/lib/pkcs12-build.js +7 -2
  59. package/lib/pki-build.js +31 -7
  60. package/lib/possession.js +575 -0
  61. package/lib/related-cert.js +708 -0
  62. package/lib/revocation-fetch.js +6 -6
  63. package/lib/scep.js +15 -5
  64. package/lib/schema-c509.js +5 -2
  65. package/lib/schema-cmc.js +1 -1
  66. package/lib/schema-cmp.js +8 -8
  67. package/lib/schema-cms.js +2 -15
  68. package/lib/schema-csr.js +69 -0
  69. package/lib/schema-engine.js +10 -3
  70. package/lib/schema-pkix.js +267 -4
  71. package/lib/schema-tsp.js +7 -6
  72. package/lib/sign-scheme.js +22 -9
  73. package/lib/sigstore.js +684 -168
  74. package/lib/sleep.js +8 -1
  75. package/lib/smime.js +22 -1
  76. package/lib/tlog.js +1235 -0
  77. package/lib/tls-cert-compress.js +25 -8
  78. package/lib/tsp-sign.js +120 -35
  79. package/lib/tuf.js +1395 -0
  80. package/lib/validator-tpm.js +4 -1
  81. package/lib/verify-seam.js +7 -1
  82. package/lib/webauthn-mds.js +41 -4
  83. package/lib/webauthn.js +43 -25
  84. package/lib/webcrypto.js +48 -46
  85. package/lib/x509-sign.js +225 -19
  86. package/package.json +2 -1
  87. package/sbom.cdx.json +6 -6
package/lib/jose.js CHANGED
@@ -39,6 +39,10 @@
39
39
  var constants = require("./constants");
40
40
  var wcEngine = require("./webcrypto");
41
41
  var webcrypto = wcEngine.webcrypto;
42
+ /** @internal The verify operation, captured at load. The object was held and its METHOD fetched per
43
+ * call, which leaves it replaceable: one answering true accepts a JWS whose signer never signed it,
44
+ * including on the embedded-key route this module pins against its thumbprint. */
45
+ var _subtleVerify = require("./guard-intrinsic").uncurry(webcrypto.subtle.verify);
42
46
  var frameworkError = require("./framework-error");
43
47
  var guard = require("./guard-all");
44
48
  var edwardsPoint = require("./edwards-point");
@@ -121,11 +125,11 @@ function b64uDecode(text) {
121
125
  * pki.jose.parseJson('{"a":1}'); // -> { a: 1 }
122
126
  */
123
127
  function parseJson(input) {
124
- return guard.json.parse(input, E, {
128
+ return guard.verdict.shield(guard.json.parse(input, E, {
125
129
  maxBytes: LIMITS.JSON_MAX_BYTES, maxDepth: LIMITS.JSON_MAX_DEPTH,
126
130
  badJson: "jose/bad-json", tooDeep: "jose/too-deep", duplicateMember: "jose/duplicate-member",
127
131
  tooLarge: "jose/too-large", badInput: "jose/bad-input", label: "the JSON document",
128
- });
132
+ }));
129
133
  }
130
134
 
131
135
 
@@ -329,9 +333,18 @@ async function verify(jws, opts) {
329
333
  if (!jws || typeof jws !== "object" || _isArray(jws)) throw E("jose/bad-jws", "a flattened JWS object is required");
330
334
  if (_hasOwn(jws, "signatures")) throw E("jose/bad-jws", "the multi-signature signatures member is forbidden");
331
335
  if (_hasOwn(jws, "header")) throw E("jose/bad-jws", "the unprotected header member is forbidden");
332
- if (typeof jws.protected !== "string" || typeof jws.signature !== "string") throw E("jose/bad-jws", "protected and signature must be base64url strings");
333
- if (typeof jws.payload !== "string") throw E("jose/bad-jws", "payload must be a base64url string (never detached)");
334
- var header = parseJson(b64uDecode(jws.protected));
336
+ /** @internal The three members are taken ONCE, for the reason `opts` is above: the JWS is the caller's
337
+ * object, and a member reached through an accessor answers every read separately. `protected` decides
338
+ * both the header the profile rules are applied to and the signing input the signature is checked over,
339
+ * and the thumbprint comparison awaits between those two uses, so reading it twice would let the rules
340
+ * be applied to one message and the signature checked over another. Everything below reads these
341
+ * locals. */
342
+ var protectedB64 = jws.protected;
343
+ var signatureB64 = jws.signature;
344
+ var payloadB64 = jws.payload;
345
+ if (typeof protectedB64 !== "string" || typeof signatureB64 !== "string") throw E("jose/bad-jws", "protected and signature must be base64url strings");
346
+ if (typeof payloadB64 !== "string") throw E("jose/bad-jws", "payload must be a base64url string (never detached)");
347
+ var header = parseJson(b64uDecode(protectedB64));
335
348
  var profileName = opts.profile || "acme-outer";
336
349
  var checked = _checkHeader(header, profileName);
337
350
  /** @internal The embedded jwk is the signer's PUBLIC key (RFC 7515 sec. 4.1.3). One carrying
@@ -342,14 +355,32 @@ async function verify(jws, opts) {
342
355
  if (opts.key !== undefined && (typeof opts.key !== "object" || opts.key === null || _isArray(opts.key))) {
343
356
  throw E("jose/bad-key", "opts.key was supplied but is not a JWK object, so the key this message must be signed under cannot be established");
344
357
  }
345
- var jwk = header.jwk || opts.key;
358
+ /** @internal The caller's key is COPIED here, before ANY member of it is read, and everything below
359
+ * reads the copy: the thumbprint comparison against the embedded jwk, the key-type check, the Edwards
360
+ * low-order point gate, the signature-width calculation, the import parameters and the algorithm
361
+ * selector. Its members were read many times over, `crv` eight times and `kty` nine on one successful
362
+ * verify, and an accessor answers each read separately.
363
+ *
364
+ * The FIRST read is the one that decides which curve the point gate validates against
365
+ * (`jwk.crv === "Ed25519" ? 6 : 7`), so a `crv` answering `Ed448` once and `Ed25519` afterwards had the
366
+ * point gated under the wrong curve's parameters while an Ed25519 key was imported and verified. That
367
+ * gate is the whole defense against a low-order key verifying a FORGED signature. Copying before the
368
+ * thumbprint comparison rather than after it matters for the same reason: otherwise a key could
369
+ * thumbprint as the embedded one and be copied as a different one.
370
+ *
371
+ * `snapshotDeep` refuses an accessor-backed member rather than reading it once, which is the stronger
372
+ * answer for a value that selects an algorithm. The embedded jwk comes from the parsed header and is
373
+ * this module's own object, so it needs no copy. */
374
+ var suppliedKey = opts.key === undefined ? undefined
375
+ : guard.bytes.snapshotDeep(opts.key, JoseError, "jose/bad-key", "opts.key");
376
+ var jwk = header.jwk || suppliedKey;
346
377
  if (!jwk) throw E("jose/bad-key", "a verification key is required (opts.key) when the profile does not embed a jwk");
347
378
  var keySource = "embedded-jwk";
348
- if (opts.key) {
379
+ if (suppliedKey) {
349
380
  keySource = "opts.key";
350
381
  if (header.jwk) {
351
382
  var embeddedTp, suppliedTp;
352
- try { embeddedTp = await thumbprint(header.jwk); suppliedTp = await thumbprint(opts.key); }
383
+ try { embeddedTp = await thumbprint(header.jwk); suppliedTp = await thumbprint(suppliedKey); }
353
384
  catch (e) { throw E("jose/bad-key", "the embedded jwk and opts.key could not be compared as RFC 7638 thumbprints", e); }
354
385
  if (embeddedTp !== suppliedTp) {
355
386
  throw E("jose/key-mismatch",
@@ -358,19 +389,19 @@ async function verify(jws, opts) {
358
389
  ", supplied " + suppliedTp + ")");
359
390
  }
360
391
  }
361
- jwk = opts.key;
392
+ jwk = suppliedKey;
362
393
  }
363
394
  _assertKeyType(checked.algRow, jwk);
364
- var sig = b64uDecode(jws.signature);
395
+ var sig = b64uDecode(signatureB64);
365
396
  var want = _expectedSigBytes(checked.algRow, jwk);
366
397
  if (want != null && sig.length !== want) throw E("jose/bad-signature", "the signature length " + sig.length + " is not the " + want + " bytes " + checked.alg + " requires");
367
- var signingInput = _bufferFrom(jws.protected + "." + jws.payload, "ascii");
398
+ var signingInput = _bufferFrom(protectedB64 + "." + payloadB64, "ascii");
368
399
  var key;
369
400
  try { key = await webcrypto.subtle.importKey("jwk", jwk, _importParams(checked.algRow, jwk), false, ["verify"]); }
370
401
  catch (e) { throw E("jose/bad-key", "the JWK could not be imported for verification", e); }
371
- var ok = await webcrypto.subtle.verify(_cryptoAlg(checked.algRow, jwk), key, sig, signingInput);
402
+ var ok = await _subtleVerify(webcrypto.subtle, _cryptoAlg(checked.algRow, jwk), key, sig, signingInput);
372
403
  if (!ok) throw E("jose/verify-failed", "the JWS signature did not verify");
373
- return { header: header, payload: b64uDecode(jws.payload), keySource: keySource };
404
+ return { header: header, payload: b64uDecode(payloadB64), keySource: keySource };
374
405
  }
375
406
 
376
407
 
@@ -409,9 +440,23 @@ async function verify(jws, opts) {
409
440
  */
410
441
  var _SIGN_KEYS = intrinsic.assign(intrinsic.create(null), { protected: 1, payload: 1, key: 1, jwk: 1, profile: 1 });
411
442
  var _SIGN_KEYS_EMBEDDED_JWK = intrinsic.assign(intrinsic.create(null), { protected: 1, payload: 1, key: 1, profile: 1 });
443
+
444
+ /** @internal The payload, copied, taken before anything else on the caller's object is read. Every later
445
+ * read of that object is caller code when the field is an accessor, and the protected header is read
446
+ * FIRST and SERIALIZED, which reaches the header object, each of its fields and any `toJSON` on it. Over
447
+ * a view of the payload any of those would replace what gets signed, after the verb had accepted the
448
+ * payload it was handed, so the copy comes before all of them rather than before whichever one is in
449
+ * view. A payload that is not a Buffer is carried through untouched and refused where it always was, so
450
+ * no diagnostic moves ahead of the header's own. */
451
+ function _fixedPayload(supplied) {
452
+ if (!_isBuffer(supplied)) return supplied;
453
+ return guard.bytes.snapshot(supplied, JoseError, "jose/bad-input", "payload");
454
+ }
455
+
412
456
  async function sign(opts) {
413
457
  opts = opts || {};
414
458
  guard.identifier.assertKnownKeys(opts, _SIGN_KEYS, E, "jose/bad-input", "unknown pki.jose.sign option ");
459
+ var fixedPayload = _fixedPayload(opts.payload);
415
460
  var headerJson;
416
461
  try { headerJson = _stringify(opts.protected); }
417
462
  catch (e) { throw E("jose/bad-header", "the protected header must be JSON-serializable", e); }
@@ -424,13 +469,25 @@ async function sign(opts) {
424
469
  (k === "jwk" ? "; the protected header already embeds the public JWK" : "");
425
470
  });
426
471
  }
427
- var payload = opts.payload;
472
+ var payload = fixedPayload;
428
473
  if (!_isBuffer(payload)) throw E("jose/bad-input", "payload must be a Buffer (empty Buffer for POST-as-GET)");
429
- payload = guard.bytes.view(payload, JoseError, "jose/bad-input", "payload");
430
- if (!wcEngine.isCryptoKeyLike(opts.key)) throw E("jose/bad-input", "a private or secret CryptoKey (opts.key) is required");
474
+ /** @internal ONE read of the caller's signing key, taken before anything inspects it, and every use
475
+ * below reads this binding. It was read four times: the key-like test, twice by the hash-to-algorithm
476
+ * check, and once more to sign with. An accessor answers each read separately, so the check could
477
+ * approve a SHA-256 key while the signature was produced by a SHA-512 one, and the JWS emitted then
478
+ * DECLARES an algorithm its signature was not made under, which is the algorithm confusion the header
479
+ * registry exists to close before any crypto call.
480
+ *
481
+ * The RSA arms are where it showed: the registry's fixed signature widths catch the HMAC and EC arms,
482
+ * and two 2048-bit RSA keys produce the same 256-byte signature, so the width pin below cannot see a
483
+ * substitution between them. */
484
+ var suppliedKey = opts.key;
485
+ if (!wcEngine.isCryptoKeyLike(suppliedKey)) throw E("jose/bad-input", "a private or secret CryptoKey (opts.key) is required");
431
486
  var checked = _checkHeader(header, opts.profile || "acme-outer");
487
+ var suppliedAlg = suppliedKey.algorithm;
488
+ var suppliedHash = suppliedAlg && suppliedAlg.hash;
432
489
  if ((checked.algRow.kty === "RSA" || checked.algRow.kty === "oct") &&
433
- (!opts.key.algorithm.hash || opts.key.algorithm.hash.name !== checked.algRow.hash)) {
490
+ (!suppliedHash || suppliedHash.name !== checked.algRow.hash)) {
434
491
  throw E("jose/bad-key", "the signing key's hash does not match the algorithm " + checked.alg);
435
492
  }
436
493
  if (header.jwk) { _assertKeyType(checked.algRow, header.jwk); assertPublicJwk(header.jwk); }
@@ -438,15 +495,18 @@ async function sign(opts) {
438
495
  var payloadB64 = payload.length === 0 ? "" : b64uEncode(payload);
439
496
  var signingInput = _bufferFrom(protectedB64 + "." + payloadB64, "ascii");
440
497
  var jwk = header.jwk || opts.jwk || {};
441
- var signKey = await wcEngine.adoptKey(opts.key, null, ["sign"], E, "jose/bad-input");
498
+ /** @internal `suppliedKey` is the reading taken at the top, so the key adopted for signing, the key the
499
+ * hash-to-algorithm check approved and the key the expected signature width is computed from are all
500
+ * one key. */
501
+ var signKey = await wcEngine.adoptKey(suppliedKey, null, ["sign"], E, "jose/bad-input");
442
502
  var sigBuf = _bufferFrom(await webcrypto.subtle.sign(_cryptoAlg(checked.algRow, jwk, signKey), signKey, signingInput));
443
- var want = _expectedSigBytes(checked.algRow, jwk, opts.key);
503
+ var want = _expectedSigBytes(checked.algRow, jwk, suppliedKey);
444
504
  if (want != null && sigBuf.length !== want) throw E("jose/bad-key", "the signing key produced a " + sigBuf.length + "-byte signature but " + checked.alg + " requires " + want + " (the key does not match the algorithm)");
445
505
  if (header.jwk) {
446
506
  var pubKey;
447
507
  try { pubKey = await webcrypto.subtle.importKey("jwk", header.jwk, _importParams(checked.algRow, header.jwk), false, ["verify"]); }
448
508
  catch (e) { throw E("jose/bad-key", "the embedded jwk could not be imported to confirm it matches the signing key", e); }
449
- if (!(await webcrypto.subtle.verify(_cryptoAlg(checked.algRow, header.jwk), pubKey, sigBuf, signingInput))) {
509
+ if (!(await _subtleVerify(webcrypto.subtle, _cryptoAlg(checked.algRow, header.jwk), pubKey, sigBuf, signingInput))) {
450
510
  throw E("jose/bad-key", "the embedded jwk does not match the signing key (it cannot verify the produced signature)");
451
511
  }
452
512
  }
@@ -485,14 +545,22 @@ var THUMBPRINT_MEMBERS = intrinsic.assign(intrinsic.create(null), {
485
545
  * await pki.jose.thumbprint(accountJwk); // -> "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs"
486
546
  */
487
547
  async function thumbprint(jwk) {
488
- if (!jwk || typeof jwk !== "object" || typeof jwk.kty !== "string") throw E("jose/bad-key", "a JWK object with a kty is required");
489
- var members = THUMBPRINT_MEMBERS[jwk.kty];
490
- if (!members) throw E("jose/bad-key", "unsupported JWK key type " + _stringify(jwk.kty));
548
+ if (!jwk || typeof jwk !== "object") throw E("jose/bad-key", "a JWK object with a kty is required");
549
+ /** @internal COPIED before any member is read, because what this returns is an IDENTITY. Each member
550
+ * was read twice, the type check and then the value put into the hash, and `kty` four times; an
551
+ * accessor answers each read separately, so a member could be checked as one value and HASHED as
552
+ * another. The thumbprint is what RFC 7638 identifies a key by: `pki.jose.verify` compares an embedded
553
+ * jwk against `opts.key` with it, and ACME uses it as the account identity in a key authorization. A
554
+ * thumbprint over a value the checks never approved names a key that was never the one validated. */
555
+ var copied = guard.bytes.snapshotDeep(jwk, JoseError, "jose/bad-key", "the JWK");
556
+ if (typeof copied.kty !== "string") throw E("jose/bad-key", "a JWK object with a kty is required");
557
+ var members = THUMBPRINT_MEMBERS[copied.kty];
558
+ if (!members) throw E("jose/bad-key", "unsupported JWK key type " + _stringify(copied.kty));
491
559
  var parts = [];
492
560
  for (var i = 0; i < members.length; i++) {
493
561
  var m = members[i];
494
- if (typeof jwk[m] !== "string") throw E("jose/bad-key", "the JWK is missing the required member " + _stringify(m));
495
- _push(parts, _stringify(m) + ":" + _stringify(jwk[m]));
562
+ if (typeof copied[m] !== "string") throw E("jose/bad-key", "the JWK is missing the required member " + _stringify(m));
563
+ _push(parts, _stringify(m) + ":" + _stringify(copied[m]));
496
564
  }
497
565
  var canonical = "{" + _join(parts, ",") + "}";
498
566
  var digest = _bufferFrom(await webcrypto.subtle.digest("SHA-256", _bufferFrom(canonical, "utf8")));
package/lib/key.js CHANGED
@@ -30,6 +30,11 @@ var schemaSec1 = require("./schema-sec1");
30
30
  var pkix = require("./schema-pkix");
31
31
  var schema = require("./schema-engine");
32
32
  var webcrypto = require("./webcrypto");
33
+ /** @internal The key-material exporter, captured at load. The signature probe that decides whether two
34
+ * halves are a pair imports and signs with what this returns, so a replacement exporting a different
35
+ * private key makes a mismatched pair report as corresponding: the probe then signs and verifies with
36
+ * a pair that does correspond, and neither half is the one the caller supplied. */
37
+ var _exportAnyKey = webcrypto.exportAnyKey;
33
38
  var compositeKem = require("./composite-kem");
34
39
  var frameworkError = require("./framework-error");
35
40
  var guard = require("./guard-all");
@@ -123,7 +128,7 @@ function _sec1ToPkcs8(der, curveOid) {
123
128
  async function _toPrivateKeyDer(input) {
124
129
  if (_isCryptoKey(input)) {
125
130
  if (input.type !== "private") throw _err("key/bad-input", "a private CryptoKey is required (got a " + input.type + " key)");
126
- return webcrypto.exportAnyKey(input, _err, "key/bad-input");
131
+ return _exportAnyKey(input, _err, "key/bad-input");
127
132
  }
128
133
  return pkix.coerceToDer(input, { pemLabel: "PRIVATE KEY", PemError: PemError, ErrorClass: KeyError, prefix: "key" });
129
134
  }
@@ -295,13 +300,13 @@ async function export_(key, opts) {
295
300
  var fmt = opts.format || "der";
296
301
  if (fmt === "jwk") {
297
302
  try {
298
- return await webcrypto.exportAnyKey(key, _err, "key/bad-input", "jwk");
303
+ return await _exportAnyKey(key, _err, "key/bad-input", "jwk");
299
304
  } catch (e) {
300
305
  if (e && e.isPkiError) throw e;
301
306
  throw _err("key/bad-input", "exporting the key as a JWK failed", e);
302
307
  }
303
308
  }
304
- var der = await webcrypto.exportAnyKey(key, _err, "key/bad-input");
309
+ var der = await _exportAnyKey(key, _err, "key/bad-input");
305
310
  if (fmt === "der") return der;
306
311
  if (fmt === "pem") return pkix.pemEncode(der, opts.label || defaultLabel, PemError);
307
312
  if (fmt === "pkcs1") return _toPkcs1(der, key);
@@ -870,8 +875,8 @@ async function publicFromPrivate(privateKey, opts) {
870
875
  return opts.pem ? pkix.pemEncode(spki, "PUBLIC KEY", PemError) : spki;
871
876
  }
872
877
  try {
873
- var priv = nodeCrypto.createPrivateKey({ key: der, format: "der", type: "pkcs8" });
874
- spki = nodeCrypto.createPublicKey(priv).export({ format: "der", type: "spki" });
878
+ var priv = guard.crypto.privateKey({ key: der, format: "der", type: "pkcs8" });
879
+ spki = guard.crypto.exportKey(guard.crypto.publicKey(priv), { format: "der", type: "spki" });
875
880
  } catch (e) { throw _err("key/bad-input", "could not derive the public key from the private key", e); }
876
881
  return opts.pem ? pkix.pemEncode(spki, "PUBLIC KEY", PemError) : spki;
877
882
  }
@@ -946,7 +951,7 @@ function _pairSignOptions(priv, pub) {
946
951
  /** @internal Null-prototype, because the engine READS this object by name. A plain object would
947
952
  * let an inherited `saltLength` reach the signature as a restriction neither half stated. */
948
953
  var options = _create(null);
949
- options.padding = nodeCrypto.constants.RSA_PKCS1_PSS_PADDING;
954
+ options.padding = guard.crypto.padding.RSA_PKCS1_PSS;
950
955
  if (salt != null) options.saltLength = salt;
951
956
  return { digest: digest, options: options };
952
957
  }
@@ -1006,7 +1011,7 @@ async function correspondsTo(privateKey, spkiDer) {
1006
1011
  var privAsPkcs3 = _dhAsPkcs3(privDer, 1, "the private key's");
1007
1012
  try {
1008
1013
  _assertDhWithin(privAsPkcs3, 1, "the private key's");
1009
- try { priv = nodeCrypto.createPrivateKey({ key: privAsPkcs3, format: "der", type: "pkcs8" }); }
1014
+ try { priv = guard.crypto.privateKey({ key: privAsPkcs3, format: "der", type: "pkcs8" }); }
1010
1015
  catch (e) { throw _err("key/bad-input", "the private key could not be read", e); }
1011
1016
  } finally { if (privAsPkcs3 !== privDer) guard.secret.zeroize(privAsPkcs3, KeyError, "key/bad-input", "the rewritten private key"); }
1012
1017
  var publicOid = _algorithmOidAt(spki, 0);
@@ -1014,7 +1019,7 @@ async function correspondsTo(privateKey, spkiDer) {
1014
1019
  if (_isDhOid(_algorithmOidAt(privDer, 1)) !== _isDhOid(publicOid)) return false;
1015
1020
  var pubAsPkcs3 = _dhAsPkcs3(spki, 0, "the public key's");
1016
1021
  _assertDhWithin(pubAsPkcs3, 0, "the public key's");
1017
- try { pub = nodeCrypto.createPublicKey({ key: pubAsPkcs3, format: "der", type: "spki" }); }
1022
+ try { pub = guard.crypto.publicKey({ key: pubAsPkcs3, format: "der", type: "spki" }); }
1018
1023
  catch (e) { throw _err("key/bad-input", "the public key could not be read", e); }
1019
1024
  if (!_sameKeyFamily(priv, pub)) return false;
1020
1025
  var kind = guard.text.keyOf(priv.asymmetricKeyType);
@@ -1029,7 +1034,7 @@ async function correspondsTo(privateKey, spkiDer) {
1029
1034
  /** @internal Compared as KEYS rather than as encodings: a PKCS #3 DHParameter may carry the
1030
1035
  * optional privateValueLength beside the same p and g, and two encodings of one public value
1031
1036
  * are one key. The runtime's equality compares the group and the value. */
1032
- return _keyEquals(nodeCrypto.createPublicKey(priv), pub) === true;
1037
+ return _keyEquals(guard.crypto.publicKey(priv), pub) === true;
1033
1038
  }
1034
1039
  if (_PAIR_KEM[kind]) {
1035
1040
  var sealed = nodeCrypto.encapsulate(pub);
@@ -1053,8 +1058,8 @@ async function correspondsTo(privateKey, spkiDer) {
1053
1058
  * structure for an attacker to plant, so what the scalar generates IS the canonical answer and
1054
1059
  * comparing against it adds a bound the agreement alone does not give. */
1055
1060
  return guard.crypto.constantTimeEqual(
1056
- nodeCrypto.createPublicKey(priv).export({ format: "der", type: "spki" }),
1057
- pub.export({ format: "der", type: "spki" }));
1061
+ guard.crypto.exportKey(guard.crypto.publicKey(priv), { format: "der", type: "spki" }),
1062
+ guard.crypto.exportKey(pub, { format: "der", type: "spki" }));
1058
1063
  }
1059
1064
  var how = _pairSignOptions(priv, pub);
1060
1065
  if (how === null) {
@@ -1062,8 +1067,8 @@ async function correspondsTo(privateKey, spkiDer) {
1062
1067
  }
1063
1068
  var signKey = how.options ? _assign(_create(null), { key: priv }, how.options) : priv;
1064
1069
  var verifyKey = how.options ? _assign(_create(null), { key: pub }, how.options) : pub;
1065
- return nodeCrypto.verify(how.digest, _PAIR_PROBE, verifyKey,
1066
- nodeCrypto.sign(how.digest, _PAIR_PROBE, signKey)) === true;
1070
+ return guard.crypto.verify(how.digest, _PAIR_PROBE, verifyKey,
1071
+ guard.crypto.sign(how.digest, _PAIR_PROBE, signKey)) === true;
1067
1072
  } catch (e) {
1068
1073
  /** @internal The incompatible-parameters verdict is decided rather than caught, so it is passed
1069
1074
  * through with the reason it was raised for. Rewrapping it would tell an operator the pair could
package/lib/lint.js CHANGED
@@ -3972,6 +3972,89 @@ var RFC9881_RULES = [
3972
3972
  var RFC9909_RULES = [
3973
3973
  _pqcSigKeyUsageRule("lint/rfc9909/slhdsa-key-usage", "rfc9909", "RFC 9909 6", "SLH-DSA", _isSlhDsa),
3974
3974
  ];
3975
+ /** @internal Section 4.1 states the criticality at SHOULD NOT ("This extension SHOULD NOT be marked
3976
+ * critical", the section adding that marking it "would severely impact interoperability"), so it is
3977
+ * graded at warn here rather than held to a value by the shared criticality table, which carries only
3978
+ * the MUSTs. The same reason keeps issuerAltName out of the CRL table. Placement is a MUST in the same
3979
+ * section: "For certificate chains, this extension MUST only be included in the end-entity
3980
+ * certificate", so the two clauses are two rows at two strengths. */
3981
+ var OID_SAPKI = oid.byName("subjectAltPublicKeyInfo");
3982
+ var OID_ALT_ALG = oid.byName("altSignatureAlgorithm");
3983
+ var OID_ALT_VAL = oid.byName("altSignatureValue");
3984
+ /** @internal ITU-T X.509 (2019) clause 9.8. Two clauses at two strengths, and the difference matters.
3985
+ * Clause 9.8.3 states the pairing as a shall: "When the altSignatureAlgorithm extension is included in
3986
+ * a particular value that is an instance of a data type that supports extensions, the altSignatureValue
3987
+ * extension shall also be included." Clause 7.2.2 requires the full set in a certificate: one "shall
3988
+ * contain alternative public key information in the subjectAltPublicKeyInfo extension ..., an
3989
+ * alternative signature algorithm value in the altSignatureAlgorithm extension ... and an alternative
3990
+ * signature value in the altSignatureValue extension". Criticality is stated only in NOTEs recommending
3991
+ * non-critical, with the clauses saying the extensions "may be flagged as critical or as non-critical",
3992
+ * so that row is a warning and never an error: grading a NOTE as an error would invent a requirement
3993
+ * level the text does not state. */
3994
+ var X509_ALTSIG_RULES = [
3995
+ {
3996
+ id: "lint/x509-altsig/algorithm-without-value", severity: "error", source: "x509-altsig", citation: "ITU-T X.509 (2019) 9.8.3",
3997
+ message: "a value carrying altSignatureAlgorithm must also carry altSignatureValue",
3998
+ appliesTo: function (cert, ctx) { return !!ctx.raw("altSignatureAlgorithm"); },
3999
+ check: function (cert, ctx) {
4000
+ return ctx.raw("altSignatureValue") ? null : { context: { present: "altSignatureAlgorithm", missing: "altSignatureValue" } };
4001
+ },
4002
+ },
4003
+ {
4004
+ id: "lint/x509-altsig/value-without-algorithm", severity: "error", source: "x509-altsig", citation: "ITU-T X.509 (2019) 9.8.3",
4005
+ message: "a value carrying altSignatureValue must also carry altSignatureAlgorithm, which states how to verify it",
4006
+ appliesTo: function (cert, ctx) { return !!ctx.raw("altSignatureValue"); },
4007
+ check: function (cert, ctx) {
4008
+ return ctx.raw("altSignatureAlgorithm") ? null : { context: { present: "altSignatureValue", missing: "altSignatureAlgorithm" } };
4009
+ },
4010
+ },
4011
+ {
4012
+ id: "lint/x509-altsig/incomplete-set", severity: "error", source: "x509-altsig", citation: "ITU-T X.509 (2019) 7.2.2",
4013
+ message: "a certificate carrying the alternative cryptographic algorithm extensions must carry all three",
4014
+ appliesTo: function (cert, ctx) {
4015
+ return !!(ctx.raw("subjectAltPublicKeyInfo") || ctx.raw("altSignatureAlgorithm") || ctx.raw("altSignatureValue"));
4016
+ },
4017
+ check: function (cert, ctx) {
4018
+ var missing = ["subjectAltPublicKeyInfo", "altSignatureAlgorithm", "altSignatureValue"]
4019
+ .filter(function (n) { return !ctx.raw(n); });
4020
+ return missing.length ? { context: { missing: missing.join(", ") } } : null;
4021
+ },
4022
+ },
4023
+ {
4024
+ id: "lint/x509-altsig/critical", severity: "warn", source: "x509-altsig", citation: "ITU-T X.509 (2019) 9.8.2 / 9.8.3",
4025
+ message: "the alternative cryptographic algorithm extensions are recommended non-critical",
4026
+ check: function (cert) {
4027
+ var rows = [];
4028
+ (cert.extensions || []).forEach(function (e) {
4029
+ if (e.critical !== true) return;
4030
+ if (e.oid !== OID_SAPKI && e.oid !== OID_ALT_ALG && e.oid !== OID_ALT_VAL) return;
4031
+ rows.push({ context: { extension: e.name || e.oid, recommended: "non-critical", was: "critical" } });
4032
+ });
4033
+ return rows.length ? rows : null;
4034
+ },
4035
+ },
4036
+ ];
4037
+
4038
+ var RFC9763_RULES = [
4039
+ {
4040
+ id: "lint/rfc9763/related-certificate-critical", severity: "warn", source: "rfc9763", citation: "RFC 9763 4.1",
4041
+ message: "the relatedCertificate extension should not be marked critical",
4042
+ appliesTo: function (cert, ctx) { return !!ctx.raw("relatedCertificate"); },
4043
+ check: function (cert, ctx) {
4044
+ var e = ctx.raw("relatedCertificate");
4045
+ return e.critical === true ? { context: { extension: "relatedCertificate", recommended: "non-critical", was: "critical" } } : null;
4046
+ },
4047
+ },
4048
+ {
4049
+ id: "lint/rfc9763/related-certificate-on-ca", severity: "error", source: "rfc9763", citation: "RFC 9763 4.1",
4050
+ message: "the relatedCertificate extension must only be included in the end-entity certificate",
4051
+ appliesTo: function (cert, ctx) { return !!ctx.raw("relatedCertificate"); },
4052
+ check: function (cert, ctx) {
4053
+ var bc = ctx.decode("basicConstraints");
4054
+ return (bc && bc.value && bc.value.cA === true) ? { context: { extension: "relatedCertificate", cA: true } } : null;
4055
+ },
4056
+ },
4057
+ ];
3975
4058
 
3976
4059
  /** @internal RFC 5280 sec. 5 states the criticality of each CRL extension it profiles, and sec. 5.3
3977
4060
  * does the same for the entry extensions. certificateIssuer is critical (sec. 5.3.3) because a
@@ -6329,6 +6412,8 @@ var PROFILES = Object.assign(Object.create(null), {
6329
6412
  "rfc9881": RFC9881_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
6330
6413
  "rfc9909": RFC9909_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
6331
6414
  "rfc9935": RFC9935_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
6415
+ "rfc9763": RFC9763_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
6416
+ "x509-altsig": X509_ALTSIG_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
6332
6417
  "cabf-tls": CABF_TLS_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
6333
6418
  "cabf-smime": CABF_SMIME_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
6334
6419
  "cabf-cs": CABF_CS_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
@@ -6351,6 +6436,7 @@ var PROFILES = Object.assign(Object.create(null), {
6351
6436
  .concat(RFC5280_EXTENSION_SYNTAX_RULES),
6352
6437
  });
6353
6438
  var ALL_RULES = RFC5280_RULES.concat(RFC9881_RULES).concat(RFC9909_RULES).concat(RFC9935_RULES)
6439
+ .concat(RFC9763_RULES).concat(X509_ALTSIG_RULES)
6354
6440
  .concat(CABF_TLS_RULES).concat(CABF_SMIME_RULES).concat(CABF_CS_RULES).concat(ETSI_QC_RULES)
6355
6441
  /** @internal The root-program rows are in the registry so `rules()` enumerates them, and stay
6356
6442
  * inert under the default set because each one asks whether the caller named the profile. */
@@ -6363,11 +6449,89 @@ var CABF_TLS_CSR_RULES = CABF_TLS_RULES.filter(function (r) {
6363
6449
  /** @internal A requested extension whose value does not decode is the same fault in a request as
6364
6450
  * in a certificate, so the request profiles take the same row the certificate profiles do. */
6365
6451
  var CSR_STRUCTURAL_RULES = RFC2986_CSR_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES);
6452
+
6453
+ var OID_SOP_ATTR = oid.byName("statementOfPossession");
6454
+ var SOP_SIGNATURE_USAGES = ["digitalSignature", "nonRepudiation", "keyCertSign", "cRLSign"];
6455
+ /** @internal RFC 9883 sec. 6 states one prohibition outright: "The privateKeyPossessionStatement
6456
+ * attribute MUST NOT be used to obtain a signature certificate." Sec. 4 states the same requirement as a
6457
+ * property of the request: "the subjectPKInfo MUST contain the public key for the key establishment
6458
+ * algorithm". The attribute exists so a key-establishment key, which cannot sign, can still be certified;
6459
+ * a request carrying it while asking for a signature key usage, or naming a subject key that can only
6460
+ * sign, is asking for the certificate those sentences forbid. The two name comparisons sec. 3 states are
6461
+ * deliberately absent here: each ends in "the certificate policy MUST describe how the CA can determine
6462
+ * that the two subject names identify the same entity", which is a question no linter can answer, and
6463
+ * `pki.possession.verifyRequest` reports the comparison for a caller's policy to resolve. */
6464
+ function _csrHasPossessionStatement(csr) {
6465
+ return (csr.attributes || []).some(function (a) { return a.type === OID_SOP_ATTR; });
6466
+ }
6467
+ /** @internal The same table `pki.possession.verifyRequest` reads, so the linter and the verifier grade one
6468
+ * request alike. Read off the requested key usages alone, this row passed a request that named no usage
6469
+ * while asking to certify a key that can only ever sign, which is the sec. 4 MUST on the subjectPKInfo and
6470
+ * the sec. 6 prohibition both, and which the verifier refuses; `pki.csr.sign` under this profile then
6471
+ * emitted it. */
6472
+ var _isSignatureOnlyKey = pkix.signatureOnlyKeyOid(NS);
6473
+ function _csrSubjectKeyOid(csr) {
6474
+ return csr.subjectPublicKeyInfo && csr.subjectPublicKeyInfo.algorithm
6475
+ ? csr.subjectPublicKeyInfo.algorithm.oid : null;
6476
+ }
6477
+ var RFC9883_CSR_RULES = [
6478
+ {
6479
+ id: "lint/rfc9883/signature-certificate-requested", severity: "error", source: "rfc9883", citation: "RFC 9883 4 and 6",
6480
+ message: "a request carrying a possession statement must not ask for a signature certificate",
6481
+ appliesTo: function (csr) { return _csrHasPossessionStatement(csr); },
6482
+ check: function (csr, ctx) {
6483
+ /** @internal The subject key decides on its own when it can only sign, so this is read whether or
6484
+ * not the request names a usage. A key that can do either says nothing here and is judged by the
6485
+ * usages the request asks for. */
6486
+ var keyOid = _csrSubjectKeyOid(csr);
6487
+ if (_isSignatureOnlyKey(keyOid)) {
6488
+ return { context: { subjectKeyAlgorithm: oid.name(keyOid) || keyOid } };
6489
+ }
6490
+ var ku = ctx.decode("keyUsage");
6491
+ var value = ku && ku.value;
6492
+ if (!value) return null;
6493
+ var asked = SOP_SIGNATURE_USAGES.filter(function (u) { return value[u] === true; });
6494
+ return asked.length ? { context: { keyUsage: asked.join(", ") } } : null;
6495
+ },
6496
+ },
6497
+ {
6498
+ id: "lint/rfc9883/statement-value-undecodable", severity: "error", source: "rfc9883", citation: "RFC 9883 3",
6499
+ message: "a statementOfPossession attribute value must be a well-formed PrivateKeyPossessionStatement",
6500
+ appliesTo: function (csr) { return _csrHasPossessionStatement(csr); },
6501
+ check: function (csr) {
6502
+ var rows = [];
6503
+ (csr.attributes || []).forEach(function (a) {
6504
+ if (a.type !== OID_SOP_ATTR) return;
6505
+ if (a.privateKeyPossessionStatement === undefined) {
6506
+ rows.push({ context: { attribute: a.name || a.type } });
6507
+ }
6508
+ });
6509
+ return rows.length ? rows : null;
6510
+ },
6511
+ },
6512
+ /** @internal Sec. 3 states the attribute's syntax in the singular and nothing anywhere in RFC 9883 says
6513
+ * which of several a CA should honor, so a request carrying more than one leaves every check sec. 3 asks
6514
+ * for without a subject: the checks would be made against one statement while the CA certifies a request
6515
+ * that carries another naming a different certificate. `pki.possession.verifyRequest` refuses such a
6516
+ * request with `possession/bad-request`, and without this row the linter graded it conforming and
6517
+ * `pki.csr.sign` under this profile would emit it. */
6518
+ {
6519
+ id: "lint/rfc9883/statement-attribute-repeated", severity: "error", source: "rfc9883", citation: "RFC 9883 3",
6520
+ message: "a request must carry one statementOfPossession attribute, not several",
6521
+ appliesTo: function (csr) { return _csrHasPossessionStatement(csr); },
6522
+ check: function (csr) {
6523
+ var count = 0;
6524
+ (csr.attributes || []).forEach(function (a) { if (a.type === OID_SOP_ATTR) count += 1; });
6525
+ return count > 1 ? { context: { attributes: String(count) } } : null;
6526
+ },
6527
+ },
6528
+ ];
6366
6529
  var CSR_PROFILES = Object.assign(Object.create(null), {
6367
6530
  "rfc2986": CSR_STRUCTURAL_RULES,
6368
6531
  "cabf-tls": CSR_STRUCTURAL_RULES.concat(CABF_TLS_CSR_RULES),
6532
+ "rfc9883": RFC9883_CSR_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
6369
6533
  });
6370
- var ALL_CSR_RULES = CSR_STRUCTURAL_RULES;
6534
+ var ALL_CSR_RULES = CSR_STRUCTURAL_RULES.concat(RFC9883_CSR_RULES);
6371
6535
 
6372
6536
  function _subjectIsNonEmpty(csr) {
6373
6537
  var rdns = csr.subject && csr.subject.rdns;