@blamejs/pki 0.8.54 → 0.8.56

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 +84 -0
  2. package/lib/acme.js +2 -2
  3. package/lib/asn1-der.js +17 -5
  4. package/lib/attrcert-verify.js +1 -1
  5. package/lib/cmc-verify.js +1 -1
  6. package/lib/cmp-session.js +2 -2
  7. package/lib/cmp-verify.js +2 -2
  8. package/lib/cms-decrypt.js +4 -1
  9. package/lib/cms-encrypt.js +4 -1
  10. package/lib/cms-sign.js +26 -12
  11. package/lib/cms-verify.js +1 -1
  12. package/lib/composite-kem.js +30 -12
  13. package/lib/composite-sig.js +42 -23
  14. package/lib/constants.js +7 -2
  15. package/lib/crl-verify.js +1 -1
  16. package/lib/crmf-sign.js +72 -43
  17. package/lib/crmf-verify.js +1 -1
  18. package/lib/csr-verify.js +1 -1
  19. package/lib/ct.js +11 -13
  20. package/lib/edwards-point.js +1 -1
  21. package/lib/est.js +3 -3
  22. package/lib/guard-async.js +22 -1
  23. package/lib/guard-bytes.js +6 -3
  24. package/lib/guard-identifier.js +24 -1
  25. package/lib/guard-intrinsic.js +474 -0
  26. package/lib/http-digest.js +146 -62
  27. package/lib/http-transport.js +82 -17
  28. package/lib/identity-match.js +2 -2
  29. package/lib/merkle.js +2 -2
  30. package/lib/ocsp-request-verify.js +1 -1
  31. package/lib/ocsp.js +46 -22
  32. package/lib/path-validate.js +234 -88
  33. package/lib/pbes2.js +14 -7
  34. package/lib/schema-attrcert.js +2 -2
  35. package/lib/schema-cmc.js +2 -2
  36. package/lib/schema-cmp.js +2 -2
  37. package/lib/schema-cms.js +2 -2
  38. package/lib/schema-crmf.js +2 -2
  39. package/lib/schema-csr.js +2 -2
  40. package/lib/schema-pkix.js +3 -3
  41. package/lib/schema-x509.js +2 -2
  42. package/lib/sigstore.js +14 -3
  43. package/lib/tlog.js +7 -4
  44. package/lib/trust.js +187 -104
  45. package/lib/tsp-sign.js +1 -1
  46. package/lib/webauthn-mds.js +257 -121
  47. package/lib/webauthn.js +327 -164
  48. package/lib/webcrypto.js +10 -3
  49. package/package.json +1 -1
  50. package/sbom.cdx.json +6 -6
package/lib/crmf-sign.js CHANGED
@@ -43,10 +43,9 @@ var _Promise = intrinsic.Promise;
43
43
  var _promiseResolve = intrinsic.uncurry(intrinsic.promiseResolve);
44
44
  /** @internal `verifyPop`'s verdict is the aggregate of the per-message results, so a replaced `all`
45
45
  * that resolves a fabricated array reports an unverified proof of possession as verified. */
46
- var _promiseAll = intrinsic.uncurry(intrinsic.promiseAll);
47
46
  var _hasOwn = intrinsic.hasOwn;
48
47
  var _isBuffer = intrinsic.isBuffer;
49
- var _push = intrinsic.push, _String = intrinsic.String;
48
+ var _String = intrinsic.String;
50
49
  var _stringify = intrinsic.stringify;
51
50
  var cms = require("./schema-cms");
52
51
  var possession = require("./possession");
@@ -108,7 +107,7 @@ function _ctlPossession(v) {
108
107
  b.raw(_b.reqDer(issuerRaw, "statementOfPossession signer.issuer (the signature certificate's issuer Name DER)")),
109
108
  b.integer(guard.range.authoredInteger(signer.serialNumber, _err, "crmf/bad-controls", "statementOfPossession signer.serialNumber")),
110
109
  ])];
