@blamejs/pki 0.5.15 → 0.5.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/lib/acme.js +1 -0
  3. package/lib/asn1-der.js +1 -0
  4. package/lib/attrcert-sign.js +1 -0
  5. package/lib/cbor-det.js +1 -0
  6. package/lib/cmc-verify.js +1 -0
  7. package/lib/cmp-build.js +1 -0
  8. package/lib/cmp-verify.js +425 -82
  9. package/lib/cms-decrypt.js +58 -29
  10. package/lib/cms-encrypt.js +45 -24
  11. package/lib/cms-sign.js +3 -3
  12. package/lib/cms-verify.js +5 -1
  13. package/lib/constants.js +1 -0
  14. package/lib/crl-sign.js +1 -0
  15. package/lib/crmf-sign.js +1 -0
  16. package/lib/csr-sign.js +1 -0
  17. package/lib/ct.js +1 -0
  18. package/lib/est.js +1 -0
  19. package/lib/framework-error.js +1 -0
  20. package/lib/guard-all.js +2 -0
  21. package/lib/guard-async.js +14 -2
  22. package/lib/guard-bytes.js +183 -93
  23. package/lib/guard-compress.js +32 -12
  24. package/lib/guard-der.js +30 -7
  25. package/lib/guard-encoding.js +28 -12
  26. package/lib/guard-header.js +18 -7
  27. package/lib/guard-identifier.js +146 -79
  28. package/lib/guard-intrinsic.js +318 -0
  29. package/lib/guard-json.js +39 -12
  30. package/lib/guard-limits.js +15 -6
  31. package/lib/guard-name.js +72 -23
  32. package/lib/guard-parsed.js +71 -34
  33. package/lib/guard-range.js +17 -4
  34. package/lib/guard-secret.js +60 -2
  35. package/lib/guard-text.js +35 -6
  36. package/lib/guard-time.js +13 -4
  37. package/lib/hpke.js +6 -2
  38. package/lib/http-transport.js +1 -0
  39. package/lib/inspect.js +1 -0
  40. package/lib/jose.js +1 -0
  41. package/lib/key.js +2 -1
  42. package/lib/lint.js +1 -0
  43. package/lib/merkle.js +1 -0
  44. package/lib/ocsp.js +1 -0
  45. package/lib/oid.js +1 -0
  46. package/lib/path-validate.js +1 -0
  47. package/lib/pbes2.js +16 -8
  48. package/lib/pkcs12-build.js +2 -1
  49. package/lib/schema-all.js +1 -0
  50. package/lib/schema-c509.js +1 -0
  51. package/lib/schema-csrattrs.js +1 -0
  52. package/lib/schema-engine.js +1 -0
  53. package/lib/schema-pkcs8.js +1 -0
  54. package/lib/schema-smime.js +1 -0
  55. package/lib/schema-tsp.js +1 -0
  56. package/lib/schema-x509.js +1 -0
  57. package/lib/shbs.js +1 -0
  58. package/lib/sigstore.js +1 -0
  59. package/lib/smime.js +1 -0
  60. package/lib/tls-cert-compress.js +1 -0
  61. package/lib/trust.js +1 -0
  62. package/lib/tsp-sign.js +1 -0
  63. package/lib/webauthn.js +1 -0
  64. package/lib/webcrypto.js +11 -8
  65. package/lib/x509-sign.js +1 -0
  66. package/package.json +1 -1
  67. package/sbom.cdx.json +6 -6
@@ -29,6 +29,10 @@ var subtle = webcrypto.webcrypto.subtle;
29
29
  var CmsError = frameworkError.CmsError;
30
30
  var WRAP_KEK_LENGTHS = schemaCms.WRAP_KEK_LENGTHS;
31
31
  var KEM_CT_LENGTHS = schemaCms.KEM_CT_LENGTHS;
32
+ // The parser's own AEAD table: the single place that decides whether a content-encryption OID names
33
+ // an authenticated encryption algorithm, so the container-vs-mode rule below cannot drift from the
34
+ // parameter validation that accepted the message.
35
+ var AEAD_ALGS = schemaCms.AEAD_ALGS;
32
36
 
33
37
  function O(n) { return oid.byName(n); }
