@blamejs/pki 0.5.17 → 0.5.19

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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,66 @@ All notable changes to `@blamejs/pki` are documented here. The format
4
4
  follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this
5
5
  project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## v0.5.19 — 2026-08-20
8
+
9
+ The crypto engine and the JWS signer decide with operations captured at load, so code that runs after the module does cannot change what a permission check concludes.
10
+
11
+ ### Changed
12
+
13
+ - `pki.webcrypto` and `pki.jose` are held to the live-read rule the guard family already follows: the check over `lib/` that refuses reading an operation from the runtime at call time covers every module that takes the captures, and these two now do. Operator-visible behavior is unchanged except where a refusal that could be bypassed now holds.
14
+
15
+ ### Fixed
16
+
17
+ - The WebCrypto key-usage check holds against a replaced `Array.prototype.indexOf`. The membership test IS the permission decision, so it runs through the captured method: dispatched live, a key permitted only for `verify` passed the engine's gate for `sign` and failed later, in the platform's own crypto, with an untyped fault instead of `webcrypto/invalid-access`.
18
+ - A CryptoKey's usage list is a copy this engine took with a captured `slice`, so a replaced one cannot leave the caller holding the array the key is checked against for the rest of its life. The same applies to the partition that decides which usages each half of a generated pair is minted with.
19
+ - `pki.key.import`, `pki.key.generate` and `subtle.generateKey` refuse a non-array `usages` by name. It reached the key constructor, where `Array.prototype.slice` on a number yields an empty list, so the call minted a key permitted for nothing rather than naming the argument that was wrong. The asymmetric branch of `generateKey` partitions the usages before either half is constructed and `filter` treats a non-list the same way, so it is checked at that verb's own door and the whole pair is refused.
20
+ - The AES-GCM ciphertext and authentication tag are split with a captured `subarray`, so a replaced one cannot hand the tag check a different range of the same buffer.
21
+ - A JWK thumbprint (RFC 7638 sec. 3.3) is built with a captured serializer. The canonical JSON is the thumbprint's preimage, so a replaced `JSON.stringify` changes the identity every consumer compares keys by.
22
+ - The JOSE header checks -- own-name reads, the `crit` list walk, the embedded-JWK shape test -- and the protected-header serialization all run on captured operations, so a caller who reaches the header through an accessor cannot decide which of them apply.
23
+ - A digest runs under the algorithm the caller named. The hash name is case-folded to pick its registry row, and dispatched live a replaced `String.prototype.toUpperCase` answering `SHA-1` gave a caller who asked for SHA-256 a 20-byte digest. The same fold picks the HMAC block size and decides whether a key's algorithm matches the operation's.
24
+ - `getRandomValues` settles the kinds WebCrypto refuses from the value's internal slot rather than by `instanceof` against a replaceable global, so a real `Float32Array` cannot pass a check written to reject it by swapping the constructor the comparison names.
25
+
26
+ ## v0.5.18 — 2026-08-20
27
+
28
+ Every producing verb refuses an authoring field it does not read, so a misspelling is a refusal at the call instead of a signed artifact that quietly omits what was asked for.
29
+
30
+ ### Changed
31
+
32
+ - The issuer argument of `pki.x509.sign` and `pki.crl.sign` accepts only the fields the form it selects reads: `{ key, cert }` when an issuing certificate is supplied, `{ key, name, publicKey }` otherwise. A caller who hands over a key-store object carrying anything else must narrow it to one of the two. The check is there because a misspelled `cert` is not otherwise refused on this argument -- the issuing certificate is optional, so a dropped one falls through to self-signing, and a cross-certificate issued that way names its own subject as its issuer. Supplying both forms is refused for the same reason: the certificate branch wins unconditionally and reads neither `name` nor `publicKey`, so a caller who named a different issuer got one issued under the certificate's own distinguished name.
33
+ - The TSA argument of `pki.tsp.sign` accepts only the certificate and key it reads. A misspelling of either was always refused for what it left missing, but that is half the class: a name belonging on the third argument, written on the TSA instead, is read by nothing there. `ordering` placed on the TSA emitted a token the same size as one that never asked for it, with the flag unset, while the caller believed they had set it. A caller passing a key-store object whole must narrow it to `{ cert, key }`.
34
+
35
+ ### Fixed
36
+
37
+ - `pki.x509.sign` refuses an unrecognized field on the certificate spec, the issuer and the options. `extension` in place of `extensions` signed a certificate carrying NONE of them: a caller asking for `basicConstraints: { cA: true }` and `keyUsage` got neither, and nothing in the returned certificate said so. An `issuer` nested inside the spec is refused by name as well -- the issuer is the second argument, and nesting it produced a self-signed certificate with no error, which reads as a broken signer rather than a mis-shaped call.
38
+ - `pki.crl.sign` refuses an unrecognized field on the CRL spec, the issuer and the options. The revoked list is `revoked`; the parser reports the same list as `revokedCertificates`, so a caller round-tripping a parsed CRL back into signing reached for that name and got a correctly signed, structurally valid CRL asserting that nothing is revoked. The refusal names the field the producer reads.
39
+ - `pki.pkcs12.build` refuses an unrecognized field on the store spec and the options, and no longer accepts an option belonging to `open` or `verifyMac`. A store that silently omits a certificate or a key looks well formed and opens cleanly; the omission surfaces wherever it is later imported.
40
+ - `pki.pkcs12.build` refuses an unrecognized field on a `safeContents` entry, on a bag, and on the PBE descriptor either of them carries. The safeContents entry is where the privacy directive lives, so a misspelling there was the costliest in the format: `encrypt` misspelled is neither present nor falsy, so it passed the guard that rejects a present-but-falsy directive, no privacy was selected, and that safe went out as plaintext `id-data` -- an unshrouded private-key bag in the clear, inside a PFX whose MAC still verified and which opened without complaint. A misspelled PBE parameter, on either the safe or the bag, silently reverted to the built-in default, so a caller who asked for a stronger KDF or a different cipher got neither. `opts.mac` is checked the same way, and every one of its fields has a default too: a misspelled `iterations` produced a store MAC'd at the shipped iteration count while the caller believed they had raised it, and an array passed as `opts.mac` carried no field any table lists, so it reached the builder as an empty descriptor and produced the default MAC.
41
+ - `pki.pkcs12.build` refuses a spec that mixes its two forms. `safeContents` and the `{ key, cert, ca }` shorthand are alternatives, and the builder returns `safeContents` the moment it is present without ever reading the shorthand fields -- so `{ safeContents: [...], key }` produced a store with the key silently absent.
42
+ - A `safeContents` entry is checked against the privacy branch it selects. `contentEncryptionAlgorithm` is the public-key branch's content cipher and is read by nothing on a password safe, whose cipher comes from `encrypt.cipher`, so an explicit `aes-128-cbc` request on a password safe was accepted and the default used anyway.
43
+ - A certificate recipient of `pki.cms.encrypt` is checked against the fields ITS CERTIFICATE'S KEY ALGORITHM reads. The arms differ: RSA reads `oaepHash` and never `ukm`; ECDH, X25519, X448 and ML-KEM read `ukm` and never `oaepHash`. One table across all three accepted `oaepHash` on an elliptic-curve recipient, where nothing reads it, and wrapped the content key under the arm's own defaults.
44
+ - A bag is checked against the fields ITS OWN TYPE reads, rather than against one table covering every type. `encrypt` is real on a `shroudedKey` bag and read by nothing on a plaintext `key` bag, so a union accepted the combination and emitted the private key as an unencrypted key bag while the caller had supplied an explicit encryption directive and the surrounding MAC gave integrity only.
45
+ - `pki.crl.sign` refuses an unrecognized field on a revoked-certificate entry. Every optional field on an entry is encoded only when present, so `reson` in place of `reason` listed the certificate as revoked carrying no reason code at all -- in a CRL that verifies, where a relying party cannot distinguish it from a revocation published without a reason.
46
+ - `pki.cms.encrypt` refuses an unrecognized field on a recipient descriptor, against a table per recipient type. The fields beyond the selector all have defaults, so `oaepHsh` encrypted the content key under the default SHA-256 OAEP, and a misspelled `iterations`, `salt` or `prf` on a password recipient derived the key at the default work factor. The single descriptor an `EncryptedData` takes is checked per arm as well: its `cek` branch derives nothing, so a work factor handed to it was discarded exactly as a misspelling would be.
47
+ - `pki.tsp.sign` refuses an unrecognized field on the `accuracy` descriptor. Its three fields are all optional, so `milis` emitted an empty Accuracy that reads back as zero seconds, millis and micros, and the token understated the precision the caller asked it to claim.
48
+ - `pki.cms.encrypt` and `pki.cms.authenticate` each refuse an unrecognized option, against separate tables. The two verbs have different option sets, so a single shared table would have accepted either verb's keys and defeated the check.
49
+ - `pki.tsp.sign` refuses an unrecognized field on the message imprint and the options. A one-letter slip -- `odering` for `ordering` -- emitted a token with the ordering flag unset, which is a claim about the timestamp the caller believed they had made.
50
+ - `pki.pkcs12.open` and `pki.pkcs12.verifyMac` each refuse an unrecognized option, against their own tables. `maxIterations` caps the work a hostile store can demand of the key derivation, so a misspelling restored the built-in ceiling and the tighter bound the caller set was never applied.
51
+ - `pki.tsp.request` refuses an unrecognized field on the message imprint as well as on the options -- the same table `pki.tsp.sign` holds its imprint to. The request's own options are the second argument, so `nonce` written on the imprint reached nothing and the request went out with no nonce.
52
+ - `pki.tsp.request` refuses an unrecognized option. Every field it encodes is optional in the request structure, so a misspelled one was dropped without complaint: `nonce` misspelled put a request on the wire carrying no nonce at all, and the client then matched the reply against a replay defense it had never actually asked for.
53
+ - `pki.tsp.response` refuses an unrecognized option. `status` carries the verdict and defaults to granted, so a misspelling did not lose a detail -- it inverted the answer, and a TSA declining a request emitted a granted response over a token it meant to withhold.
54
+ - The options of `pki.pkcs12.build` are checked against the spec form in force. `recipientCerts` selects public-key privacy only where the builder assembles the safes itself, so under the `safeContents` form it selected nothing: a plaintext key bag went into an `id-data` safe with the private key in the clear, while the caller had asked for recipient-enveloped privacy. The full form carries privacy per entry, as `safeContents[i].recipients`.
55
+ - `pki.cms.encrypt` and `pki.cms.authenticate` refuse a per-recipient default that no recipient of the message reads. `oaepHash` is read only by an RSA certificate recipient, `ukm` only by a key-agreement or KEM certificate recipient, and `keyIdentifier` only by a certificate recipient -- so a password-only message given an `oaepHash` described a wrapping it never performed. A recipient that supplies its own value reads that one and never the default, so a default every recipient overrides is refused too; one recipient that does read it is enough. An `EncryptedData` carries no recipients at all and so reads none of the three.
56
+ - `pki.cmp.build` refuses a signature parameter under MAC protection. `pss` and `digestAlgorithm` are read only by the `{ key, cert }` form; supplied alongside `mac`, they emitted a message MAC'd under the default PRF, byte for byte identical to the call that never named them.
57
+ - `pki.jose.sign` refuses `opts.jwk` when the protected header already embeds one. The embedded key wins, so the option is read in kid mode only, and a caller who named a different public JWK beside an embedded one had it discarded.
58
+ - `pki.jose.sign` serializes the protected header once and every check reads back those exact bytes, through the same bounded reader a received JWS goes through. The header is a caller-owned object, so a `toJSON` or an accessor could answer the public-only check and the serializer differently: a JWK whose own members carried no private half but which serialized `d` passed the check and shipped the private key in the header. Serializing runs that code, so the option check now runs before it -- a `toJSON` reaching into the options bag could otherwise delete a misspelled option before the door looked at it. A header that cannot be serialized is refused rather than reaching the signer.
59
+ - `pki.pkcs12.build` decides which of the two spec forms it has once, and the field check and the builder both act on that decision. Held separately the two tests disagreed on a `safeContents` that is present but not a list, so the refusal named a field that was correct instead of the one that was wrong.
60
+ - `pki.cms.encrypt` refuses `authAttrs` under a CBC content algorithm. Authenticated attributes are carried by an `AuthEnvelopedData`, which an AEAD algorithm produces; a CBC algorithm selects `EnvelopedData`, which has no field for them, so the attributes a caller asked to authenticate were dropped and the message went out without them.
61
+ - `pki.cms.authenticate` refuses `digestAlgorithm` and `authAttrs` when `authenticatedAttributes` is `false`. That branch MACs the content octets directly and builds no attribute set, so the digest algorithm that names its hash, and any attribute added to it, are read by nothing.
62
+ - `pem` on an `EncryptedData` descriptor is refused; it is an option, the third argument, as on every other verb here. A second spelling on the descriptor was read by the password arm alone, so a `{ cek, pem: true }` descriptor was accepted and returned DER.
63
+ - `pki.crl.sign` refuses `spec.issuer` when the issuer argument already names one. The distinguished name has three possible sources and `issuer.cert` and `issuer.name` both win outright, so a spec naming one issuer beside a certificate for another produced a CRL, which verifies, issued under the certificate's name. `spec.issuer` is still the name when the issuer argument supplies none.
64
+ - `opts.mac.keyLength` on a `pki.pkcs12.build` store is refused under the classic `hmac` algorithm, which is the default. The Appendix B derivation produces a key at the hash's own output length and reads no key length, so an explicit one was discarded and the store carried the same MAC as a call that never named it. `keyLength` remains a PBMAC1 parameter (RFC 9579).
65
+ - A `pki.pkcs12.build` public-key integrity signer is checked against the form it selects, `{ cert, key }` or `{ spki, keyIdentifier, key }`, plus the signature parameters. Every field beyond the identity has a default, so a misspelled `pss` or `digestAlgorithm` signed the store under PKCS#1 with SHA-256 while the caller had asked for something else, and a store records only what was used. `opts.integrity.signer` and `opts.integrity.signers` are the same setting spelled two ways and the list won outright, so supplying both signed the store under the list alone; exactly one is now required.
66
+
7
67
  ## v0.5.17 — 2026-08-20