111
- if (v.certificate != null) _push(fields, b.raw(_b.reqDer(v.certificate, "statementOfPossession certificate")));
110
+ if (v.certificate != null) guard.list.append(fields, b.raw(_b.reqDer(v.certificate, "statementOfPossession certificate")));
112
111
  var der = b.sequence(fields);
113
112
  try { possession.parse(der); }
114
113
  catch (e) { throw _err("crmf/bad-controls", "statementOfPossession: " + ((e && e.message) || _String(e)), e); }
@@ -177,8 +176,8 @@ function _encodeOptionalValidity(validity) {
177
176
  var nb = validity.notBefore, na = validity.notAfter;
178
177
  if (nb == null && na == null) throw _err("crmf/bad-validity", "validity must contain notBefore or notAfter (RFC 4211 sec. 5)");
179
178
  var parts = [];
180
- if (nb != null) parts.push(b.explicit(0, _b.timeDer(nb, "validity notBefore")));
181
- if (na != null) parts.push(b.explicit(1, _b.timeDer(na, "validity notAfter")));
179
+ if (nb != null) guard.list.append(parts, b.explicit(0, _b.timeDer(nb, "validity notBefore")));
180
+ if (na != null) guard.list.append(parts, b.explicit(1, _b.timeDer(na, "validity notAfter")));
182
181
  // allow:nan-date-comparison-unguarded -- both instants passed timeDer's guard.time.assertEncodable above.
183
182
  if (nb != null && na != null && guard.time.instantOf(nb) > guard.time.instantOf(na)) throw _err("crmf/bad-validity", "notBefore must not be after notAfter");
184
183
  return b.implicit(4, b.sequence(parts));
@@ -190,19 +189,22 @@ function _encodeCertTemplate(tpl, opts) {
190
189
  var fields = [];
191
190
  if (tpl.version != null) {
192
191
  if (tpl.version !== 2) throw _err("crmf/bad-version", "certTemplate version MUST be 2 (v3) if supplied (RFC 4211 sec. 5)");
193
- fields.push(b.implicit(0, b.integer(2n)));
192
+ guard.list.append(fields, b.implicit(0, b.integer(2n)));
194
193
  }
195
- if (tpl.serialNumber != null) fields.push(b.implicit(1, _b.serialInteger(tpl.serialNumber)));
196
- if (tpl.issuer != null) fields.push(b.explicit(3, _b.encodeName(tpl.issuer)));
197
- if (tpl.validity != null) fields.push(_encodeOptionalValidity(tpl.validity));
198
- if (tpl.subject != null) fields.push(b.explicit(5, _b.encodeName(tpl.subject)));
194
+ /** @internal Appended through the guard. These members are the template a proof of possession is
195
+ * computed over and a CA issues from, so a replaced append that drops or substitutes one changes
196
+ * what was requested from what was proven. */
197
+ if (tpl.serialNumber != null) guard.list.append(fields, b.implicit(1, _b.serialInteger(tpl.serialNumber)));
198
+ if (tpl.issuer != null) guard.list.append(fields, b.explicit(3, _b.encodeName(tpl.issuer)));
199
+ if (tpl.validity != null) guard.list.append(fields, _encodeOptionalValidity(tpl.validity));
200
+ if (tpl.subject != null) guard.list.append(fields, b.explicit(5, _b.encodeName(tpl.subject)));
199
201
  var spki = null;
200
202
  if (tpl.publicKey != null) {
201
203
  spki = _b.reqDer(tpl.publicKey, "certTemplate.publicKey (the SPKI DER of the requested key)");
202
204
  _b.assertValidSpki(spki, "certTemplate.publicKey");
203
- fields.push(b.implicit(6, spki));
205
+ guard.list.append(fields, b.implicit(6, spki));
204
206
  }
205
- if (tpl.extensions != null) fields.push(b.implicit(9, _b.requestedExtensions(tpl.extensions, spki)));
207
+ if (tpl.extensions != null) guard.list.append(fields, b.implicit(9, _b.requestedExtensions(tpl.extensions, spki)));
206
208
  return { der: b.sequence(fields), spki: spki, complete: tpl.subject != null && tpl.publicKey != null };
207
209
  }
208
210
 
@@ -237,7 +239,7 @@ function _buildAttrTypeAndValues(spec, code, label, valueMap, collect) {
237
239
  var typeOid = O(k);
238
240
  if (seen[typeOid]) throw _err(code, "duplicate " + label + " type " + k);
239
241
  seen[typeOid] = true;
240
- out.push(b.sequence([b.oid(typeOid), enc(spec[k])]));
242
+ guard.list.append(out, b.sequence([b.oid(typeOid), enc(spec[k])]));
241
243
  });