34
38
  function _err(code, message, cause) { return new CmsError(code, message, cause); }
@@ -397,26 +401,45 @@ async function _kemriCek(ri, km) {
397
401
  }
398
402
 
399
403
  // The declared cipher MODE must match the container's authentication model: an AEAD cipher belongs in
400
- // AuthEnvelopedData and a plain CBC cipher in EnvelopedData (RFC 5083 sec. 2.1 / RFC 5084 sec. 3). Key
401
- // length alone does not separate them, so without this an EnvelopedData naming AES-GCM was opened as
402
- // unauthenticated CBC while the result still reported the AEAD algorithm, telling the caller the
403
- // content was authenticated when nothing had authenticated it. The mirror case dereferenced AEAD
404
- // parameters the parser leaves null for a non-AEAD algorithm, faulting instead of failing closed.
405
- // An unresolvable OID is left to _openContent, which names it as the unsupported algorithm it is.
404
+ // AuthEnvelopedData and a plain CBC cipher in EnvelopedData (RFC 5083 sec. 2). Key length alone does
405
+ // not separate them, so without this an EnvelopedData naming AES-GCM was opened as unauthenticated CBC
406
+ // while the result still reported the AEAD algorithm, telling the caller the content was authenticated
407
+ // when nothing had authenticated it. The mirror case dereferenced AEAD parameters the parser leaves
408
+ // null for a non-AEAD algorithm, faulting instead of failing closed.
409
+ //
410
+ // The line drawn here is AEAD-vs-not, which is the line RFC 5083 draws. GCM-vs-CBC is a narrower
411
+ // line this toolkit draws, because AES-GCM is the only AEAD it implements, and stating the narrower
412
+ // one as though the RFC required it sends an operator to argue with a specification that says the
413
+ // opposite: RFC 5084 sec. 3.1 defines AES-CCM for this content type. So an AEAD reaching the right
414
+ // container passes here even when decryption will later decline it, and it is _openContent that says
415
+ // why, by name. An unresolvable OID is left to _openContent too, as the unsupported algorithm it is.
406
416
  function _assertContentCipherMode(eci, ct) {
407
417
  var oidStr = eci.contentEncryptionAlgorithm.oid;
408
- if (!CONTENT_MODE[oidStr]) return;
409
- var wantMode = ct === "authEnvelopedData" ? "gcm" : "cbc";
410
- if (CONTENT_MODE[oidStr] !== wantMode) {
411
- throw _err("cms/unsupported-algorithm", "contentEncryptionAlgorithm " + oidStr + " is not a " + wantMode.toUpperCase() + " cipher, which " + ct + " requires (RFC 5083 sec. 2.1 / RFC 5084 sec. 3)");
418
+ var isAead = guard.intrinsic.hasOwn(AEAD_ALGS, oidStr);
419
+ if (!isAead && !guard.intrinsic.hasOwn(CONTENT_MODE, oidStr)) return;
420
+ if (ct === "authEnvelopedData") {
421
+ if (isAead) return;
422
+ throw _err("cms/unsupported-algorithm", "contentEncryptionAlgorithm " + oidStr + " is not an authenticated encryption algorithm, which authEnvelopedData requires (RFC 5083 sec. 2)");
412
423
  }
424
+ if (!isAead) return;
425
+ throw _err("cms/unsupported-algorithm", "contentEncryptionAlgorithm " + oidStr + " is an authenticated encryption algorithm, which belongs in an authEnvelopedData rather than a " + ct + " (RFC 5083 sec. 2)");
413
426
  }
414
427
 
415
428
  // ---- stage 3: open the content (uniform failure) ---------------------------
416
429
  async function _openContent(parsed, eci, cek, ct) {
417
430
  var alg = eci.contentEncryptionAlgorithm;
418
- var keyBits = CONTENT_KEYBITS[alg.oid];
419
- if (!keyBits) throw _err("cms/unsupported-algorithm", "unsupported contentEncryptionAlgorithm " + alg.oid);
431
+ var keyBits = guard.intrinsic.hasOwn(CONTENT_KEYBITS, alg.oid) ? CONTENT_KEYBITS[alg.oid] : 0;
432
+ if (!keyBits) {
433
+ // AES-CCM parses completely here -- registered OIDs, the sec. 3.1 nonce bound, the seven legal
434
+ // ICV lengths -- and is then declined, because AES-GCM is the only AEAD this toolkit implements
435
+ // for content encryption. Saying only "unsupported <oid>" is what an operator also sees for an
436
+ // OID nothing in the toolkit recognizes, and the two call for different things: one is a
437
+ // capability gap to route around, the other is a malformed or exotic message.
438
+ if (guard.intrinsic.hasOwn(AEAD_ALGS, alg.oid) && AEAD_ALGS[alg.oid] === "ccm") {
439
+ throw _err("cms/unsupported-algorithm", "contentEncryptionAlgorithm " + alg.oid + " is AES-CCM. RFC 5084 sec. 3.1 permits it for this content type; this toolkit implements AES-GCM only for content encryption");
440
+ }
441
+ throw _err("cms/unsupported-algorithm", "unsupported contentEncryptionAlgorithm " + alg.oid);
442
+ }
420
443
  if (eci.encryptedContent == null) throw _err("cms/no-encrypted-content", "the message has no encryptedContent (detached; supply it out of band)");
421
444
  // A null CEK (v1.5 implicit rejection) or a wrong-length CEK -> a fresh random key of the right
422
445
  // length, so the failure surfaces here as the uniform verdict, never earlier.
@@ -435,7 +458,7 @@ async function _openContent(parsed, eci, cek, ct) {
435
458
  }
436
459
  var iv = asn1.read.octetString(asn1.decode(alg.parameters));
437
460
  if (iv.length !== 16) throw _fail();
438
- return pbes2.cbcDecrypt(cek, iv, eci.encryptedContent, keyBits);
461
+ return pbes2.cbcDecrypt(cek, iv, eci.encryptedContent, keyBits, CmsError, "cms/decrypt-failed");
439
462
  } catch (e) {
440
463
  if (e instanceof CmsError && e.code !== "cms/decrypt-failed") throw e;
441
464
  throw _fail();
@@ -450,7 +473,10 @@ function _gcmOpen(cek, nonce, ct, tag, aad, keyBits, icvLen) {
450
473
  var d = nodeCrypto.createDecipheriv("aes-" + keyBits + "-gcm", cek, nonce, { authTagLength: icvLen });
451
474
  d.setAuthTag(tag);
452
475
  if (aad && aad.length) d.setAAD(aad);
453
- return Buffer.concat([d.update(ct), d.final()]); // final() throws on tag mismatch -> uniform
476
+ // final() throws on a tag mismatch, and update() has already produced the whole plaintext by
477
+ // then. RFC 5083 sec. 1 requires a receiver whose integrity check fails to DESTROY the plaintext,
478
+ // not merely withhold it, so the intermediate is cleared on both exits.
479
+ return guard.secret.cipherFinish(d, ct, CmsError, "cms/decrypt-failed", "the recovered content");
454
480
  }
455
481
 
456
482
  // ---- EncryptedData (sec. 8) ------------------------------------------------
@@ -465,7 +491,7 @@ async function _decryptEncryptedData(parsed, km, opts) {
465
491
  var cek = guard.bytes.view(km.cek, CmsError, "cms/bad-input", "cek");
466
492
  if (cek.length !== keyBits / 8) throw _err("cms/bad-input", "the supplied cek length does not match the content algorithm");
467
493
  var iv = asn1.read.octetString(asn1.decode(alg.parameters));
468
- try { return _originFields({ content: pbes2.cbcDecrypt(cek, iv, eci.encryptedContent, keyBits), contentType: eci.contentType, contentTypeName: oid.name(eci.contentType) || eci.contentType, recipientType: "cek", recipientIndex: -1, contentEncryptionAlgorithm: alg.name || alg.oid, authenticated: false }, null, null); }
494
+ try { return _originFields({ content: pbes2.cbcDecrypt(cek, iv, eci.encryptedContent, keyBits, CmsError, "cms/decrypt-failed"), contentType: eci.contentType, contentTypeName: oid.name(eci.contentType) || eci.contentType, recipientType: "cek", recipientIndex: -1, contentEncryptionAlgorithm: alg.name || alg.oid, authenticated: false }, null, null); }
469
495
  catch (_e) { throw _fail(); }
470
496
  }
471
497
  async function _decryptPbes2(parsed, eci, km, opts) {
@@ -497,7 +523,7 @@ async function _decryptPbes2(parsed, eci, km, opts) {
497
523
  var key;
498
524
  try { key = nodeCrypto.pbkdf2Sync(pwE.bytes, pb.salt, pb.iterations, keyBits / 8, pb.prfNode); }
499
525
  finally { if (pwE.owned) guard.secret.zeroize(pwE.bytes, CmsError, "cms/bad-input", "the password encoding"); }
500
- try { return _originFields({ content: pbes2.cbcDecrypt(key, iv, eci.encryptedContent, keyBits), contentType: eci.contentType, contentTypeName: oid.name(eci.contentType) || eci.contentType, recipientType: "password", recipientIndex: -1, contentEncryptionAlgorithm: oid.name(encOid) || encOid, authenticated: false }, null, null); }
526
+ try { return _originFields({ content: pbes2.cbcDecrypt(key, iv, eci.encryptedContent, keyBits, CmsError, "cms/decrypt-failed"), contentType: eci.contentType, contentTypeName: oid.name(eci.contentType) || eci.contentType, recipientType: "password", recipientIndex: -1, contentEncryptionAlgorithm: oid.name(encOid) || encOid, authenticated: false }, null, null); }
501
527
  catch (_e) { throw _fail(); }
502
528
  finally { guard.secret.zeroize(key, CmsError, "cms/bad-input", "the password-derived content-encryption key"); }
503
529
  }
@@ -548,27 +574,30 @@ function _pwriUnwrap(kek, wrapped, iv, keyBits) {
548
574
  // pass2 = CBC(kek, iv2 = pass1[last block], pass1). Recover pass1's last block by decrypting the
549
575
  // last ciphertext block (ECB) and XORing the previous ciphertext block, a standard CBC last-
550
576
  // block decrypt that needs no IV, then CBC-decrypt pass2 under that recovered iv2 to get pass1.
551
- var ecb = nodeCrypto.createDecipheriv("aes-" + keyBits + "-ecb", kek, Buffer.alloc(0)); ecb.setAutoPadding(false);
552
- var lastDec = Buffer.concat([ecb.update(wrapped.subarray(n - blk)), ecb.final()]);
553
- var iv2 = Buffer.alloc(blk);
554
- for (var i = 0; i < blk; i++) iv2[i] = lastDec[i] ^ wrapped[n - 2 * blk + i];
555
- var d1 = nodeCrypto.createDecipheriv(alg, kek, iv2); d1.setAutoPadding(false);
556
- var pass1 = Buffer.concat([d1.update(wrapped), d1.final()]);
557
- var d2 = nodeCrypto.createDecipheriv(alg, kek, iv); d2.setAutoPadding(false);
558
- // body is the recovered plaintext block: it holds the CEK. The caller receives an independent
559
- // copy, so the block is cleared on every exit, including the two validation rejects, which are
560
- // the paths an attacker induces by tampering with the wrapped key.
561
- var body = Buffer.concat([d2.update(pass1), d2.final()]);
577
+ // Every intermediate below is derived from the KEK and reveals key material, so all three are
578
+ // named before the try and cleared in one finally. pass1 and body hold the wrapped key in the
579
+ // clear; lastDec is pass1's final block, recovered ahead of it. The two validation rejects are
580
+ // inside the same scope because they are the paths an attacker induces by tampering with the
581
+ // wrapped key, and they are where an unswept intermediate would otherwise be abandoned.
582
+ var lastDec = null, pass1 = null, body = null;
562
583
  try {
584
+ var ecb = nodeCrypto.createDecipheriv("aes-" + keyBits + "-ecb", kek, Buffer.alloc(0)); ecb.setAutoPadding(false);
585
+ lastDec = guard.secret.cipherFinish(ecb, wrapped.subarray(n - blk), CmsError, "cms/decrypt-failed", "the recovered CBC last block");
586
+ var iv2 = Buffer.alloc(blk);
587
+ for (var i = 0; i < blk; i++) iv2[i] = lastDec[i] ^ wrapped[n - 2 * blk + i];
588
+ var d1 = nodeCrypto.createDecipheriv(alg, kek, iv2); d1.setAutoPadding(false);
589
+ pass1 = guard.secret.cipherFinish(d1, wrapped, CmsError, "cms/decrypt-failed", "the PWRI first-pass plaintext");
590
+ var d2 = nodeCrypto.createDecipheriv(alg, kek, iv); d2.setAutoPadding(false);
591
+ body = guard.secret.cipherFinish(d2, pass1, CmsError, "cms/decrypt-failed", "the PWRI plaintext block");
563
592
  var count = body[0];
564
593
  if (count < 1 || count + 4 > body.length) throw _fail();
565
594
  var cek = body.subarray(4, 4 + count);
566
595
  var bad = 0;
567
596
  for (var j = 0; j < 3; j++) bad |= (body[1 + j] ^ 0xff) ^ cek[j]; // complement check bytes
568
597
  if (bad !== 0) throw _fail();
569
- return Buffer.from(cek);
598
+ return Buffer.from(cek); // the caller's copy, independent of the block cleared below
570
599
  } finally {
571
- guard.secret.zeroizeAll([body, pass1], CmsError, "cms/bad-input", "the PWRI plaintext block");
600
+ guard.secret.zeroizeAll([body, pass1, lastDec], CmsError, "cms/bad-input", "the PWRI plaintext block");
572
601
  }
573
602
  }
574
603
  // Coverage residual (the unsupported-algorithm throw arm of each lookup below: _hashW3c,
@@ -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
  }
@@ -432,11 +432,48 @@ function _emit(inner, ctName, opts) {
432
432
 
433
433
  function _envelopedData(contentBytes, cek, ca, contentType, riNodes, recips) {
434
434
  var iv = nodeCrypto.randomBytes(16);
435
- var enc = pbes2.cbcEncrypt(cek, iv, contentBytes, ca.keyBits);
435
+ var enc = pbes2.cbcEncrypt(cek, iv, contentBytes, ca.keyBits, CmsError, "cms/bad-input");
436
436
  var eci = b.sequence([b.oid(O(contentType)), b.sequence([b.oid(O(ca.oid)), b.octetString(iv)]), b.contextPrimitive(0, enc)]);
437
437
  return b.sequence([b.integer(BigInt(_envelopedVersion(recips, false))), b.setOf(riNodes), eci]);
438
438
  }
439
439
 
440
+ // Every authenticated attribute this module MACs -- auto-built or caller-supplied, in either content
441
+ // type that carries them -- MUST be a well-formed Attribute SEQUENCE { type OBJECT IDENTIFIER,
442
+ // non-empty SET OF value } with no repeated type (RFC 5652 sec. 5.3). Rejecting here means a
443
+ // malformed attribute never reaches the wire; leaving it to the wire means the operator learns from
444
+ // a peer's parser that the message they built is unreadable, with the MAC already computed over it.
445
+ //
446
+ // `forbidden` names an attribute type this particular content type must not emit, with the clause
447
+ // that says so. It is a parameter rather than a constant because the same attribute is required in
448
+ // one content type and discouraged in the other: AuthenticatedData MACs its own message-digest by
449
+ // design (RFC 5652 sec. 9.2), while for AuthEnvelopedData that value is the unencrypted hash of a
450
+ // plaintext the message exists to conceal (RFC 5083 sec. 2.1 / sec. 5).
451
+ function _assertAuthAttrs(pairs, forbidden) {
452
+ var seenTypes = {};
453
+ pairs.forEach(function (p) {
454
+ var node;
455
+ try { node = asn1.decode(p); } catch (e) { throw _err("cms/bad-input", "an authenticated attribute is not well-formed DER", e); }
456
+ if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children || node.children.length !== 2 ||
457
+ node.children[1].tagClass !== "universal" || node.children[1].tagNumber !== asn1.TAGS.SET ||
458
+ !node.children[1].children || node.children[1].children.length < 1) {
459
+ throw _err("cms/bad-input", "an authenticated attribute must be an Attribute SEQUENCE { type, non-empty SET OF value } (RFC 5652)");
460
+ }
461
+ var t;
462
+ 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); }
463
+ if (guard.intrinsic.hasOwn(seenTypes, t)) throw _err("cms/bad-input", "authenticated attributes must not repeat an attribute type (RFC 5652): " + t);
464
+ seenTypes[t] = 1;
465
+ if (forbidden && t === forbidden.oid) throw _err("cms/bad-input", forbidden.why);
466
+ });
467
+ }
468
+
469
+ // The one attribute AuthEnvelopedData declines to emit. Its value is the unencrypted one-way hash of
470
+ // the plaintext, so disclosing it enables content tracking and confirms a guessed plaintext against
471
+ // a message that was encrypted to prevent exactly that.
472
+ var AUTH_ENVELOPED_FORBIDDEN = {
473
+ oid: O("messageDigest"),
474
+ 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)",
475
+ };
476
+
440
477
  function _authEnvelopedData(contentBytes, cek, ca, contentType, opts, riNodes, recips) {
441
478
  var nonce = nodeCrypto.randomBytes(12);
442
479
  var authAttrsDer = null, aad = Buffer.alloc(0);
@@ -446,6 +483,7 @@ function _authEnvelopedData(contentBytes, cek, ca, contentType, opts, riNodes, r
446
483
  throw _err("cms/bad-input", "AuthEnvelopedData with a non-data contentType requires authAttrs (RFC 5083 sec. 2.1)");
447
484
  }
448
485
  if (opts.authAttrs && opts.authAttrs.length) {
486
+ _assertAuthAttrs(opts.authAttrs, AUTH_ENVELOPED_FORBIDDEN);
449
487
  // authAttrs are transmitted [1] IMPLICIT but MACed under the EXPLICIT SET OF tag (RFC 5083 sec. 2.2).
450
488
  var setOf = b.setOf(opts.authAttrs);
451
489
  aad = setOf; authAttrsDer = b.contextConstructed(1, setOf.subarray(_tlvHeaderLen(setOf)));
@@ -475,7 +513,7 @@ function _encryptedData(contentBytes, desc, ca, contentType, opts, cek) {
475
513
  } else {
476
514
  throw _err("cms/bad-input", "EncryptedData needs a single { cek } or { password } descriptor");
477
515
  }
478
- var enc = pbes2.cbcEncrypt(encKey, iv, contentBytes, ca.keyBits);
516
+ var enc = pbes2.cbcEncrypt(encKey, iv, contentBytes, ca.keyBits, CmsError, "cms/bad-input");
479
517
  var eci = b.sequence([b.oid(O(contentType)), contentAlgNode, b.contextPrimitive(0, enc)]);
480
518
  var inner = b.sequence([b.integer(0n), eci]);
481
519
  return _emit(inner, "encryptedData", opts);
@@ -495,7 +533,7 @@ function _encryptedDataPbes2(contentBytes, desc, ca, contentType, iv, opts) {
495
533
  var key = nodeCrypto.pbkdf2Sync(password, salt, iterations, ca.keyBits / 8, pbes2.prfNodeByName(prf, _err, "cms"));
496
534
  if (pwOwn2.owned) guard.secret.zeroize(password, CmsError, "cms/bad-input", "the password encoding");
497
535
  try {
498
- var enc = pbes2.cbcEncrypt(key, iv, contentBytes, ca.keyBits);
536
+ var enc = pbes2.cbcEncrypt(key, iv, contentBytes, ca.keyBits, CmsError, "cms/bad-input");
499
537
  var contentAlg = pbes2.pbes2AlgId(salt, iterations, prf, ca.oid, iv);
500
538
  var eci = b.sequence([b.oid(O(contentType)), contentAlg, b.contextPrimitive(0, enc)]);
501
539
  var inner = b.sequence([b.integer(0n), eci]);
@@ -509,7 +547,7 @@ function _encryptedDataPbes2(contentBytes, desc, ca, contentType, iv, opts) {
509
547
  function _gcmEncrypt(key, nonce, plaintext, aad, keyBits, tagLen) {
510
548
  var c = nodeCrypto.createCipheriv("aes-" + keyBits + "-gcm", key, nonce, { authTagLength: tagLen });
511
549
  if (aad && aad.length) c.setAAD(aad);
512
- var ct = Buffer.concat([c.update(plaintext), c.final()]);
550
+ var ct = guard.secret.cipherFinish(c, plaintext, CmsError, "cms/bad-input", "a content-encryption intermediate");
513
551
  return { ct: ct, tag: c.getAuthTag() };
514
552
  }
515
553
 
@@ -573,24 +611,7 @@ async function authenticate(content, recipients, opts) {
573
611
  b.sequence([b.oid(O("messageDigest")), b.setOf([b.octetString(mdDigest)])]),
574
612
  ];
575
613
  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
- });
614
+ _assertAuthAttrs(pairs, null);
594
615
  var setOf = b.setOf(pairs);
595
616
  preimage = setOf; // MAC over the 0x31 SET OF
596
617
  authAttrsDer = b.contextConstructed(2, setOf.subarray(_tlvHeaderLen(setOf))); // [2] IMPLICIT on the wire
package/lib/cms-sign.js CHANGED
@@ -109,7 +109,7 @@ function _buildSid(cert, useSki) {
109
109
  function _buildSignedAttrs(pairs) {
110
110
  var seenTypes = {};
111
111
  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);
112
+ 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
113
  seenTypes[p.type] = 1;
114
114
  return b.sequence([b.oid(p.type), b.set(p.values)]);
115
115
  });
@@ -147,8 +147,8 @@ function _buildUnsignedAttrs(list) {
147
147
  var pairs = _resolveAttrPairs(list, "an unsigned attribute value");
148
148
  var seen = {};
149
149
  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);
150
+ 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)");
151
+ 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
152
  seen[p.type] = 1;
153
153
  });