8
68
 
9
69
  A failed integrity check destroys the plaintext it recovered, AuthEnvelopedData validates the attributes it is asked to authenticate, and every refusal names the rule it is actually applying.
package/lib/acme.js CHANGED
@@ -694,7 +694,9 @@ var _promised = guard.async.deferred;
694
694
  function _signOuter(o, payloadObj) {
695
695
  if (!_isObject(o)) throw E("acme/bad-input", "a request options object is required");
696
696
  if (!o.key) throw E("acme/bad-input", "a signing key (opts.key) is required");
697
- return jose.sign({ protected: _outerHeader(o), payload: _payloadBuf(payloadObj), key: o.key, jwk: o.jwk, profile: "acme-outer" });
697
+ // The header carries the account jwk in jwk mode and the kid in kid mode, so the public JWK is
698
+ // already there whenever there is one to name; jose.sign reads its own opts.jwk in kid mode only.
699
+ return jose.sign({ protected: _outerHeader(o), payload: _payloadBuf(payloadObj), key: o.key, profile: "acme-outer" });
698
700
  }
699
701
 
700
702
  /**
@@ -778,7 +780,7 @@ function newAccount(o) {
778
780
  if (!_isObject(o.externalAccountBinding)) throw E("acme/bad-input", "externalAccountBinding must be an EAB inner JWS object");
779
781
  payload.externalAccountBinding = o.externalAccountBinding;
780
782
  }
781
- return jose.sign({ protected: { alg: o.alg, nonce: o.nonce, url: o.url, jwk: o.jwk }, payload: _payloadBuf(payload), key: o.key, jwk: o.jwk, profile: "acme-outer" });
783
+ return jose.sign({ protected: { alg: o.alg, nonce: o.nonce, url: o.url, jwk: o.jwk }, payload: _payloadBuf(payload), key: o.key, profile: "acme-outer" });
782
784
  });
783
785
  }
784
786
 
@@ -1123,7 +1125,7 @@ function revokeCert(o) {
1123
1125
  }
1124
1126
  var header = { alg: o.alg, nonce: o.nonce, url: o.url };
1125
1127
  if (hasKid) header.kid = o.kid; else header.jwk = o.jwk;
1126
- return jose.sign({ protected: header, payload: _payloadBuf(payload), key: o.key, jwk: o.jwk, profile: "acme-outer" });
1128
+ return jose.sign({ protected: header, payload: _payloadBuf(payload), key: o.key, profile: "acme-outer" });
1127
1129
  });
1128
1130
  }
1129
1131
 
@@ -1164,7 +1166,7 @@ async function keyChange(o) {
1164
1166
  var inner = await jose.sign({
1165
1167
  protected: { alg: o.newAlg, url: o.url, jwk: o.newJwk },
1166
1168
  payload: _payloadBuf({ account: o.account, oldKey: o.oldKey }),
1167
- key: o.newKey, jwk: o.newJwk, profile: "keychange-inner",
1169
+ key: o.newKey, profile: "keychange-inner",
1168
1170
  });
1169
1171
  return jose.sign({ protected: { alg: o.alg, nonce: o.nonce, url: o.url, kid: o.kid }, payload: _payloadBuf(inner), key: o.key, profile: "acme-outer" });
1170
1172
  }
package/lib/cmp-build.js CHANGED
@@ -75,7 +75,13 @@ var BODY_TAG = {
75
75
  };
76
76
  var CRMF_BODY = { ir: 1, cr: 1, kur: 1 }; // arms whose content is a CertReqMessages via pki.crmf.build
77
77
  var CERT_REP_ARM = { ip: 1, cp: 1, kup: 1, ccp: 1 }; // arms carrying a CertRepMessage
78
+ // Protection has two mutually exclusive forms, and the signature parameters are read only by the
79
+ // signature one: under PBMAC1 a requested pss or digestAlgorithm selects nothing and the message
80
+ // goes out MAC'd under the default PRF, byte-identical to the call that never named them. The union
81
+ // stays in force where the form is ambiguous, so _resolveProtection keeps its both/neither verdicts.
78
82
  var KNOWN_OPTS_KEYS = { key: 1, cert: 1, mac: 1, extraCerts: 1, pem: 1, pss: 1, digestAlgorithm: 1 };
83
+ var KNOWN_OPTS_SIG_KEYS = { key: 1, cert: 1, extraCerts: 1, pem: 1, pss: 1, digestAlgorithm: 1 };
84
+ var KNOWN_OPTS_MAC_KEYS = { mac: 1, extraCerts: 1, pem: 1 };
79
85
  var KNOWN_MESSAGE_KEYS = { header: 1, body: 1 };
80
86
  var KNOWN_MAC_KEYS = { secret: 1, salt: 1, iterationCount: 1, prf: 1, keyLength: 1, algorithm: 1 };
81
87
 
@@ -565,7 +571,15 @@ function _build(message, opts) {
565
571
  guard.identifier.assertKnownKeys(message, KNOWN_MESSAGE_KEYS, _err, "cmp/bad-input", function (k) {
566
572
  return "unknown message field " + JSON.stringify(k) + " (a message carries only header + body)";
567
573
  });
568
- guard.identifier.assertKnownKeys(opts, KNOWN_OPTS_KEYS, _err, "cmp/bad-input", "unknown opts field ");
574
+ var sigForm = opts.key != null || opts.cert != null, macForm = opts.mac != null;
575
+ guard.identifier.assertKnownKeys(opts,
576
+ sigForm === macForm ? KNOWN_OPTS_KEYS : sigForm ? KNOWN_OPTS_SIG_KEYS : KNOWN_OPTS_MAC_KEYS,
577
+ _err, "cmp/bad-input", function (k) {
578
+ return "unknown opts field " + JSON.stringify(k) +
579
+ (macForm && (k === "pss" || k === "digestAlgorithm")
580
+ ? " for MAC protection; the signature parameters are read only by the { key, cert } form"
581
+ : "");
582
+ });
569
583
  if (message.header == null) throw _err("cmp/bad-input", "message.header is required");
570
584
  if (message.body == null) throw _err("cmp/bad-input", "message.body is required");
571
585
 
@@ -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);
@@ -500,8 +605,31 @@ function _authEnvelopedData(contentBytes, cek, ca, contentType, opts, riNodes, r
500
605
  return b.sequence(kids);
501
606
  }
502
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
+
503
618
  function _encryptedData(contentBytes, desc, ca, contentType, opts, cek) {
504
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
+ }
505
633
  var iv = nodeCrypto.randomBytes(16);
506
634
  var contentAlgNode, encKey;
507
635
  if (desc && desc.cek != null) {
@@ -537,7 +665,7 @@ function _encryptedDataPbes2(contentBytes, desc, ca, contentType, iv, opts) {
537
665
  var contentAlg = pbes2.pbes2AlgId(salt, iterations, prf, ca.oid, iv);
538
666
  var eci = b.sequence([b.oid(O(contentType)), contentAlg, b.contextPrimitive(0, enc)]);
539
667
  var inner = b.sequence([b.integer(0n), eci]);
540
- return _emit(inner, "encryptedData", { pem: opts && opts.pem != null ? opts.pem : desc.pem });
668
+ return _emit(inner, "encryptedData", opts);
541
669
  } finally {
542
670
  guard.secret.zeroize(key, CmsError, "cms/bad-input", "the password-derived content-encryption key");
543
671
  }
@@ -581,13 +709,22 @@ var SUPPORTED_DIGEST = { sha256: 1, sha384: 1, sha512: 1 };
581
709
 
582
710
  async function authenticate(content, recipients, opts) {
583
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
+ });
584
722
  var contentBytes = guard.bytes.view(content, CmsError, "cms/bad-input", "content");
585
723
  var macName = opts.macAlgorithm || "hmac-sha256";
586
724
  var mac = MAC_ALGS[macName];
587
725
  if (!mac) throw _err("cms/bad-input", "unsupported macAlgorithm " + JSON.stringify(macName) + " (hmac-sha256/384/512)");
588
726
  if (!Array.isArray(recipients) || !recipients.length) throw _err("cms/bad-input", "at least one recipient is required (RFC 5652 sec. 9.1)");
589
727
  var contentType = opts.contentType || "data";
590
- var withAttrs = opts.authenticatedAttributes !== false;
591
728
  // RFC 5652 sec. 9.1: authAttrs MUST be present when the eContentType is not id-data.
592
729
  if (contentType !== "data" && !withAttrs) throw _err("cms/bad-input", "AuthenticatedData with a non-data contentType requires authenticated attributes (RFC 5652 sec. 9.1)");
593
730
 
@@ -596,12 +733,16 @@ async function authenticate(content, recipients, opts) {
596
733
  var macKey = nodeCrypto.randomBytes(MAC_KEY_OCTETS);
597
734
  try {
598
735
  var recips = [];
599
- 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);
600
739
  var riNodes = recips.map(_taggedRecipient);
601
740
 
602
- var digestName = opts.digestAlgorithm || mac.node;
603
741
  var digestAlgTagged = null, authAttrsDer = null, preimage;
604
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;
605
746
  if (!SUPPORTED_DIGEST[digestName]) throw _err("cms/bad-input", "unsupported digestAlgorithm " + JSON.stringify(digestName) + " (sha256/384/512)");
606
747
  // sec. 9.2: content-type (== eContentType) + message-digest (== digest(content)) attributes,
607
748
  // SET-OF-sorted, MACed under the EXPLICIT SET OF tag (0x31) but transmitted [2] IMPLICIT (0xA2).
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.
@@ -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/crl-sign.js CHANGED
@@ -72,8 +72,38 @@ var REASON_BY_NAME = {};
72
72
  Object.keys(CRL_REASON).forEach(function (v) { REASON_BY_NAME[CRL_REASON[v]] = Number(v); });
73
73
 
74
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
+
75
95
  var KNOWN_IDP_KEYS = { fullName: 1, onlyContainsUserCerts: 1, onlyContainsCACerts: 1, onlyContainsAttributeCerts: 1, indirectCRL: 1 };
76
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
+
77
107
  // RFC 5280 sec. 5.2 -- the profile-fixed criticality of each recognized CRL extension. cRLNumber (5.2.3),
78
108
  // authorityKeyIdentifier (5.2.1), freshestCRL (5.2.6), and authorityInfoAccess (5.2.7) MUST be non-critical;
79
109
  // issuingDistributionPoint (5.2.5) and deltaCRLIndicator (5.2.4) MUST be critical. Enforced on the object
@@ -312,6 +342,12 @@ function _buildRevoked(entryList, isDelta) {
312
342
  var anyExt = false, seenSerials = {};
313
343
  var entries = entryList.map(function (e, idx) {
314
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
+ });
315
351
  if (e.serialNumber == null) throw _err("crl/bad-input", "revoked entry [" + idx + "] requires a serialNumber");
316
352
  var serialTlv = _serialInteger(e.serialNumber);
317
353
  // RFC 5280 sec. 5.1.2.6 -- a serial number identifies a certificate uniquely, so a CRL MUST NOT list the
@@ -399,11 +435,34 @@ function _sign(spec, issuer, opts) {
399
435
  opts = opts || {};
400
436
  if (!spec || typeof spec !== "object" || Buffer.isBuffer(spec)) throw _err("crl/bad-input", "the CRL spec must be an object");
401
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 ");
402
461
  if (issuer.key == null) throw _err("crl/bad-input", "a signing key (issuer.key, a PKCS#8 private key) is required");
403
462
 
404
463
  // Resolve the issuer name + signing-key SPKI. `{ cert }` supplies both; else `{ name|spec.issuer, publicKey }`.
405
464
  var issuerDer, issuerSpki, issuerCert = null;
406
- if (issuer.cert != null) {
465
+ if (byCert) {
407
466
  issuerCert = _parseIssuerCert(issuer.cert);
408
467
  _assertIssuerCanSignCrl(issuerCert);
409
468
  issuerDer = pkiBuild.tbsNameField(issuerCert, "subject");
@@ -411,7 +470,7 @@ function _sign(spec, issuer, opts) {
411
470
  } else {
412
471
  issuerSpki = _reqDer(issuer.publicKey, "issuer.publicKey (the issuer SPKI DER)");
413
472
  _assertValidSpki(issuerSpki, "issuer.publicKey");
414
- var dnSource = issuer.name != null ? issuer.name : spec.issuer;
473
+ var dnSource = byName ? issuer.name : spec.issuer;
415
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");
416
475
  issuerDer = _encodeName(dnSource);
417
476
  }
@@ -84,6 +84,10 @@ var _jsonStringify = JSON.stringify;
84
84
  var _mathFloor = Math.floor;
85
85
  var _mathCeil = Math.ceil;
86
86
  var _mathMin = Math.min;
87
+ var _mathMax = Math.max;
88
+ // A numeric parse a bound is then compared against: one answering a larger value widens the bound
89
+ // the comparison was written to enforce.
90
+ var _parseInt = parseInt;
87
91
  var _promiseResolve = Promise.resolve;
88
92
  var _promiseReject = Promise.reject;
89
93
 
@@ -102,6 +106,19 @@ var _arrSort = uncurry(Array.prototype.sort);
102
106
  var _arrConcat = uncurry(Array.prototype.concat);
103
107
  var _arrSlice = uncurry(Array.prototype.slice);
104
108
  var _arrJoin = uncurry(Array.prototype.join);
109
+ var _arrPush = uncurry(Array.prototype.push);
110
+
111
+ // The string and byte-view operations a decision is then made on. A `toUpperCase` or `toLowerCase`
112
+ // replaced after load changes which registry row an algorithm name resolves to; a `subarray`
113
+ // returning a different window hands the next step bytes from somewhere else in the same buffer;
114
+ // and a radix conversion is how a key's own numbers are read back before they are compared.
115
+ var _strToUpperCase = uncurry(String.prototype.toUpperCase);
116
+ var _strToLowerCase = uncurry(String.prototype.toLowerCase);
117
+ var _strIndexOf = uncurry(String.prototype.indexOf);
118
+ var _taSubarray = uncurry(Uint8Array.prototype.subarray);
119
+ var _abSlice = uncurry(ArrayBuffer.prototype.slice);
120
+ var _numToString = uncurry(Number.prototype.toString);
121
+ var _bigIntToString = uncurry(BigInt.prototype.toString);
105
122
 
106
123
  // The collection operations, uncurried. A guard's cycle-safe chain walk is a `Set` it owns, and
107
124
  // `has` is what stops it: one answering `true` ends the walk at the first prototype, so a scan that
@@ -270,6 +287,16 @@ module.exports = {
270
287
  floor: _mathFloor,
271
288
  ceil: _mathCeil,
272
289
  min: _mathMin,
290
+ max: _mathMax,
291
+ parseInt: _parseInt,
292
+ push: _arrPush,
293
+ toUpperCase: _strToUpperCase,
294
+ toLowerCase: _strToLowerCase,
295
+ stringIndexOf: _strIndexOf,
296
+ subarray: _taSubarray,
297
+ arrayBufferSlice: _abSlice,
298
+ numberToString: _numToString,
299
+ bigIntToString: _bigIntToString,
273
300
  promiseResolve: _promiseResolve,
274
301
  promiseReject: _promiseReject,
275
302
  forEach: _arrForEach,