242
244
  if (!out.length) throw _err(code, label + " must carry at least one entry");
243
245
  return b.sequence(out);
@@ -916,19 +918,23 @@ function _buildEncryptedKeyPop(pop, outerTag, template) {
916
918
  "the proof is that the enclosed private key is the one being certified (RFC 4211 sec. 4.2)");
917
919
  }
918
920
  encKeyWithIdDer = _encKeyWithID(privateKeyDer, pop.identifier);
919
- return _assertEnclosedKeyMatches(privateKeyDer, template.spki).then(function () {
920
- return cmsEncrypt.encrypt(encKeyWithIdDer, recipients, { contentType: "encKeyWithID", contentEncryptionAlgorithm: cea })
921
- .then(null, function (e) {
922
- if (e instanceof CrmfError) throw e;
923
- throw _err("crmf/bad-input", "the encryptedKey proof of possession could not envelope the private key to " +
924
- "pop.recipients: " + ((e && e.message) || e), e);
925
- });
926
- })
927
- .then(function (contentInfo) {
928
- var inner = _bareEnvelopedData(contentInfo);
929
- return b.explicit(outerTag, b.implicit(4, inner));
930
- })
931
- .then(function (out) { wipe(); return out; }, function (e) { wipe(); throw e; });
921
+ /** @internal Each step is continued through the capture. The first decides whether the enclosed
922
+ * private key IS the one being certified, which is the whole proof, and the last is what wipes the
923
+ * key material whichever way the chain settles. */
924
+ var enveloped = intrinsic.chain(_assertEnclosedKeyMatches(privateKeyDer, template.spki), function () {
925
+ var encrypted = cmsEncrypt.encrypt(encKeyWithIdDer, recipients,
926
+ { contentType: "encKeyWithID", contentEncryptionAlgorithm: cea });
927
+ return intrinsic.chain(encrypted, null, function (e) {
928
+ if (e instanceof CrmfError) throw e;
929
+ throw _err("crmf/bad-input", "the encryptedKey proof of possession could not envelope the private key to " +
930
+ "pop.recipients: " + ((e && e.message) || e), e);
931
+ });
932
+ });
933
+ var wrapped = intrinsic.chain(enveloped, function (contentInfo) {
934
+ var inner = _bareEnvelopedData(contentInfo);
935
+ return b.explicit(outerTag, b.implicit(4, inner));
936
+ });
937
+ return intrinsic.chain(wrapped, function (out) { wipe(); return out; }, function (e) { wipe(); throw e; });
932
938
  }
933
939
  }
934
940
 
