@blamejs/pki 0.5.16 → 0.5.18

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 (50) hide show
  1. package/CHANGELOG.md +57 -1
  2. package/lib/acme.js +7 -4
  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 +16 -1
  8. package/lib/cms-decrypt.js +58 -29
  9. package/lib/cms-encrypt.js +200 -38
  10. package/lib/cms-sign.js +18 -4
  11. package/lib/cms-verify.js +5 -1
  12. package/lib/constants.js +1 -0
  13. package/lib/crl-sign.js +62 -2
  14. package/lib/crmf-sign.js +1 -0
  15. package/lib/csr-sign.js +1 -0
  16. package/lib/ct.js +1 -0
  17. package/lib/est.js +1 -0
  18. package/lib/framework-error.js +1 -0
  19. package/lib/guard-secret.js +45 -1
  20. package/lib/hpke.js +6 -2
  21. package/lib/http-transport.js +1 -0
  22. package/lib/inspect.js +1 -0
  23. package/lib/jose.js +31 -3
  24. package/lib/key.js +2 -1
  25. package/lib/lint.js +1 -0
  26. package/lib/merkle.js +1 -0
  27. package/lib/ocsp.js +1 -0
  28. package/lib/oid.js +1 -0
  29. package/lib/path-validate.js +1 -0
  30. package/lib/pbes2.js +16 -8
  31. package/lib/pkcs12-build.js +203 -12
  32. package/lib/schema-all.js +1 -0
  33. package/lib/schema-c509.js +1 -0
  34. package/lib/schema-csrattrs.js +1 -0
  35. package/lib/schema-engine.js +1 -0
  36. package/lib/schema-pkcs8.js +1 -0
  37. package/lib/schema-smime.js +1 -0
  38. package/lib/schema-tsp.js +1 -0
  39. package/lib/schema-x509.js +1 -0
  40. package/lib/shbs.js +1 -0
  41. package/lib/sigstore.js +1 -0
  42. package/lib/smime.js +1 -0
  43. package/lib/tls-cert-compress.js +1 -0
  44. package/lib/trust.js +1 -0
  45. package/lib/tsp-sign.js +65 -0
  46. package/lib/webauthn.js +1 -0
  47. package/lib/webcrypto.js +11 -8
  48. package/lib/x509-sign.js +38 -0
  49. package/package.json +1 -1
  50. package/sbom.cdx.json +6 -6