154
154
  var setOf = b.set(pairs.map(function (p) { return b.sequence([b.oid(p.type), b.set(p.values)]); }));
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),
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
  *
package/lib/guard-all.js CHANGED
@@ -67,6 +67,7 @@ var compress = require("./guard-compress");
67
67
  var secret = require("./guard-secret");
68
68
  var parsed = require("./guard-parsed");
69
69
  var async_ = require("./guard-async");
70
+ var intrinsic = require("./guard-intrinsic");
70
71
 
71
72
  module.exports = {
72
73
  bytes: bytes,
@@ -85,4 +86,5 @@ module.exports = {
85
86
  secret: secret,
86
87
  parsed: parsed,
87
88
  async: async_,
89
+ intrinsic: intrinsic,
88
90
  };
@@ -1,6 +1,15 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // Copyright (c) blamejs contributors
3
3
  "use strict";
4
+
5
+ // The two settle operations, taken at load and uncurried so the call sites read no property of
6
+ // them. These are how a synchronous fault becomes the rejection this guard promises, so a replaced
7
+ // `reject` returning a resolved promise turns a refusal into a pass at the one place the exit shape
8
+ // is decided. See guard-intrinsic for the whole captured set.
9
+ var _intrinsic = require("./guard-intrinsic");
10
+ var _resolve = _intrinsic.uncurry(_intrinsic.promiseResolve);
11
+ var _reject = _intrinsic.uncurry(_intrinsic.promiseReject);
12
+ var _Promise = Promise;
4
13
  //
5
14
  // @internal -- no operator-facing namespace. The documented surface is every verb whose
6
15
  // @signature says `-> Promise<...>`; this is how each of them refuses.
@@ -29,9 +38,12 @@
29
38
  // test/layer-0-primitives/promise-contract.test.js, which reads every `-> Promise<` @signature out
30
39
  // of lib/ and calls each verb to prove the refusal arrives as a rejection. A new verb is in
31
40
  // scope the day it is documented, so no list here can go stale.
41
+ // Both taken at load. These are how a synchronous fault becomes the rejection this guard promises,
42
+ // so a replaced `reject` returning a resolved promise turns a refusal into a pass at the one place
43
+ // the exit shape is decided. See guard-intrinsic for the whole captured set.
32
44
  function deferred(body) {
33
- try { return Promise.resolve(body()); }
34
- catch (e) { return Promise.reject(e); }
45
+ try { return _resolve(_Promise, body()); }
46
+ catch (e) { return _reject(_Promise, e); }
35
47
  }
36
48
 
37
49
  module.exports = { deferred: deferred };