@@ -964,9 +970,9 @@ function _assertEnclosedKeyMatches(privateKeyDer, templateSpki) {
964
970
  * cannot use it, so those are proven by USING the key against the template. */
965
971
  var enclosedAlg = key.algorithmOidAt(privateKeyDer, 1);
966
972
  if (enclosedAlg !== null && !key.isDhOid(enclosedAlg)) return _assertEnclosedKeyCorresponds(privateKeyDer, templateSpki);
967
- return _promiseResolve(_Promise, undefined)
968
- .then(function () { return key.publicFromPrivate(privateKeyDer); })
969
- .then(function (derivedSpki) {
973
+ var derivedP = intrinsic.chain(_promiseResolve(_Promise, undefined),
974
+ function () { return key.publicFromPrivate(privateKeyDer); });
975
+ return intrinsic.chain(derivedP, function (derivedSpki) {
970
976
  var derived = guard.bytes.view(derivedSpki, CrmfError, "crmf/bad-input", "the derived public key");
971
977
  var wanted = guard.bytes.view(templateSpki, CrmfError, "crmf/bad-input", "certTemplate.publicKey");
972
978
  /** @internal A finite-field key names itself in either encoding here too, and an enclosed key
@@ -1074,12 +1080,20 @@ function _buildProofOfPossession(pop, certReqDer, template, signingKey, opts, po
1074
1080
  signedRegion = poposkSeq;
1075
1081
  poposkInputField = b.implicit(0, poposkSeq);
1076
1082
  }
1077
- return signScheme.signOverTbs(scheme, signingKey, signedRegion, _signE).then(function (sig) {
1078
- return _promiseResolve(_Promise, _b.assertSignatureVerifies(signedRegion, sig, provingSpki, scheme)).then(function () {
1083
+ /** @internal Both continuations are captured. The inner one runs after the proof's own signature
1084
+ * has been verified against the key being certified, so a replacement settling it emits a proof
1085
+ * whose signature was never checked against that key. */
1086
+ return intrinsic.chain(signScheme.signOverTbs(scheme, signingKey, signedRegion, _signE), function (sig) {
1087
+ var selfChecked = _promiseResolve(_Promise, _b.assertSignatureVerifies(signedRegion, sig, provingSpki, scheme));
1088
+ return intrinsic.chain(selfChecked, function () {
1079
1089
  var popoChildren = [];
1080
- if (poposkInputField) popoChildren.push(poposkInputField);
1081
- popoChildren.push(scheme.sigAlgId);
1082
- popoChildren.push(b.bitString(sig, 0));
1090
+ /** @internal Appended through the guard, which defines the index rather than calling a method
1091
+ * the prototype answers for. These three members ARE the proof: the signature value is added
1092
+ * AFTER it has been verified against the key being certified, so a replaced append that alters
1093
+ * it emits a proof whose bytes nothing checked. */
1094
+ if (poposkInputField) guard.list.append(popoChildren, poposkInputField);
1095
+ guard.list.append(popoChildren, scheme.sigAlgId);
1096
+ guard.list.append(popoChildren, b.bitString(sig, 0));
1083
1097
  return b.implicit(1, b.sequence(popoChildren));
1084
1098
  });
1085
1099
  }, function (e) {
@@ -1171,7 +1185,7 @@ function _buildCertReqMsg(spec, key, opts) {
1171
1185
  var template = _encodeCertTemplate(spec.certTemplate);
1172
1186
  var certReqChildren = [b.integer(_certReqId(spec.certReqId)), template.der];
1173
1187
  var encodedControls = [];
1174
- if (spec.controls != null) certReqChildren.push(_buildAttrTypeAndValues(spec.controls, "crmf/bad-controls", "controls", CONTROL_VALUE, encodedControls));
1188
+ if (spec.controls != null) guard.list.append(certReqChildren, _buildAttrTypeAndValues(spec.controls, "crmf/bad-controls", "controls", CONTROL_VALUE, encodedControls));
1175
1189
  var certReqDer = b.sequence(certReqChildren);
1176
1190
 
1177
1191
  /** @internal The key the RFC 9883 control names, when the request carries one: that key makes the
@@ -1196,10 +1210,12 @@ function _buildCertReqMsg(spec, key, opts) {
1196
1210
  possessionSpki = _encodedPossessionSpki(encodedControls);
1197
1211
  }
1198
1212
 
1199
- return _promiseResolve(_Promise, _buildProofOfPossession(spec.pop, certReqDer, template, signingKey, opts, possessionSpki)).then(function (popoDer) {
1213
+ var popoP = _promiseResolve(_Promise,
1214
+ _buildProofOfPossession(spec.pop, certReqDer, template, signingKey, opts, possessionSpki));
1215
+ return intrinsic.chain(popoP, function (popoDer) {
1200
1216
  var msgChildren = [certReqDer];
1201
- if (popoDer) msgChildren.push(popoDer);
1202
- if (spec.regInfo != null) msgChildren.push(_buildAttrTypeAndValues(spec.regInfo, "crmf/bad-reg-info", "regInfo", REGINFO_VALUE));
1217
+ if (popoDer) guard.list.append(msgChildren, popoDer);
1218
+ if (spec.regInfo != null) guard.list.append(msgChildren, _buildAttrTypeAndValues(spec.regInfo, "crmf/bad-reg-info", "regInfo", REGINFO_VALUE));
1203
1219
  return b.sequence(msgChildren);
1204
1220
  });
1205
1221
  }
@@ -1218,7 +1234,9 @@ function _build(spec, key, opts) {
1218
1234
  specs = [spec];
1219
1235
  }
1220
1236
  if (!specs.length) throw _err("crmf/bad-input", "at least one certificate request message is required (RFC 4211 sec. 3)");
1221
- return _promiseAll(_Promise, specs.map(function (s) { return _buildCertReqMsg(s, key, opts); })).then(function (msgs) {
1237
+ return intrinsic.chain(intrinsic.allOf(intrinsic.mapList(specs, function (s) {
1238
+ return _buildCertReqMsg(s, key, opts);
1239
+ })), function (msgs) {
1222
1240
  var der = b.sequence(msgs);
1223
1241
  if (opts.pem != null) {
1224
1242
  if (typeof opts.pem !== "string" || !opts.pem) throw _err("crmf/bad-input", "opts.pem must be a non-empty PEM label string");
@@ -1310,7 +1328,10 @@ function _verifyOne(msg) {
1310
1328
  spki = sopSpki;
1311
1329
  bound = false;
1312
1330
  }
1313
- return crmfVerify.verifyPopSignature(popo, spki, preimage).then(function (ok) {
1331
+ /** @internal The continuation comes from the capture. It is where the signature answer becomes this
1332
+ * message's verdict, so a replacement settling it hands back a record stating `verified: true` for
1333
+ * a proof nothing checked, and the aggregate above then reports the whole set as verified. */
1334
+ return intrinsic.chain(crmfVerify.verifyPopSignature(popo, spki, preimage), function (ok) {
1314
1335
  return guard.verdict.of(base, {
1315
1336
  valid: ok === true, verified: ok === true, method: "signature", cryptographicallyVerified: ok === true,
1316
1337
  subject: bound ? base.subject : null,
@@ -1368,9 +1389,17 @@ function _verifyOne(msg) {
1368
1389
  function verifyPop(messages) { return guard.async.deferred(function () { return _verifyPop(messages); }); }
1369
1390
  function _verifyPop(messages) {
1370
1391
  var parsed = _coerceMessages(messages);
1371
- return _promiseAll(_Promise, parsed.messages.map(_verifyOne)).then(function (out) {
1372
- var allVerified = out.length > 0 && out.every(function (m) { return m.verified === true; });
1373
- return guard.verdict.of({ valid: allVerified, verified: allVerified, messages: out });
1392
+ /** @internal The aggregate, the projection into it and the universal test over its results all come
1393
+ * from the captures. This IS the proof-of-possession verdict for a set of requests: the aggregate's
1394
+ * static form reads `resolve` off the constructor at each call, so a replacement answers for every
1395
+ * message without one being verified, and the test below is what requires all of them to have
1396
+ * verified. */
1397
+ return intrinsic.chain(intrinsic.allOf(intrinsic.mapList(parsed.messages, _verifyOne)), function (out) {
1398
+ var allVerified = out.length > 0 && intrinsic.everyOwn(out, function (m) { return m && m.verified === true; });
1399
+ /** @internal Copied before it is handed back. The aggregate's own list carries no prototype, so
1400
+ * that a `then` installed on `Array.prototype` cannot settle the aggregate with something else;
1401
+ * a caller reading `result.messages` expects an ordinary array it can iterate and map. */
1402
+ return guard.verdict.of({ valid: allVerified, verified: allVerified, messages: intrinsic.copyList(out) });
1374
1403
  });
1375
1404
  }
1376
1405
 
@@ -12,4 +12,4 @@ function verifyPopSignature(popo, spkiBytes, preimage) {
12
12
  return _seam.verify(popo.algorithmIdentifier, popo.signature, spkiBytes, preimage);
13
13
  }
14
14
 
15
- module.exports = { setEngine: _seam.setEngine, verifyPopSignature: verifyPopSignature };
15
+ module.exports = Object.freeze({ setEngine: _seam.setEngine, verifyPopSignature: verifyPopSignature });
package/lib/csr-verify.js CHANGED
@@ -13,4 +13,4 @@ function verifyCsrSignature(parsed) {
13
13
  parsed.subjectPublicKeyInfo.bytes, parsed.certificationRequestInfoBytes);
14
14
  }
15
15
 
16
- module.exports = { setEngine: _seam.setEngine, verifyCsrSignature: verifyCsrSignature };
16
+ module.exports = Object.freeze({ setEngine: _seam.setEngine, verifyCsrSignature: verifyCsrSignature });
package/lib/ct.js CHANGED
@@ -1353,18 +1353,16 @@ function _clientDoor(opts, keyName) {
1353
1353
  }
1354
1354
 
1355
1355
  /** @internal Every DECLARED option the bag can answer, read once into a record of plain values, before
1356
- * anything derives from one. `in` is used rather than an enumeration because the door accepts an option
1357
- * supplied through a caller's defaults object, and an enumeration omits an inherited FUNCTION member,
1358
- * which silently dropped a `transport` and fell through to opening a real connection. It consults the
1359
- * prototype chain exactly as the direct reads it replaces did, so it carries no new exposure, and
1360
- * absence survives: a name the bag cannot answer is left out rather than materialized as `undefined`,
1361
- * which a guard that tells absent from present-and-undefined depends on. */
1356
+ * anything derives from one. The presence-preserving snapshot is the one this module needs: absence
1357
+ * survives, so a name the bag cannot answer is left out rather than materialized as `undefined`,
1358
+ * which is what lets an absent `transport` still mean "open the connection yourself, under the
1359
+ * pinned anchors". It consults the prototype chain exactly as the direct reads it replaces did,
1360
+ * because the door accepts an option supplied through a caller's defaults object and an enumeration
1361
+ * omits an inherited FUNCTION member, which silently dropped a `transport` and fell through to
1362
+ * opening a real connection. */
1363
+ // @guard-via guard.identifier.snapshotPresentOptions
1362
1364
  function _takeTopLevel(bag, known) {
1363
- var o = intrinsic.create(null);
1364
- intrinsic.forEach(intrinsic.keys(known), function (k) {
1365
- if (k in bag) o[k] = bag[k];
1366
- });
1367
- return o;
1365
+ return guard.identifier.snapshotPresentOptions(bag, known);
1368
1366
  }
1369
1367
 
1370
1368
  /** @internal The two values a verdict rests on, made IMMUNE rather than merely taken. Holding a
@@ -2255,7 +2253,7 @@ async function fetchLogList(opts) {
2255
2253
  });
2256
2254
  }
2257
2255
 
2258
- module.exports = {
2256
+ module.exports = intrinsic.freeze({
2259
2257
  parseSctList: parseSctList,
2260
2258
  reconstructSignedData: reconstructSignedData,
2261
2259
  verifySct: verifySct,
@@ -2278,4 +2276,4 @@ module.exports = {
2278
2276
  addPreChain: addPreChain,
2279
2277
  HASH_ALGORITHMS: HASH_ALGORITHMS,
2280
2278
  SIGNATURE_ALGORITHMS: SIGNATURE_ALGORITHMS,
2281
- };
2279
+ });
@@ -73,4 +73,4 @@ function validateSpki(spkiBytes, crv, E, code) {
73
73
  }
74
74
  }
75
75
 
76
- module.exports = { validate: validate, validateSpki: validateSpki };
76
+ module.exports = Object.freeze({ validate: validate, validateSpki: validateSpki });
package/lib/est.js CHANGED
@@ -287,7 +287,7 @@ function _findMultipartMarks(text, boundary) {
287
287
  if (lsN !== -1) { var r1 = _matchDelimAt(text, lsN, boundary); if (r1) { found = r1; foundAt = p; break; } }
288
288
  }
289
289
  if (!found) break;
290
- marks[marks.length] = { at: foundAt, bodyStart: found.end, close: found.close };
290
+ guard.list.append(marks, { at: foundAt, bodyStart: found.end, close: found.close });
291
291
  searchStart = found.end === foundAt ? found.end + 1 : found.end;
292
292
  }
293
293
  return marks;
@@ -328,9 +328,9 @@ function _splitCrlf(s) {
328
328
  var nlLen = 0;
329
329
  if (_charCodeAt(s, i) === 0x0d && i + 1 < n && _charCodeAt(s, i + 1) === 0x0a) nlLen = 2;
330
330
  else if (_charCodeAt(s, i) === 0x0a) nlLen = 1;
331
- if (nlLen > 0) { out[out.length] = _strSlice(s, last, i); i += nlLen; last = i; } else i += 1;
331
+ if (nlLen > 0) { guard.list.append(out, _strSlice(s, last, i)); i += nlLen; last = i; } else i += 1;
332
332
  }
333
- out[out.length] = _strSlice(s, last, n);
333
+ guard.list.append(out, _strSlice(s, last, n));
334
334
  return out;
335
335
  }
336
336
 
@@ -22,6 +22,26 @@ function deferred(body) {
22
22
  catch (e) { return _reject(_Promise, e); }
23
23
  }
24
24
 
25
+ // @internal
26
+ /** @internal A continuation taken from the capture, for a module that holds the guards rather than
27
+ * the intrinsic captures directly. `p.then(f)` reads `then` off the promise at the call, so a
28
+ * replacement settles the chain without running `f`, and `f` is where a step's result becomes the
29
+ * next step's input. */
30
+ // @enforced-by behavioral -- the RED vectors live with each consumer's own verdict
31
+ function chain(p, onOk, onFail) {
32
+ return _intrinsic.chain(p, onOk, onFail);
33
+ }
34
+
35
+ // @internal
36
+ /** @internal Run `body` once the work settles, either way, and pass the outcome through unchanged.
37
+ * `Promise.prototype.finally` is not this: it calls the receiver's own `then`, which is the live
38
+ * method, so a replacement there settles the chain with a value of its own AND skips the callback.
39
+ * Cleanup that wipes key material is exactly the callback that must not be skipped. */
40
+ // @enforced-by behavioral -- each consumer's RED vector drives its own cleanup
41
+ function cleanup(p, body) {
42
+ return _intrinsic.chain(p, function (out) { body(); return out; }, function (e) { body(); throw e; });
43
+ }
44
+
25
45
  // @internal
26
46
  // @enforced-by behavioral -- a RED vector in each client suite refuses a transport yielding no thenable
27
47
  function awaited(value, E, code, label) {
@@ -96,4 +116,5 @@ function bounded(value, ms, E, code, label) {
96
116
  });
97
117
  }
98
118
 
99
- module.exports = _intrinsic.freeze({ deferred: deferred, awaited: awaited, invoked: invoked, bounded: bounded });
119
+ module.exports = _intrinsic.freeze({ deferred: deferred, awaited: awaited, invoked: invoked, bounded: bounded,
120
+ chain: chain, cleanup: cleanup });
@@ -34,7 +34,6 @@ var _mapForEach = intrinsic.uncurry(Map.prototype.forEach);
34
34
  var _String = intrinsic.String;
35
35
  var _Date = intrinsic.Date;
36
36
  var _apply = intrinsic.apply;
37
- var _promiseFinally = intrinsic.promiseFinally;
38
37
  var _ArrayBuffer = intrinsic.ArrayBuffer;
39
38
  var _Uint8Array = intrinsic.Uint8Array;
40
39
  var _DataView = intrinsic.DataView;
@@ -559,10 +558,14 @@ function fixArguments(ErrorClass, code, args) {
559
558
  // @enforced-by behavioral -- an arrangement of two calls has no rename-proof shape to detect. The
560
559
  function fixedCall(ErrorClass, code, args, body) {
561
560
  var handle = null;
562
- return _promiseFinally(async.deferred(function () {
561
+ /** @internal The release is attached on BOTH outcomes rather than through `finally`, which calls
562
+ * the receiver's own `then` and so reads the live method: a replacement there settles the chain
563
+ * with a value of its own AND skips this callback, so the fixed views stay pinned. */
564
+ var ran = async.deferred(function () {
563
565
  handle = fixArguments(ErrorClass, code, args);
564
566
  return _apply(body, null, handle.values);
565
- }), function () { if (handle) handle.release(); });
567
+ });
568
+ return async.cleanup(ran, function () { if (handle) handle.release(); });
566
569
  }
567
570
 
568
571
  /**
@@ -69,7 +69,7 @@ function assertCanonicalOid(str, E, code, label, boundsCode, countCode) {
69
69
  }
70
70
  if (boundsCode === null) return str;
71
71
  var bcode = boundsCode === undefined ? code : boundsCode;
72
- var parts = _strSplit(str, ".");
72
+ var parts = intrinsic.splitChar(str, 46);
73
73
  if (parts.length - 1 > constants.LIMITS.OID_MAX_SUBIDENTIFIERS) {
74
74
  throw E(countCode || bcode, who + " carries more than " + constants.LIMITS.OID_MAX_SUBIDENTIFIERS + " sub-identifiers");
75
75
  }
@@ -101,6 +101,28 @@ function snapshotOptions(obj, known) {
101
101
  return out;
102
102
  }
103
103
 
104
+ /** @internal The same one-read capture, for a caller that tells an ABSENT option from one present
105
+ * and holding `undefined`. `snapshotOptions` materializes every declared name, which is correct for
106
+ * a caller testing `!== undefined` and wrong for one testing presence: an omitted option becomes an
107
+ * own key, and a presence-based guard then sees an option the caller never passed. Measured, a bag
108
+ * omitting `transport` is ACCEPTED by `assertCallableOption` and REFUSED after `snapshotOptions`,
109
+ * because the materialized `undefined` is a transport that is not callable.
110
+ *
111
+ * Presence consults the prototype chain, because a caller supplying options through a defaults
112
+ * object is supplying them, and an enumeration omits an inherited FUNCTION member: that is how a
113
+ * copy once dropped a `transport` and fell through to opening a real connection where the caller
114
+ * had passed a fake one. The VALUE is still read once per name, which is what the snapshot is for.
115
+ * The two live side by side rather than behind a flag so a caller chooses between them knowingly. */
116
+ // @enforced-by guard-shape-reinlined
117
+ // @guard-shape if \(\w+ in \w+\) \w+\[\w+\] = \w+\[\w+\];
118
+ function snapshotPresentOptions(obj, known) {
119
+ var out = _create(null);
120
+ if (obj === null || obj === undefined) return out;
121
+ var o = _Object(obj);
122
+ _forEach(_getOwnPropertyNames(known), function (k) { if (k in o) out[k] = o[k]; });
123
+ return out;
124
+ }
125
+
104
126
  /** @internal The option names a bag carries, read the way assertKnownKeys read them: every own
105
127
  * string key, whatever its descriptor says. Object.keys reports only the enumerable ones, so an
106
128
  * option written with `enumerable: false` clears the door and then vanishes from the read.
@@ -656,6 +678,7 @@ module.exports = intrinsic.freeze({
656
678
  optionsObject: optionsObject,
657
679
  refuseAccessorFields: refuseAccessorFields,
658
680
  snapshotOptions: snapshotOptions,
681
+ snapshotPresentOptions: snapshotPresentOptions,
659
682
  optionNames: optionNames,
660
683
  ownOptions: ownOptions,
661
684
  pickSettings: pickSettings,