@@ -351,10 +351,10 @@ function _pwriWrapIv(kek, cek, iv) {
351
351
  var wk = _pwriFormat(cek);
352
352
  try {
353
353
  var c1 = nodeCrypto.createCipheriv("aes-256-cbc", kek, iv); c1.setAutoPadding(false);
354
- var pass1 = Buffer.concat([c1.update(wk), c1.final()]);
354
+ var pass1 = guard.secret.cipherFinish(c1, wk, CmsError, "cms/bad-input", "the PWRI first-pass ciphertext");
355
355
  var iv2 = pass1.subarray(pass1.length - 16);
356
356
  var c2 = nodeCrypto.createCipheriv("aes-256-cbc", kek, iv2); c2.setAutoPadding(false);
357
- return Buffer.concat([c2.update(pass1), c2.final()]);
357
+ return guard.secret.cipherFinish(c2, pass1, CmsError, "cms/bad-input", "the PWRI wrapped key");
358
358
  } finally {
359
359
  guard.secret.zeroize(wk, CmsError, "cms/bad-input", "the PWRI plaintext block");
360
360
  }
@@ -364,22 +364,91 @@ var PRF_HASH = { hmacWithSHA1: "SHA-1", hmacWithSHA256: "SHA-256", hmacWithSHA38
364
364
  function _prfHash(prf) { if (!PRF_HASH[prf]) throw _err("cms/bad-input", "unsupported pwri prf " + JSON.stringify(prf)); return PRF_HASH[prf]; }
365
365
 
366
366
  // ---- recipient dispatch ----------------------------------------------------
367
- async function _buildRecipient(cek, desc, opts) {
367
+ // The fields a recipient descriptor carries, PER ARM. Each arm reads a disjoint set, and every
368
+ // field beyond the selector is optional with a default, so an unrecognized name is missed by
369
+ // nothing and the recipient is built with the shipped parameters instead: `oaepHsh` for `oaepHash`
370
+ // encrypts the content key under the default SHA-256 OAEP, and a misspelled `iterations`, `salt`
371
+ // or `prf` on a password recipient derives the key at the default work factor. A union table would
372
+ // admit another arm's fields, which the chosen arm never reads.
373
+ var _PWRI_KEYS = { password: 1, iterations: 1, prf: 1, salt: 1 };
374
+ var _KEKRI_KEYS = { kek: 1, kekId: 1 };
375
+ // A certificate recipient dispatches on the certificate's KEY ALGORITHM, and the arms read
376
+ // different fields: ktri (RSA) reads oaepHash and never ukm; kari (ECDH / X25519 / X448) and kemri
377
+ // (ML-KEM) read ukm and never oaepHash. One table across all three accepted `oaepHash` on an EC
378
+ // certificate, where nothing reads it, so the content key was wrapped under the arm's own defaults
379
+ // while the caller had named a hash. Checked after the key algorithm resolves, since that is what
380
+ // selects the arm.
381
+ var _KTRI_RECIPIENT_KEYS = { cert: 1, oaepHash: 1, keyIdentifier: 1 };
382
+ var _AGREE_RECIPIENT_KEYS = { cert: 1, keyIdentifier: 1, ukm: 1 };
383
+
384
+ // The three top-level options that are per-recipient DEFAULTS rather than message-wide settings.
385
+ // Which of them any given message actually consumes depends on the recipient arms it selects, so
386
+ // they are checked against the arms after those resolve, not against a fixed list.
387
+ var _RECIPIENT_DEFAULTS = ["oaepHash", "keyIdentifier", "ukm"];
388
+
389
+ function _assertRecipientKeys(desc, known, arm) {
390
+ guard.identifier.assertKnownKeys(desc, known, _err, "cms/bad-input", function (k) {
391
+ return "unknown field " + JSON.stringify(k) + " on a " + arm +
392
+ " recipient; that field is not read for this recipient type";
393
+ });
394
+ }
395
+
396
+ // A default no selected recipient reads is discarded exactly as a misspelling would be: `oaepHash`
397
+ // on a message whose only recipient is a password recipient names a wrapping the message does not
398
+ // perform. Run after every recipient's arm has resolved and BEFORE anything is emitted.
399
+ function _assertDefaultsConsumed(opts, consumed) {
400
+ for (var i = 0; i < _RECIPIENT_DEFAULTS.length; i++) {
401
+ var name = _RECIPIENT_DEFAULTS[i];
402
+ if (opts[name] != null && !consumed[name]) {
403
+ throw _err("cms/bad-input", "opts." + name + " is a per-recipient default and no recipient of this message reads it" +
404
+ (name === "oaepHash" ? " (only an RSA certificate recipient does)"
405
+ : name === "ukm" ? " (only a key-agreement or KEM certificate recipient does)"
406
+ : " (only a certificate recipient does)"));
407
+ }
408
+ }
409
+ }
410
+
411
+ async function _buildRecipient(cek, desc, opts, consumed) {
368
412
  if (desc == null || typeof desc !== "object") throw _err("cms/bad-input", "each recipient must be a descriptor object");
369
- if (desc.password != null) return _buildPwri(cek, desc);
370
- if (desc.kek != null) return _buildKekri(cek, desc);
413
+ // Checked per arm, on the same selector the dispatch below uses, so the table that runs is the
414
+ // one belonging to the arm that will build. `consumed` records which top-level defaults the
415
+ // selected arms read, so a default no recipient consumes can be refused once they all resolve.
416
+ if (desc.password != null) { _assertRecipientKeys(desc, _PWRI_KEYS, "password"); return _buildPwri(cek, desc); }
417
+ if (desc.kek != null) { _assertRecipientKeys(desc, _KEKRI_KEYS, "KEK"); return _buildKekri(cek, desc); }
371
418
  if (desc.cert != null) {
372
419
  var cert = x509.parse(_normCertDer(desc.cert, "a recipient certificate"));
373
420
  var keyOid = cert.subjectPublicKeyInfo.algorithm.oid;
374
- if (keyOid === O("rsaEncryption") || keyOid === O("rsassaPss")) return _buildKtri(cek, cert, mergeOpts(opts, desc));
375
- if (keyOid === O("ecPublicKey") || MONT_KA[keyOid]) return _buildKari(cek, cert, mergeOpts(opts, desc));
376
- if (KEM_WC[keyOid]) return _buildKemri(cek, cert, mergeOpts(opts, desc));
421
+ if (keyOid === O("rsaEncryption") || keyOid === O("rsassaPss")) {
422
+ _assertRecipientKeys(desc, _KTRI_RECIPIENT_KEYS, "RSA certificate");
423
+ return _buildKtri(cek, cert, mergeOpts(opts, desc, _KTRI_RECIPIENT_KEYS, consumed));
424
+ }
425
+ if (keyOid === O("ecPublicKey") || MONT_KA[keyOid]) {
426
+ _assertRecipientKeys(desc, _AGREE_RECIPIENT_KEYS, "key-agreement certificate");
427
+ return _buildKari(cek, cert, mergeOpts(opts, desc, _AGREE_RECIPIENT_KEYS, consumed));
428
+ }
429
+ if (KEM_WC[keyOid]) {
430
+ _assertRecipientKeys(desc, _AGREE_RECIPIENT_KEYS, "KEM certificate");
431
+ return _buildKemri(cek, cert, mergeOpts(opts, desc, _AGREE_RECIPIENT_KEYS, consumed));
432
+ }
377
433
  throw _err("cms/unsupported-algorithm", "unsupported recipient certificate key algorithm " + keyOid);
378
434
  }
379
435
  throw _err("cms/bad-input", "a recipient needs { cert }, { password }, or { kek, kekId }");
380
436
  }
381
- function mergeOpts(opts, desc) {
382
- return { oaepHash: desc.oaepHash || opts.oaepHash, keyIdentifier: desc.keyIdentifier || opts.keyIdentifier, ukm: desc.ukm != null ? desc.ukm : opts.ukm };
437
+ // Each default is taken from the descriptor when it supplies one and from the top-level options
438
+ // otherwise. That one choice is also the record of which top-level defaults the recipient CONSUMED:
439
+ // an arm that does not read the name never consumes it, and neither does one whose descriptor
440
+ // overrode it, so recording it from the arm's table alone would accept a value nothing read.
441
+ function _pickDefault(desc, opts, name, known, consumed, nullish) {
442
+ var supplied = desc[name], fromDesc = nullish ? supplied != null : !!supplied;
443
+ if (!fromDesc && known[name] && consumed && opts[name] != null) consumed[name] = 1;
444
+ return fromDesc ? supplied : opts[name];
445
+ }
446
+ function mergeOpts(opts, desc, known, consumed) {
447
+ return {
448
+ oaepHash: _pickDefault(desc, opts, "oaepHash", known, consumed, false),
449
+ keyIdentifier: _pickDefault(desc, opts, "keyIdentifier", known, consumed, false),
450
+ ukm: _pickDefault(desc, opts, "ukm", known, consumed, true),
451
+ };
383
452
  }
384
453
  // RecipientInfo CHOICE: ktri untagged; kari [1], kekri [2], pwri [3], ori [4], all IMPLICIT,
385
454
  // so the arm's SEQUENCE tag is replaced by the context tag.
@@ -397,12 +466,46 @@ function _envelopedVersion(recips, hasUnprotected) {
397
466
  return forcesTwo ? 2 : 0;
398
467
  }
399
468
 
469
+ // The two exported verbs have DISJOINT option sets, so they get separate tables: one shared table
470
+ // would admit every other verb's keys, which is the hole rather than the door. Each is the union of
471
+ // what the verb reads THROUGH ITS DELEGATIONS -- encrypt never names oaepHash, keyIdentifier or ukm
472
+ // itself, but hands opts to the per-recipient builders that do, and to _emit for pem.
473
+ // Authenticated attributes live in an AuthEnvelopedData, which only an AEAD content algorithm
474
+ // produces: a CBC algorithm selects EnvelopedData, which has no field for them and is not handed
475
+ // opts at all. Requested there, they named a protection the message does not carry.
476
+ var _ENCRYPT_OPTS_AEAD = {
477
+ contentEncryptionAlgorithm: 1, contentType: 1, authAttrs: 1, oaepHash: 1, keyIdentifier: 1,
478
+ ukm: 1, pem: 1,
479
+ };
480
+ var _ENCRYPT_OPTS_CBC = {
481
+ contentEncryptionAlgorithm: 1, contentType: 1, oaepHash: 1, keyIdentifier: 1, ukm: 1, pem: 1,
482
+ };
483
+ // authenticatedAttributes: false MACs the content octets directly, and the attribute set is what
484
+ // carries the digest: neither the digest algorithm nor any extra attribute is read on that branch.
485
+ var _AUTHENTICATE_OPTS_ATTRS = {
486
+ macAlgorithm: 1, digestAlgorithm: 1, authenticatedAttributes: 1, authAttrs: 1, contentType: 1,
487
+ keyIdentifier: 1, oaepHash: 1, ukm: 1, pem: 1,
488
+ };
489
+ var _AUTHENTICATE_OPTS_BARE = {
490
+ macAlgorithm: 1, authenticatedAttributes: 1, contentType: 1,
491
+ keyIdentifier: 1, oaepHash: 1, ukm: 1, pem: 1,
492
+ };
493
+
400
494
  async function encrypt(content, recipients, opts) {
401
495
  opts = opts || {};
402
- var contentBytes = guard.bytes.view(content, CmsError, "cms/bad-input", "content");
496
+ // The content algorithm selects the structure, so it is resolved before the door and the table
497
+ // that runs is the one belonging to the structure this call will emit.
403
498
  var algName = opts.contentEncryptionAlgorithm || "aes-256-gcm";
404
499
  var ca = CONTENT_ALGS[algName];
405
500
  if (!ca) throw _err("cms/bad-input", "unsupported contentEncryptionAlgorithm " + JSON.stringify(algName));
501
+ guard.identifier.assertKnownKeys(opts, ca.aead ? _ENCRYPT_OPTS_AEAD : _ENCRYPT_OPTS_CBC, _err, "cms/bad-input",
502
+ function (k) {
503
+ return "pki.cms.encrypt has an unknown option " + JSON.stringify(k) +
504
+ (!ca.aead && k === "authAttrs"
505
+ ? " for a CBC content algorithm; authenticated attributes are carried only by an AuthEnvelopedData, which an AEAD algorithm produces"
506
+ : "");
507
+ });
508
+ var contentBytes = guard.bytes.view(content, CmsError, "cms/bad-input", "content");
406
509
  var contentType = opts.contentType || "data";
407
510
  var cek = nodeCrypto.randomBytes(ca.keyBits / 8);
408
511
 
@@ -415,7 +518,9 @@ async function encrypt(content, recipients, opts) {
415
518
 
416
519
  if (!recipients.length) throw _err("cms/bad-input", "at least one recipient is required (RFC 5652 sec. 6.1)");
417
520
  var recips = [];
418
- for (var i = 0; i < recipients.length; i++) recips.push(await _buildRecipient(cek, recipients[i], opts));
521
+ var consumed = {};
522
+ for (var i = 0; i < recipients.length; i++) recips.push(await _buildRecipient(cek, recipients[i], opts, consumed));
523
+ _assertDefaultsConsumed(opts, consumed);
419
524
  var riNodes = recips.map(_taggedRecipient);
420
525
 
421
526
  if (ca.aead) return _emit(_authEnvelopedData(contentBytes, cek, ca, contentType, opts, riNodes, recips), "authEnvelopedData", opts);
@@ -432,11 +537,48 @@ function _emit(inner, ctName, opts) {
432
537
 
433
538
  function _envelopedData(contentBytes, cek, ca, contentType, riNodes, recips) {
434
539
  var iv = nodeCrypto.randomBytes(16);
435
- var enc = pbes2.cbcEncrypt(cek, iv, contentBytes, ca.keyBits);
540
+ var enc = pbes2.cbcEncrypt(cek, iv, contentBytes, ca.keyBits, CmsError, "cms/bad-input");
436
541
  var eci = b.sequence([b.oid(O(contentType)), b.sequence([b.oid(O(ca.oid)), b.octetString(iv)]), b.contextPrimitive(0, enc)]);
437
542
  return b.sequence([b.integer(BigInt(_envelopedVersion(recips, false))), b.setOf(riNodes), eci]);
438
543
  }
439
544
 
545
+ // Every authenticated attribute this module MACs -- auto-built or caller-supplied, in either content
546
+ // type that carries them -- MUST be a well-formed Attribute SEQUENCE { type OBJECT IDENTIFIER,
547
+ // non-empty SET OF value } with no repeated type (RFC 5652 sec. 5.3). Rejecting here means a
548
+ // malformed attribute never reaches the wire; leaving it to the wire means the operator learns from
549
+ // a peer's parser that the message they built is unreadable, with the MAC already computed over it.
550
+ //
551
+ // `forbidden` names an attribute type this particular content type must not emit, with the clause
552
+ // that says so. It is a parameter rather than a constant because the same attribute is required in
553
+ // one content type and discouraged in the other: AuthenticatedData MACs its own message-digest by
554
+ // design (RFC 5652 sec. 9.2), while for AuthEnvelopedData that value is the unencrypted hash of a
555
+ // plaintext the message exists to conceal (RFC 5083 sec. 2.1 / sec. 5).
556
+ function _assertAuthAttrs(pairs, forbidden) {
557
+ var seenTypes = {};
558
+ pairs.forEach(function (p) {
559
+ var node;
560
+ try { node = asn1.decode(p); } catch (e) { throw _err("cms/bad-input", "an authenticated attribute is not well-formed DER", e); }
561
+ if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children || node.children.length !== 2 ||
562
+ node.children[1].tagClass !== "universal" || node.children[1].tagNumber !== asn1.TAGS.SET ||
563
+ !node.children[1].children || node.children[1].children.length < 1) {
564
+ throw _err("cms/bad-input", "an authenticated attribute must be an Attribute SEQUENCE { type, non-empty SET OF value } (RFC 5652)");
565
+ }
566
+ var t;
567
+ try { t = asn1.read.oid(node.children[0]); } catch (e) { throw _err("cms/bad-input", "an authenticated attribute type is not an OBJECT IDENTIFIER", e); }
568
+ if (guard.intrinsic.hasOwn(seenTypes, t)) throw _err("cms/bad-input", "authenticated attributes must not repeat an attribute type (RFC 5652): " + t);
569
+ seenTypes[t] = 1;
570
+ if (forbidden && t === forbidden.oid) throw _err("cms/bad-input", forbidden.why);
571
+ });
572
+ }
573
+
574
+ // The one attribute AuthEnvelopedData declines to emit. Its value is the unencrypted one-way hash of
575
+ // the plaintext, so disclosing it enables content tracking and confirms a guessed plaintext against
576
+ // a message that was encrypted to prevent exactly that.
577
+ var AUTH_ENVELOPED_FORBIDDEN = {
578
+ oid: O("messageDigest"),
579
+ why: "authAttrs must not carry the message-digest attribute in an AuthEnvelopedData: its value is the unencrypted hash of the plaintext, which enables content tracking and confirmation of a guessed plaintext (RFC 5083 sec. 2.1, sec. 5)",
580
+ };
581
+
440
582
  function _authEnvelopedData(contentBytes, cek, ca, contentType, opts, riNodes, recips) {
441
583
  var nonce = nodeCrypto.randomBytes(12);
442
584
  var authAttrsDer = null, aad = Buffer.alloc(0);
@@ -446,6 +588,7 @@ function _authEnvelopedData(contentBytes, cek, ca, contentType, opts, riNodes, r
446
588
  throw _err("cms/bad-input", "AuthEnvelopedData with a non-data contentType requires authAttrs (RFC 5083 sec. 2.1)");
447
589
  }
448
590
  if (opts.authAttrs && opts.authAttrs.length) {
591
+ _assertAuthAttrs(opts.authAttrs, AUTH_ENVELOPED_FORBIDDEN);
449
592
  // authAttrs are transmitted [1] IMPLICIT but MACed under the EXPLICIT SET OF tag (RFC 5083 sec. 2.2).
450
593
  var setOf = b.setOf(opts.authAttrs);
451
594
  aad = setOf; authAttrsDer = b.contextConstructed(1, setOf.subarray(_tlvHeaderLen(setOf)));
@@ -462,8 +605,31 @@ function _authEnvelopedData(contentBytes, cek, ca, contentType, opts, riNodes, r
462
605
  return b.sequence(kids);
463
606
  }
464
607
 
608
+ // The single descriptor an EncryptedData takes, PER ARM. `cek` and `password` select the arm and
609
+ // the arms read disjoint sets, so a union would admit the other arm's fields: `{ cek, iterations }`
610
+ // supplies a work factor to a branch that derives nothing, and the caller's parameter is discarded
611
+ // exactly as a misspelling would be. `pem` is read after both arms, so it belongs to each.
612
+ // pem selects the output encoding of the whole call and is an OPTION, the third argument, on every
613
+ // other verb here. A second spelling on the descriptor was read by the password arm alone, so the
614
+ // cek arm accepted it and returned DER; there is now one place to ask for PEM.
615
+ var _ENCRYPTED_DATA_CEK_KEYS = { cek: 1 };
616
+ var _ENCRYPTED_DATA_PW_KEYS = { password: 1, iterations: 1, salt: 1, prf: 1 };
617
+
465
618
  function _encryptedData(contentBytes, desc, ca, contentType, opts, cek) {
466
619
  if (ca.aead) throw _err("cms/bad-input", "EncryptedData supports only CBC content encryption");
620
+ // An EncryptedData carries no RecipientInfos at all, so nothing here reads a per-recipient
621
+ // default. This branch returns before the recipient loop, so it answers for them itself.
622
+ _assertDefaultsConsumed(opts, {});
623
+ // Checked per arm, on the same selector the dispatch below uses, so the table that runs belongs
624
+ // to the branch that will encrypt.
625
+ if (desc != null && typeof desc === "object") {
626
+ var edKeys = desc.cek != null ? _ENCRYPTED_DATA_CEK_KEYS : _ENCRYPTED_DATA_PW_KEYS;
627
+ var edArm = desc.cek != null ? "cek" : "password";
628
+ guard.identifier.assertKnownKeys(desc, edKeys, _err, "cms/bad-input", function (k) {
629
+ return "unknown field " + JSON.stringify(k) + " on a " + edArm +
630
+ " EncryptedData descriptor; that field is not read for this arm";
631
+ });
632
+ }
467
633
  var iv = nodeCrypto.randomBytes(16);
468
634
  var contentAlgNode, encKey;
469
635
  if (desc && desc.cek != null) {
@@ -475,7 +641,7 @@ function _encryptedData(contentBytes, desc, ca, contentType, opts, cek) {
475
641
  } else {
476
642
  throw _err("cms/bad-input", "EncryptedData needs a single { cek } or { password } descriptor");
477
643
  }
478
- var enc = pbes2.cbcEncrypt(encKey, iv, contentBytes, ca.keyBits);
644
+ var enc = pbes2.cbcEncrypt(encKey, iv, contentBytes, ca.keyBits, CmsError, "cms/bad-input");
479
645
  var eci = b.sequence([b.oid(O(contentType)), contentAlgNode, b.contextPrimitive(0, enc)]);
480
646
  var inner = b.sequence([b.integer(0n), eci]);
481
647
  return _emit(inner, "encryptedData", opts);
@@ -495,11 +661,11 @@ function _encryptedDataPbes2(contentBytes, desc, ca, contentType, iv, opts) {
495
661
  var key = nodeCrypto.pbkdf2Sync(password, salt, iterations, ca.keyBits / 8, pbes2.prfNodeByName(prf, _err, "cms"));
496
662
  if (pwOwn2.owned) guard.secret.zeroize(password, CmsError, "cms/bad-input", "the password encoding");
497
663
  try {
498
- var enc = pbes2.cbcEncrypt(key, iv, contentBytes, ca.keyBits);
664
+ var enc = pbes2.cbcEncrypt(key, iv, contentBytes, ca.keyBits, CmsError, "cms/bad-input");
499
665
  var contentAlg = pbes2.pbes2AlgId(salt, iterations, prf, ca.oid, iv);
500
666
  var eci = b.sequence([b.oid(O(contentType)), contentAlg, b.contextPrimitive(0, enc)]);
501
667
  var inner = b.sequence([b.integer(0n), eci]);
502
- return _emit(inner, "encryptedData", { pem: opts && opts.pem != null ? opts.pem : desc.pem });
668
+ return _emit(inner, "encryptedData", opts);
503
669
  } finally {
504
670
  guard.secret.zeroize(key, CmsError, "cms/bad-input", "the password-derived content-encryption key");
505
671
  }
@@ -509,7 +675,7 @@ function _encryptedDataPbes2(contentBytes, desc, ca, contentType, iv, opts) {
509
675
  function _gcmEncrypt(key, nonce, plaintext, aad, keyBits, tagLen) {
510
676
  var c = nodeCrypto.createCipheriv("aes-" + keyBits + "-gcm", key, nonce, { authTagLength: tagLen });
511
677
  if (aad && aad.length) c.setAAD(aad);
512
- var ct = Buffer.concat([c.update(plaintext), c.final()]);
678
+ var ct = guard.secret.cipherFinish(c, plaintext, CmsError, "cms/bad-input", "a content-encryption intermediate");
513
679
  return { ct: ct, tag: c.getAuthTag() };
514
680
  }
515
681
 
@@ -543,13 +709,22 @@ var SUPPORTED_DIGEST = { sha256: 1, sha384: 1, sha512: 1 };
543
709
 
544
710
  async function authenticate(content, recipients, opts) {
545
711
  opts = opts || {};
712
+ // The attribute branch is selected once, and the door and the MAC below both act on that one
713
+ // decision, so the table that runs belongs to the branch that will compute the MAC.
714
+ var withAttrs = opts.authenticatedAttributes !== false;
715
+ guard.identifier.assertKnownKeys(opts, withAttrs ? _AUTHENTICATE_OPTS_ATTRS : _AUTHENTICATE_OPTS_BARE,
716
+ _err, "cms/bad-input", function (k) {
717
+ return "pki.cms.authenticate has an unknown option " + JSON.stringify(k) +
718
+ (!withAttrs && (k === "digestAlgorithm" || k === "authAttrs")
719
+ ? " with authenticatedAttributes: false; the MAC then covers the content octets directly and no attribute set is built"
720
+ : "");
721
+ });
546
722
  var contentBytes = guard.bytes.view(content, CmsError, "cms/bad-input", "content");
547
723
  var macName = opts.macAlgorithm || "hmac-sha256";
548
724
  var mac = MAC_ALGS[macName];
549
725
  if (!mac) throw _err("cms/bad-input", "unsupported macAlgorithm " + JSON.stringify(macName) + " (hmac-sha256/384/512)");
550
726
  if (!Array.isArray(recipients) || !recipients.length) throw _err("cms/bad-input", "at least one recipient is required (RFC 5652 sec. 9.1)");
551
727
  var contentType = opts.contentType || "data";
552
- var withAttrs = opts.authenticatedAttributes !== false;
553
728
  // RFC 5652 sec. 9.1: authAttrs MUST be present when the eContentType is not id-data.
554
729
  if (contentType !== "data" && !withAttrs) throw _err("cms/bad-input", "AuthenticatedData with a non-data contentType requires authenticated attributes (RFC 5652 sec. 9.1)");
555
730
 
@@ -558,12 +733,16 @@ async function authenticate(content, recipients, opts) {
558
733
  var macKey = nodeCrypto.randomBytes(MAC_KEY_OCTETS);
559
734
  try {
560
735
  var recips = [];
561
- for (var i = 0; i < recipients.length; i++) recips.push(await _buildRecipient(macKey, recipients[i], opts));
736
+ var consumedAuth = {};
737
+ for (var i = 0; i < recipients.length; i++) recips.push(await _buildRecipient(macKey, recipients[i], opts, consumedAuth));
738
+ _assertDefaultsConsumed(opts, consumedAuth);
562
739
  var riNodes = recips.map(_taggedRecipient);
563
740
 
564
- var digestName = opts.digestAlgorithm || mac.node;
565
741
  var digestAlgTagged = null, authAttrsDer = null, preimage;
566
742
  if (withAttrs) {
743
+ // Inside the branch that uses it: the attribute set is what carries the digest, so on the
744
+ // other branch there is no digest to name and opts.digestAlgorithm is refused at the door.
745
+ var digestName = opts.digestAlgorithm || mac.node;
567
746
  if (!SUPPORTED_DIGEST[digestName]) throw _err("cms/bad-input", "unsupported digestAlgorithm " + JSON.stringify(digestName) + " (sha256/384/512)");
568
747
  // sec. 9.2: content-type (== eContentType) + message-digest (== digest(content)) attributes,
569
748
  // SET-OF-sorted, MACed under the EXPLICIT SET OF tag (0x31) but transmitted [2] IMPLICIT (0xA2).
@@ -573,24 +752,7 @@ async function authenticate(content, recipients, opts) {
573
752
  b.sequence([b.oid(O("messageDigest")), b.setOf([b.octetString(mdDigest)])]),
574
753
  ];
575
754
  if (opts.authAttrs && opts.authAttrs.length) pairs = pairs.concat(opts.authAttrs);
576
- // Every authAttr (auto-built or caller-supplied) MUST be a well-formed Attribute SEQUENCE
577
- // { type OBJECT IDENTIFIER, values SET OF } and each type appears at most once (RFC 5652), so a
578
- // malformed or duplicate caller attribute is rejected before it is MACed and emitted, never left to
579
- // fail an operator's parser downstream.
580
- var seenTypes = {};
581
- pairs.forEach(function (p) {
582
- var node;
583
- try { node = asn1.decode(p); } catch (e) { throw _err("cms/bad-input", "an authenticated attribute is not well-formed DER", e); }
584
- if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children || node.children.length !== 2 ||
585
- node.children[1].tagClass !== "universal" || node.children[1].tagNumber !== asn1.TAGS.SET ||
586
- !node.children[1].children || node.children[1].children.length < 1) {
587
- throw _err("cms/bad-input", "an authenticated attribute must be an Attribute SEQUENCE { type, non-empty SET OF value } (RFC 5652)");
588
- }
589
- var t;
590
- try { t = asn1.read.oid(node.children[0]); } catch (e) { throw _err("cms/bad-input", "an authenticated attribute type is not an OBJECT IDENTIFIER", e); }
591
- if (seenTypes[t]) throw _err("cms/bad-input", "authenticated attributes must not repeat an attribute type (RFC 5652): " + t);
592
- seenTypes[t] = 1;
593
- });
755
+ _assertAuthAttrs(pairs, null);
594
756
  var setOf = b.setOf(pairs);
595
757
  preimage = setOf; // MAC over the 0x31 SET OF
596
758
  authAttrsDer = b.contextConstructed(2, setOf.subarray(_tlvHeaderLen(setOf))); // [2] IMPLICIT on the wire
package/lib/cms-sign.js CHANGED
@@ -43,6 +43,14 @@ var KNOWN_SIGN_OPTS = {
43
43
  signedAttributes: 1, signingTime: 1, additionalSignedAttributes: 1, unsignedAttributes: 1,
44
44
  sid: 1, eContentType: 1, detached: 1, certificates: 1, pem: 1,
45
45
  };
46
+ // The SIGNER descriptor has two forms, and every field it reads is named here so a caller that
47
+ // authors one can be held to the form it selects: a certificate signer, and the key-only signer
48
+ // RFC 5272 sec. 3.2 requires for a Full PKI Request, whose identity comes from `spki` +
49
+ // `keyIdentifier` instead. The signature parameters are read by both. Exported for the verbs that
50
+ // take signer descriptors as AUTHORING input and gate them at their own door -- pki.cms.sign takes
51
+ // them straight from its caller, where a bag carrying extra handles is an ordinary shape.
52
+ var KNOWN_SIGNER_CERT_KEYS = { cert: 1, key: 1, pss: 1, digestAlgorithm: 1, combinedRsaSig: 1 };
53
+ var KNOWN_SIGNER_KEY_ONLY_KEYS = { spki: 1, keyIdentifier: 1, key: 1, pss: 1, digestAlgorithm: 1, combinedRsaSig: 1 };
46
54
  // Countersign has its own set: it takes signerIndex and countersignatureOf to SELECT which
47
55
  // signature it attaches to, and has no content of its own, so eContentType, detached and
48
56
  // unsignedAttributes are not among the options it reads.
@@ -109,7 +117,7 @@ function _buildSid(cert, useSki) {
109
117
  function _buildSignedAttrs(pairs) {
110
118
  var seenTypes = {};
111
119
  var attrs = pairs.map(function (p) {
112
- if (seenTypes[p.type]) throw _err("cms/bad-input", "signedAttrs must not repeat an attribute type (RFC 5652 sec. 5.3): " + p.type);
120
+ if (guard.intrinsic.hasOwn(seenTypes, p.type)) throw _err("cms/bad-input", "signedAttrs must not repeat an attribute type (RFC 5652 sec. 5.3): " + p.type);
113
121
  seenTypes[p.type] = 1;
114
122
  return b.sequence([b.oid(p.type), b.set(p.values)]);
115
123
  });
@@ -147,8 +155,8 @@ function _buildUnsignedAttrs(list) {
147
155
  var pairs = _resolveAttrPairs(list, "an unsigned attribute value");
148
156
  var seen = {};
149
157
  pairs.forEach(function (p) {
150
- if (UNSIGNED_FORBIDDEN[p.type]) throw _err("cms/bad-input", "the " + UNSIGNED_FORBIDDEN[p.type] + " attribute must not appear as an unsigned attribute (RFC 5652 sec. 11)");
151
- if (seen[p.type]) throw _err("cms/bad-input", "unsignedAttrs must not repeat an attribute type (RFC 5652 sec. 5.3): " + p.type);
158
+ if (guard.intrinsic.hasOwn(UNSIGNED_FORBIDDEN, p.type)) throw _err("cms/bad-input", "the " + UNSIGNED_FORBIDDEN[p.type] + " attribute must not appear as an unsigned attribute (RFC 5652 sec. 11)");
159
+ if (guard.intrinsic.hasOwn(seen, p.type)) throw _err("cms/bad-input", "unsignedAttrs must not repeat an attribute type (RFC 5652 sec. 5.3): " + p.type);
152
160
  seen[p.type] = 1;
153
161
  });
154
162
  var setOf = b.set(pairs.map(function (p) { return b.sequence([b.oid(p.type), b.set(p.values)]); }));
@@ -658,4 +666,10 @@ function _countersign(cmsInput, signers, opts) {
658
666
  // belt-and-suspenders against a future caller reordering the two.
659
667
  // * the `crls [1]` preservation branches in `countersign`. pki.cms.sign never emits a crls field,
660
668
  // so a store this producer countersigns never carries one to preserve.
661
- module.exports = { sign: sign, countersign: countersign };
669
+ module.exports = {
670
+ sign: sign, countersign: countersign,
671
+ // @internal -- the signer-descriptor field tables, for a verb that AUTHORS signer descriptors
672
+ // from its own caller's options and gates them at its own door.
673
+ KNOWN_SIGNER_CERT_KEYS: KNOWN_SIGNER_CERT_KEYS,
674
+ KNOWN_SIGNER_KEY_ONLY_KEYS: KNOWN_SIGNER_KEY_ONLY_KEYS,
675
+ };
package/lib/cms-verify.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * @module pki.cms
6
6
  * @nav Verification
7
7
  * @title CMS
8
+ * @fullname CMS (Cryptographic Message Syntax) signing and verification
8
9
  * @intro Verify a CMS SignedData signature (RFC 5652 sec. 5) -- the structure S/MIME signed
9
10
  * mail, RFC 3161 timestamp tokens, and code-signing all rest on. `verify(cms, opts)` parses
10
11
  * the SignedData (over the strict `pki.schema.cms` codec), locates each SignerInfo's signer
@@ -1118,7 +1119,10 @@ var countersign = cmsSign.countersign;
1118
1119
  * @opts oaepHash The RSAES-OAEP hash for ktri recipients: `"sha256"` (default) / `"sha384"` / `"sha512"`.
1119
1120
  * @opts keyIdentifier The recipient identifier form: `"issuerAndSerial"` (default) or `"subjectKeyIdentifier"`.
1120
1121
  * @opts ukm User keying material (a Buffer) for kari / kemri recipients.
1121
- * @opts authAttrs Authenticated attributes (SET OF Attribute) for AuthEnvelopedData.
1122
+ * @opts authAttrs Authenticated attributes (SET OF Attribute) for AuthEnvelopedData. Each must be a
1123
+ * well-formed `Attribute SEQUENCE { type, non-empty SET OF value }` and no type may repeat (RFC 5652
1124
+ * sec. 5.3). `message-digest` is refused: its value is the unencrypted hash of the plaintext, which
1125
+ * enables content tracking and confirms a guessed plaintext (RFC 5083 sec. 2.1, sec. 5).
1122
1126
  * @opts pem Return a PEM string instead of a DER Buffer.
1123
1127
  * @example
1124
1128
  * var rsa = { name: "RSA-OAEP", modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" };
package/lib/constants.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * @module pki.C
6
6
  * @nav Core
7
7
  * @title Constants
8
+ * @fullname Constants: size caps, time units and named bounds
8
9
  * @order 10
9
10
  * @featured true
10
11
  * @slug constants
package/lib/crl-sign.js CHANGED
@@ -6,6 +6,7 @@
6
6
  * @module pki.crl
7
7
  * @nav Signing
8
8
  * @title CRLs
9
+ * @fullname CRLs (certificate revocation lists): read, sign and shard
9
10
  * @intro The X.509 CRL producing side. `pki.crl.sign` builds a `TBSCertList`, signs it, and emits a
10
11
  * `CertificateList` (RFC 5280 sec. 5) that `pki.schema.crl.parse`, `pki.path.crlChecker`, and OpenSSL
11
12
  * all accept -- over any signature algorithm the toolkit registry resolves: RSA (PKCS#1 v1.5 / PSS),
@@ -71,8 +72,38 @@ var REASON_BY_NAME = {};
71
72
  Object.keys(CRL_REASON).forEach(function (v) { REASON_BY_NAME[CRL_REASON[v]] = Number(v); });
72
73
 
73
74
  var KNOWN_CRL_EXT_KEYS = { authorityKeyIdentifier: 1, issuingDistributionPoint: 1, deltaCRLIndicator: 1, freshestCRL: 1, authorityInfoAccess: 1 };
75
+ // The three caller-owned objects pki.crl.sign reads. `crlNumber` and `extensions` are read by
76
+ // _buildCrlExtensions, which _sign hands the whole spec to, so they belong here even though _sign
77
+ // never names them -- the table is derived from what the call ACTUALLY reads, including through
78
+ // its delegations, not from the doc block.
79
+ var KNOWN_SPEC_KEYS = {
80
+ issuer: 1, thisUpdate: 1, nextUpdate: 1, revoked: 1, crlNumber: 1, extensions: 1,
81
+ };
82
+ // spec.issuer is the third place an issuer distinguished name can come from, and it is read only
83
+ // when neither issuer.cert nor issuer.name supplies one: both of those win outright. A spec naming
84
+ // one issuer beside a certificate for another was signed under the certificate's.
85
+ var KNOWN_SPEC_KEYS_ISSUER_NAMED = {
86
+ thisUpdate: 1, nextUpdate: 1, revoked: 1, crlNumber: 1, extensions: 1,
87
+ };
88
+ // Two FORMS, exactly as x509-sign has: when `issuer.cert` is present the builder takes the DN and
89
+ // the public key from it and never reads `name` or `publicKey`, so a union signed under the
90
+ // certificate's identity while discarding the one the caller wrote out.
91
+ var KNOWN_ISSUER_CERT_KEYS = { key: 1, cert: 1 };
92
+ var KNOWN_ISSUER_EXPLICIT_KEYS = { key: 1, name: 1, publicKey: 1 };
93
+ var KNOWN_SIGN_OPTS = { digestAlgorithm: 1, pem: 1, pss: 1 };
94
+
74
95
  var KNOWN_IDP_KEYS = { fullName: 1, onlyContainsUserCerts: 1, onlyContainsCACerts: 1, onlyContainsAttributeCerts: 1, indirectCRL: 1 };
75
96
 
97
+ // The fields ONE revoked-certificate entry carries. Every optional one is encoded only when
98
+ // present, so a misspelling is missed by nothing and the entry is signed without it: `reson` for
99
+ // `reason` lists the certificate as revoked with no reasonCode at all, and a relying party reading
100
+ // the CRL cannot tell a caller who meant keyCompromise from one who gave no reason. The entry is
101
+ // where this bites hardest, because the CRL as a whole still verifies. `certificateIssuer` is
102
+ // listed because the builder DOES read it -- to refuse it with its own message.
103
+ var KNOWN_REVOKED_KEYS = {
104
+ serialNumber: 1, revocationDate: 1, reason: 1, invalidityDate: 1, certificateIssuer: 1, extensions: 1,
105
+ };
106
+
76
107
  // RFC 5280 sec. 5.2 -- the profile-fixed criticality of each recognized CRL extension. cRLNumber (5.2.3),
77
108
  // authorityKeyIdentifier (5.2.1), freshestCRL (5.2.6), and authorityInfoAccess (5.2.7) MUST be non-critical;
78
109
  // issuingDistributionPoint (5.2.5) and deltaCRLIndicator (5.2.4) MUST be critical. Enforced on the object
@@ -311,6 +342,12 @@ function _buildRevoked(entryList, isDelta) {
311
342
  var anyExt = false, seenSerials = {};
312
343
  var entries = entryList.map(function (e, idx) {
313
344
  if (!e || typeof e !== "object" || Buffer.isBuffer(e)) throw _err("crl/bad-input", "each revoked entry must be an object");
345
+ // Before any field of the entry is encoded, so an unrecognized name is a refusal rather than an
346
+ // entry signed without what it asked for.
347
+ guard.identifier.assertKnownKeys(e, KNOWN_REVOKED_KEYS, _err, "crl/bad-input", function (k) {
348
+ return "unknown field " + JSON.stringify(k) + " in revoked entry [" + idx +
349
+ "]; an entry is { serialNumber, revocationDate, reason?, invalidityDate?, extensions? }";
350
+ });
314
351
  if (e.serialNumber == null) throw _err("crl/bad-input", "revoked entry [" + idx + "] requires a serialNumber");
315
352
  var serialTlv = _serialInteger(e.serialNumber);
316
353
  // RFC 5280 sec. 5.1.2.6 -- a serial number identifies a certificate uniquely, so a CRL MUST NOT list the
@@ -398,11 +435,34 @@ function _sign(spec, issuer, opts) {
398
435
  opts = opts || {};
399
436
  if (!spec || typeof spec !== "object" || Buffer.isBuffer(spec)) throw _err("crl/bad-input", "the CRL spec must be an object");
400
437
  issuer = issuer || {};
438
+ // Before any field is read. A dropped key here is worse than elsewhere in the toolkit: a CRL
439
+ // asserts what is revoked, so the failure is a correctly signed, structurally valid CRL saying
440
+ // NOTHING is revoked. `revokedCertificates` earns its own hint because it is the name the parser
441
+ // uses for the same list -- a caller round-tripping a parsed CRL back into sign reaches for it
442
+ // first, and got a signed empty list.
443
+ // The issuer form is settled once, before either door, so the spec table matches the source the
444
+ // distinguished name will actually come from.
445
+ var byCert = issuer.cert != null, byName = issuer.name != null;
446
+ guard.identifier.assertKnownKeys(spec, byCert || byName ? KNOWN_SPEC_KEYS_ISSUER_NAMED : KNOWN_SPEC_KEYS,
447
+ _err, "crl/bad-input", function (k) {
448
+ return "unknown CRL spec field " + JSON.stringify(k) +
449
+ (k === "revokedCertificates" ? "; the producer reads `revoked` (the parser reports the same list as `revokedCertificates`)"
450
+ : k === "issuer" ? "; the issuer argument already names one, through " + (byCert ? "issuer.cert" : "issuer.name") +
451
+ ", and that is the name the CRL is signed under"
452
+ : "");
453
+ });
454
+ guard.identifier.assertKnownKeys(issuer, byCert ? KNOWN_ISSUER_CERT_KEYS : KNOWN_ISSUER_EXPLICIT_KEYS,
455
+ _err, "crl/bad-input", function (k) {
456
+ return "unknown issuer field " + JSON.stringify(k) + " for the " + (byCert
457
+ ? "certificate form; issuer.cert supplies the name and the public key, so they are not read here"
458
+ : "explicit form; the issuer takes { key, name, publicKey }, or { key, cert } to take both from a certificate");
459
+ });
460
+ guard.identifier.assertKnownKeys(opts, KNOWN_SIGN_OPTS, _err, "crl/bad-input", "pki.crl.sign has an unknown option ");
401
461
  if (issuer.key == null) throw _err("crl/bad-input", "a signing key (issuer.key, a PKCS#8 private key) is required");
402
462
 
403
463
  // Resolve the issuer name + signing-key SPKI. `{ cert }` supplies both; else `{ name|spec.issuer, publicKey }`.
404
464
  var issuerDer, issuerSpki, issuerCert = null;
405
- if (issuer.cert != null) {
465
+ if (byCert) {
406
466
  issuerCert = _parseIssuerCert(issuer.cert);
407
467
  _assertIssuerCanSignCrl(issuerCert);
408
468
  issuerDer = pkiBuild.tbsNameField(issuerCert, "subject");
@@ -410,7 +470,7 @@ function _sign(spec, issuer, opts) {
410
470
  } else {
411
471
  issuerSpki = _reqDer(issuer.publicKey, "issuer.publicKey (the issuer SPKI DER)");
412
472
  _assertValidSpki(issuerSpki, "issuer.publicKey");
413
- var dnSource = issuer.name != null ? issuer.name : spec.issuer;
473
+ var dnSource = byName ? issuer.name : spec.issuer;
414
474
  if (dnSource == null) throw _err("crl/bad-issuer", "an issuer distinguished name is required (issuer.name or spec.issuer) when no issuer.cert is given");
415
475
  issuerDer = _encodeName(dnSource);
416
476
  }
package/lib/crmf-sign.js CHANGED
@@ -6,6 +6,7 @@
6
6
  * @module pki.crmf
7
7
  * @nav Signing
8
8
  * @title Certificate request messages
9
+ * @fullname CRMF (Certificate Request Message Format, RFC 4211)
9
10
  * @intro The RFC 4211 certificate-request-message producing side. `pki.crmf.build` assembles a
10
11
  * `CertReqMessages` -- one or more `CertReqMsg`, each a `CertRequest` (a `CertTemplate` of the requested
11
12
  * certificate fields plus optional controls) paired with a proof of possession. The common proof is a
package/lib/csr-sign.js CHANGED
@@ -6,6 +6,7 @@
6
6
  * @module pki.csr
7
7
  * @nav Signing
8
8
  * @title Certification requests
9
+ * @fullname CSRs (PKCS#10 certification requests): build, sign and verify
9
10
  * @intro The PKCS#10 certification-request producing side. `pki.csr.sign` builds a
10
11
  * `CertificationRequestInfo`, signs it with the subject's own private key (proof of possession, since
11
12
  * a CSR has no issuer), and emits a `CertificationRequest` (RFC 2986) that `pki.schema.csr.parse`,
package/lib/ct.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * @module pki.ct
6
6
  * @nav Transparency
7
7
  * @title CT
8
+ * @fullname Certificate Transparency (CT) logs and SCTs
8
9
  * @order 210
9
10
  * @slug ct
10
11
  *
package/lib/est.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * @module pki.est
6
6
  * @nav Schema
7
7
  * @title EST
8
+ * @fullname EST (Enrollment over Secure Transport, RFC 7030)
8
9
  * @order 190
9
10
  * @slug est
10
11
  *
@@ -5,6 +5,7 @@
5
5
  * @module pki.errors
6
6
  * @nav Core
7
7
  * @title Errors
8
+ * @fullname Error classes and the stable domain/reason codes they carry
8
9
  * @order 20
9
10
  * @slug errors
10
11
  *