@blamejs/pki 0.8.58 → 0.8.60

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 (66) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/lib/acme.js +53 -30
  3. package/lib/alt-sig.js +3 -1
  4. package/lib/asn1-der.js +98 -39
  5. package/lib/attrcert-sign.js +19 -16
  6. package/lib/byte-writer.js +63 -13
  7. package/lib/cbor-det.js +9 -3
  8. package/lib/cmc-build.js +16 -10
  9. package/lib/cmp-build.js +45 -34
  10. package/lib/cmp-session.js +19 -9
  11. package/lib/cms-decrypt.js +28 -10
  12. package/lib/cms-encrypt.js +23 -17
  13. package/lib/cms-sign.js +65 -43
  14. package/lib/cms-verify.js +11 -7
  15. package/lib/crl-sign.js +27 -20
  16. package/lib/csr-sign.js +5 -2
  17. package/lib/ct.js +37 -17
  18. package/lib/est.js +55 -32
  19. package/lib/guard-bytes.js +5 -1
  20. package/lib/guard-der.js +6 -1
  21. package/lib/guard-identifier.js +1 -1
  22. package/lib/guard-intrinsic.js +12 -4
  23. package/lib/guard-list.js +64 -0
  24. package/lib/guard-name.js +1 -1
  25. package/lib/hpke.js +10 -3
  26. package/lib/http-transport.js +12 -3
  27. package/lib/inspect.js +10 -2
  28. package/lib/jose.js +3 -1
  29. package/lib/key.js +11 -7
  30. package/lib/lint.js +236 -152
  31. package/lib/mime.js +9 -5
  32. package/lib/ocsp.js +25 -22
  33. package/lib/path-validate.js +48 -18
  34. package/lib/pbes2.js +6 -2
  35. package/lib/pkcs11-uri.js +3 -1
  36. package/lib/pkcs12-build.js +14 -11
  37. package/lib/pki-build.js +18 -14
  38. package/lib/possession.js +3 -1
  39. package/lib/rc2.js +6 -2
  40. package/lib/revocation-fetch.js +78 -19
  41. package/lib/scep.js +14 -6
  42. package/lib/schema-attrcert.js +4 -2
  43. package/lib/schema-c509.js +94 -78
  44. package/lib/schema-cmc.js +5 -2
  45. package/lib/schema-cmp.js +6 -3
  46. package/lib/schema-cms.js +7 -4
  47. package/lib/schema-crl.js +1 -1
  48. package/lib/schema-crmf.js +1 -1
  49. package/lib/schema-csr.js +3 -3
  50. package/lib/schema-engine.js +36 -18
  51. package/lib/schema-pkcs1.js +7 -3
  52. package/lib/schema-pkcs12.js +9 -6
  53. package/lib/schema-pkix.js +19 -3
  54. package/lib/schema-sec1.js +4 -1
  55. package/lib/schema-tsp.js +4 -1
  56. package/lib/shbs.js +7 -3
  57. package/lib/sign-scheme.js +4 -4
  58. package/lib/smime.js +18 -14
  59. package/lib/tlog.js +17 -11
  60. package/lib/tuf.js +3 -1
  61. package/lib/validator-keydesc.js +4 -1
  62. package/lib/validator-tls.js +7 -2
  63. package/lib/webcrypto.js +14 -8
  64. package/lib/x509-sign.js +20 -15
  65. package/package.json +1 -1
  66. package/sbom.cdx.json +6 -6
package/CHANGELOG.md CHANGED
@@ -4,6 +4,56 @@ 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.8.60 — 2026-10-07
8
+
9
+ Build every list a parse or an encode reads from by defining its indexes, so an accessor inherited from the array prototype cannot take an element as it is added.
10
+
11
+ ### Changed
12
+
13
+ - The library no longer has a storing append to reach for, and a check refuses one that is written anyway: a module that reads `push` or `unshift` off a prototype as a value, quotes either inside brackets, or binds either by destructuring fails the static gates with the non-storing form named in the message. A binding is read from the parse rather than from the text, so the form it is written in does not matter: a declaration, an assignment however deeply nested, a `for`-`of` or `for`-`in` header, a catch parameter, and the parameter list of any function, generator, method or arrow, with a computed, quoted or escaped key read as the name it denotes. An object literal is a different construct and is never read as one, including a literal used as a default inside a pattern. Both grow the list, so both store at an index the array has no own property at; the mutators that remain move or drop elements that already have own slots and reach no inherited accessor. A module prepending to a list counts its elements and appends them in order instead. An escaped spelling of either name is read as the name it denotes, since `p\u0075sh` and `"\x70ush"` are the same property. The ordinary call position is held to a per-module figure instead of a flat refusal, because the module that builds report lines still carries those calls: every other module in the library is held to zero and a new call in any of them fails, the one that is listed cannot grow, and converting calls without lowering a figure fails too. A module may only hold such a figure while nothing else in the library reads it, which is checked rather than asserted: a module another module requires decides something for that caller, so it is held to zero like the rest. What the check cannot settle is a property reached without naming it, through a runtime-assembled key or reflection, or a computed key whose value the program does not determine. What the rest rests on is that neither name is captured or exported anywhere in the library, so there is nothing to bind by habit, and that the non-storing primitives carry the tests for the behavior.
14
+ - The same check now recognizes the variable-width and 64-bit integer writes (`writeUIntBE` and its siblings) alongside the fixed-width ones it already knew, so a module that reads one of those off the prototype at the moment it writes a field is reported like any other.
15
+
16
+ ### Fixed
17
+
18
+ - The DER codec encodes the bytes a value describes. Four of its byte lists were built by prepending or by assigning at an index, and both are stores: an accessor inherited at an array index took the octets as they were produced, and the decisions read back off those lists were then read through the hole a store leaves. Measured: `pki.asn1.build.integer(128n)` emitted the INTEGER 0, a nine-digit integer emitted as all zeroes, -300 emitted as +212, `pki.asn1.build.oid("1.2.840.113549.1.1.11")` emitted a different OBJECT IDENTIFIER, a SEQUENCE wrapping 300 bytes emitted as `3000`, and tag 31 emitted a truncated identifier. The length octets, the identifier octets and an INTEGER's content octets are written into the output directly now, and an OID's sub-identifier octets are appended by defining each index, so all four describe the value they encode.
19
+ - A decoded value's children are appended by defining the index, in both codecs. Those children are what every format module reads its fields out of. On a SEQUENCE of the integers 1 and 2 the first value came back as 9, and a certificate parsed with the accessor's value in place of its first relative distinguished name, throwing a bare TypeError rather than a typed refusal. The segments a constructed string is joined from, an identifier's arcs, and the checked children of every constructed element are built the same way.
20
+ - Every list the schema engine builds is appended to by defining its index. The engine walks each SEQUENCE OF and SET OF into the list a format's own reader takes its elements from, and collects a structure's fields into the list that is concatenated in order into the bytes it emits, so an accessor at an array index decided both what a parse bound and what an encode wrote. The elements are also mapped through an operation captured at load, which neither dispatches through a replaceable `map` nor builds its result through the receiver's `Symbol.species`.
21
+ - The tables that decide whether a string is a token, a label, a decimal-digit string or a hexadecimal escape held an entry per character they allow and nothing at the codes they do not, and reading one at a code it had no entry for answered from the array prototype. A value installed at index 47 therefore made `pki.acme.keyAuthorization` accept a challenge token carrying `/`, which the base64url set does not allow, instead of refusing it with `acme/bad-token`. The tables have no prototype now, so there is nothing to inherit. The same helper in the name guard was already written this way.
22
+ - The certificate-policy tree is read at the depth a node was placed at. The per-depth layers were held in an array, so a depth with no node yet was a hole and reading it answered from the array prototype, which handed the policy processing a layer of the caller's choosing along with the expected policy sets its nodes carry. Creating a layer was a store at the same index. The layers are keyed in a record with no prototype now, and a generated node takes a qualifier only from a list that holds one.
23
+ - Removing a policy node constructs nothing. The removal went through `splice`, which returns what it removed and therefore builds that result through the receiver's construction protocol: it reads the list's `constructor` and then that constructor's `Symbol.species`, so an accessor installed on `Array.prototype.constructor` ran during the unlink with the live list of policy-node records as its subject, which is a window to change a surviving node's expected-policy set before the walk reads it. Binding `splice` at load does not close that lookup, because the lookup is part of what the function does. The removal now defines the surviving slots and returns nothing. The same change covers the certificate list `pki.cmp` reorders while it accumulates authenticated CA certificates, the extension list every certificate parse builds, and the key-usage list `pki.webcrypto` copies from a caller.
24
+ - An extension or an attribute is found by searching, not by indexing a selection. Written as a filter whose first element is taken, the selection is an empty list when nothing matched, and reading index 0 of an empty list answers from the array prototype: a value installed there stood in for an extension or attribute the input does not carry, and the refusal that follows the lookup was not reached. That covers the subject key identifier a CMS signer is matched by, the message-digest attribute a countersignature and an authenticated-attribute set are checked against, the key usage that decides whether an issuer may sign a CRL, the acmeIdentifier and SubjectAltName extensions a TLS-ALPN validation certificate must carry, and the authority key identifier `pki.acme.ariCertId` reads.
25
+ - Every list a parse builds is appended to by defining its index, across the certificate, CRL, CSR, PKCS#8, CMS, OCSP, TSP, CMP, CMC, PKCS#12, attribute-certificate, trust-anchor and C509 readers, the CBOR codec under C509, and the content-key recovery in `pki.cms.decrypt`, whose key-derivation input is assembled the same way. A SCEP response's certificates and CRLs are checked one at a time and collected the same way, so the certificate returned is the one the check accepted. The trust anchors a path is built against, the addresses a request is allowed to connect to, the anchors a handshake runs under, the distribution-point names a revocation check compares, and the RC2 key schedule a legacy PKCS#12 import derives are all built this way too.
26
+ - The lists built outside the readers are appended to the same way: a certificate's extension rows in the CSR, CRL and CRMF readers, the byte strings an HPKE key schedule is computed over, the peer chain `pki.transport.peerChain` returns, and the lists `pki.acme`, `pki.cmp`, `pki.est`, `pki.ct`, `pki.tuf`, `pki.tlog`, `pki.jose`, `pki.webcrypto` and the PKCS#11 URI reader build while they work.
27
+ - A transparency-log tile is requested from the path its index describes. The path segments were prepended into a list and the per-segment prefix was then applied by a read-modify-write through the hole a prepend leaves, so an accessor at an array index reduced `pki.tlog.tilePath(0, 1234067n)` to `tile/0/`.
28
+ - A C509 iPAddress constraint carries the address it encodes. Its octets were prepended into a list whose leading entry is both the prefix length checked for range and a byte the result carries, so an accessor at an array index decided the check and the emitted address together.
29
+ - A length-prefixed vector declares the bytes it writes. The wire writer under `pki.ct`'s signed-certificate-timestamp list measured the caller's view and then wrote from that same view, so a body on a resizable buffer could shrink in between: the frame then declared more bytes than it carried, and the concatenation that assembles the output failed with an error from the runtime rather than a verdict from the writer. A bound was also converted inside the comparison the length is checked by, which ran the bound's own conversion before the prefix was written and let it replace the write that states the length: measured, a vector declared a length of zero over a payload of two bytes. The body is copied into the writer's own allocation before it is measured, the bounds are converted and typed up front, and the writes that place an integer in its field are bound when the module loads.
30
+ - Every component of the preimage `pki.ct.reconstructSignedData` rebuilds is appended by defining its index, and the concatenation is bound at load. Those bytes are what a log's signature is verified over, so an accessor inherited at an array index replacing any one of them, the version, the signature type, the timestamp, the entry type, the certificate or the extensions, would put the verification on bytes the log never signed.
31
+ - The lists `pki.cms.sign` builds while it works are appended to by defining their indexes: a message's signed attributes, its certificate set and its signer infos, including a countersignature's own signing-time attribute, signed-attribute bytes, signature algorithm and signature as they are assembled into the SignerInfo that is then signed.
32
+ - A message carries the fields its caller asked for. The verbs that assemble a structure for signing collected its fields by storing at an index, so an accessor inherited at that index took a field as it was added and the structure was then signed over whatever the accessor answered. Two measured through their own verbs: `pki.cmp.build` returned a protected message carrying a transaction identifier of `eeee...` where the caller had supplied `0707...`, and the failure reason a SCEP CertRep states is a signed attribute that landed on such an index, so a FAILURE response could be signed stating a reason the caller never gave. Every list outside `pki.inspect` is appended to by defining its index now: `pki.cmp`'s messages and its accumulated CA certificates, `pki.ocsp`'s signed requests and responses, `pki.scep`'s responses, the certificate, CRL, attribute-certificate and certification-request signers, `pki.cms.encrypt`'s recipient infos, `pki.cmc`'s controls, the shared extension and name builders every signing verb embeds, the bags a PKCS#12 file carries under its MAC, the headers and parts of an S/MIME message, the key-derivation parameters an encrypted structure declares, the authentication path a hash-based signature is formed from, and the lists `pki.est`, `pki.acme`, `pki.webcrypto` and the TLS and key-description validators build while they work.
33
+ - `pki.path.fetchingChecker` only fetches a revocation location whose address it is allowed to reach, and both halves of that decision are now bound when the module loads. The test for whether a host is an address literal, which decides whether the blocked-address check runs at all, and the blocked-address check itself were each read off their module at the moment they were used. Replacing the first made every host look like a name so the check was skipped; replacing the second made it answer that nothing is blocked. Either way a distribution point naming a loopback or link-local address was fetched. The operations that read a host, a response body and a media type are bound at load for the same reason, and the bracket test around an IPv6 literal reads character codes rather than comparing substrings.
34
+ - A revocation response is read for the fields it carries itself. The transport seam returns a record of a status, headers and a body, and a transport handing back an object carrying none of them had every one of those reads answered from the object prototype, so an inherited status of 200 with an inherited body and headers was accepted as a revocation response. The three fields are read as the response's own now.
35
+ - A revocation response body is bytes, a string, or absent; a status is the number 200; a media type is a string. Anything else was converted, which ran the value's own conversion: a transport handing back an object decided the bytes a CRL or an OCSP response was then parsed from, a header holding one named the media type from itself, and a status holding one ran during the refusal that reports it. Binding the conversion does not close this, because what runs is the value's hook rather than the conversion. A body that is neither bytes nor a string is refused now and names that as the reason, a status is reported through the formatter that does not convert it, and a media type that is not a string reads as absent.
36
+ - A revocation response's media type is read. It was parsed with the MIME entity reader, which takes the bytes of a whole entity rather than one header value, so the attempt failed for every response and the media type was reported as absent each time. The notes that compare it could therefore never appear, including the one stating that a CRL or an OCSP response arrived under an unexpected media type. The header value is read directly now, taking the span before any parameter and folding its case. The media type still only annotates a verdict and never selects a parser, which is chosen by where the location came from.
37
+ - `pki.path.fetchingChecker` asked a certificate for its extension list by a read that answered from `Object.prototype` when the certificate carries none, so a list installed there was decoded and reported on. It is an own read now, and a value that is not an array is treated as no extensions.
38
+ - A signer refuses to emit an artifact that violates the profile it is signing under, and that refusal is now reached. `pki.x509.sign`, `pki.csr.sign` and `pki.crl.sign` lint what the spec describes before they sign and throw `x509/profile-violation`, `csr/profile-violation` or `crl/profile-violation` naming the rule. The findings that decision reads were collected by storing at an index, so an accessor inherited from the array prototype took each finding as it was recorded and the index answered with the accessor's value: a benign severity there stood in for the error finding and the signer emitted the artifact instead. Measured, `pki.x509.sign` returned a signed CA certificate whose key usage omits keyCertSign. The profile-specific report the check adds for a caller-named profile was substituted whole by the same means. Every list in the linting engine is appended to by defining its index now, so the rules a signer refuses on are the rules that ran. The records that decision is read from were plain objects too, so a field a record does not carry was both written and read through the prototype chain: a finding's severity could be taken off it by an inherited setter, a byte reader's success could be answered as a fatal error, and a rule row that omits an optional field read it from `Object.prototype`, where a value supplied for `appliesTo` or `effectiveDate` stops the rule running at all. Those rows now carry no prototype, the optional reads are own reads, and a report's fields are defined rather than assigned.
39
+
40
+ ## v0.8.59 — 2026-10-07
41
+
42
+ Render an object identifier, match a header name and read a media type through operations a replacement cannot answer.
43
+
44
+ ### Changed
45
+
46
+ - The check that holds these modules to captured operations now counts a method reached through an index, not only one reached through a call. It is the same receiver one bracket over, and the earlier form could not see it: two live copies in the RFC 5280 policy-mapping walk, where the copy becomes a node's expected policy set, were found by other means.
47
+
48
+ ### Fixed
49
+
50
+ - An object identifier is rendered to its dotted form through operations bound when the module loaded, and its arcs are collected by defining each index rather than by appending to the array. Three different replacements each decided that string, which is the value `pki.oid` resolves, which selects a signature algorithm and which names an extension: a replaced integer conversion made the content octets of `1.2.3` render as `1.3.4`, and an accessor inherited at the first index of every array took each arc as it was collected and made the same octets render as `0.1.3`. Appending stores at an index, and a store consults the prototype chain for a setter, so a list this build assembles is now built by defining its indexes.
51
+ - A media type is read on the delimiter character and folded through a captured conversion, in `pki.acme`, `pki.cmp` and `pki.ct`. The token decides which parser a response body is handed to, and it was produced by splitting, trimming and folding through live operations. Splitting is now a character scan, which this toolkit requires in its libraries because a separator's own split hook is part of that operation.
52
+ - `pki.est` reads a content-type parameter through captured operations, and collects the segments it searches by defining each index. That is where the RFC 7030 multipart boundary comes from, so the value decides where a multi-part response is divided: measured, a header declaring `boundary=wire` yielded `forged`, both through a replaced append and through an accessor inherited at an array index. The name comparison that selects a parameter, the slicing that extracts it and the quote stripping around it were each live, as were the name and value of every header on a part, which is where a part's own content type comes from: measured, a part declaring `application/pkcs8` was read as `application/pkcs7-mime` carrying `smime-type=certs-only`. Its two case-insensitive header lookups are folded through the capture on both sides of the comparison.
53
+ - `pki.acme` parses a Link header through captured operations: the header is divided into links, each link into parameters, and the target cut out of its angle brackets, all through operations bound when the module loaded. The relation decides whether a header names a resource the client will follow and the target is the URL it would fetch, and both were produced by methods read off the live prototype: measured, a header stating one alternate chain resolved to a different URL.
54
+ - `pki.cmc.build` selects which request arm a message carries through a selection that reads each candidate once and builds its own result, rather than through a method that is both read off the prototype and specified to construct its result through a replaceable protocol.
55
+ - The algorithm, extension and criticality tables that `pki.path.validate`, `pki.cms`, `pki.crl`, `pki.key`, `pki.webcrypto`, `pki.x509` and the signature-scheme registry build when they load are built through captured walks, so a replacement installed before a module loads cannot decide which rows exist.
56
+
7
57
  ## v0.8.58 — 2026-10-07
8
58
 
9
59
  Connect under the trust anchors, the address and the identity check the request named, not the ones a second read answers with.
package/lib/acme.js CHANGED
@@ -88,8 +88,13 @@ var _sortI = intrinsic.sort;
88
88
  var _objectKeys = intrinsic.keys;
89
89
  var _lower = intrinsic.toLowerCase;
90
90
  var _StringI = intrinsic.String;
91
+ var _acmeTrim = intrinsic.uncurry(String.prototype.trim);
91
92
  var _stringify = intrinsic.stringify;
92
- var _push = intrinsic.push;
93
+ /** @internal `append`, not a captured `push`: appending with `push` STORES at the index, and a store
94
+ * walks the prototype chain for a setter, so an accessor installed at an array index receives each
95
+ * element as it is appended and can define something else in its place. `append` defines the index
96
+ * on the array itself. */
97
+ var _push = intrinsic.append;
93
98
  var _pop = intrinsic.pop;
94
99
  var _shift = intrinsic.shift;
95
100
  var _join = intrinsic.join;
@@ -98,6 +103,7 @@ var _split = intrinsic.uncurry(String.prototype.split);
98
103
  var _parseIntI = intrinsic.parseInt;
99
104
  var _numToString = intrinsic.numberToString;
100
105
  var _strSlice = intrinsic.uncurry(String.prototype.slice);
106
+ var _charAtA = intrinsic.uncurry(String.prototype.charAt);
101
107
  var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
102
108
  var _charAt = intrinsic.uncurry(String.prototype.charAt);
103
109
  var pkgVersion = require("../package.json").version;
@@ -351,7 +357,7 @@ var SPEC_NAMES = intrinsic.create(null);
351
357
  var kinds = _objectKeys(SPECS);
352
358
  for (var i = 0; i < kinds.length; i++) {
353
359
  var fields = SPECS[kinds[i]], names = [];
354
- for (var j = 0; j < fields.length; j++) names[j] = fields[j].name;
360
+ for (var j = 0; j < fields.length; j++) _push(names, fields[j].name);
355
361
  SPEC_NAMES[kinds[i]] = names;
356
362
  }
357
363
  })();
@@ -750,14 +756,18 @@ async function verifyTlsAlpn01(certDer, token, accountJwk, identifier) {
750
756
  if (!_isString(idType)) throw E("acme/bad-input", "an identifier { type, value } is required");
751
757
  var cert = x509.parse(certDer);
752
758
  var exts = cert.extensions || [];
753
- var acmeExt = exts.filter(function (e) { return e.oid === OID_ACME_IDENTIFIER; })[0];
759
+ /** @internal Each selection returns the first matching row or null. `filter(...)[0]` indexes a
760
+ * result that is EMPTY when the extension is absent, and index 0 of an empty array answers from
761
+ * the array prototype, so a value installed there would stand in for an extension the validation
762
+ * certificate does not carry and the refusals below would not be reached. */
763
+ var acmeExt = guard.list.firstMatch(exts, function (e) { return e.oid === OID_ACME_IDENTIFIER; });
754
764
  if (!acmeExt) throw E("acme/bad-tlsalpn", "the validation certificate is missing the acmeIdentifier extension (RFC 8737 sec. 3)");
755
765
  if (!acmeExt.critical) throw E("acme/bad-tlsalpn", "the acmeIdentifier extension must be critical (RFC 8737 sec. 3)");
756
766
  var auth = _readAcmeIdentifier(acmeExt.value);
757
767
  var ka = await keyAuthorization(token, accountJwk);
758
768
  var expected = await _sha256(Buffer.from(ka, "ascii"));
759
769
  if (!auth.equals(expected)) throw E("acme/bad-tlsalpn", "the acmeIdentifier digest does not match the key authorization");
760
- var sanExt = exts.filter(function (e) { return e.oid === OID_SAN; })[0];
770
+ var sanExt = guard.list.firstMatch(exts, function (e) { return e.oid === OID_SAN; });
761
771
  if (!sanExt) throw E("acme/bad-tlsalpn", "the validation certificate is missing the SubjectAltName");
762
772
  var san = _extDecoders.byOid[OID_SAN](sanExt.value);
763
773
  if (!san.names || san.names.length !== 1) throw E("acme/bad-tlsalpn", "the SubjectAltName must carry EXACTLY ONE entry (RFC 8737 sec. 3)");
@@ -1381,7 +1391,7 @@ async function keyChange(o) {
1381
1391
  function ariCertId(certDer) {
1382
1392
  if (!guard.bytes.isByteSource(certDer)) throw E("acme/bad-input", "ariCertId requires a DER certificate BufferSource");
1383
1393
  var cert = x509.parse(certDer);
1384
- var akiExt = (cert.extensions || []).filter(function (e) { return e.oid === OID_AKI; })[0];
1394
+ var akiExt = guard.list.firstMatch(cert.extensions || [], function (e) { return e.oid === OID_AKI; });
1385
1395
  if (!akiExt) throw E("acme/bad-certid", "the certificate has no authorityKeyIdentifier extension (RFC 9773 sec. 4.1)");
1386
1396
  var aki = _extDecoders.byOid[OID_AKI](akiExt.value);
1387
1397
  if (!aki || !Buffer.isBuffer(aki.keyIdentifier)) throw E("acme/bad-certid", "the authorityKeyIdentifier has no keyIdentifier field (RFC 9773 sec. 4.1)");
@@ -1707,61 +1717,71 @@ function _hasCtlOctet(s) {
1707
1717
  }
1708
1718
 
1709
1719
  function _splitLinkValues(s) {
1720
+ /** @internal The header is cut through the captured operations, the same as the parameters below:
1721
+ * each piece becomes a link whose relation decides whether this client follows it. */
1710
1722
  var out = [], start = 0, inAngle = false, inQuote = false;
1711
1723
  for (var i = 0; i < s.length; i++) {
1712
- var ch = s.charAt(i);
1724
+ var ch = _charAtA(s, i);
1713
1725
  if (inQuote) { if (ch === "\\") { i++; continue; } if (ch === "\"") inQuote = false; continue; }
1714
1726
  if (ch === "\"") { inQuote = true; continue; }
1715
1727
  if (ch === "<") inAngle = true;
1716
1728
  else if (ch === ">") inAngle = false;
1717
- else if (ch === "," && !inAngle) { out.push(s.slice(start, i)); start = i + 1; }
1729
+ else if (ch === "," && !inAngle) { intrinsic.append(out, _strSlice(s, start, i)); start = i + 1; }
1718
1730
  }
1719
1731
  if (inQuote || inAngle) throw E("acme/bad-link", "the Link header has an unterminated quote or angle bracket (RFC 8288)");
1720
- out.push(s.slice(start));
1732
+ intrinsic.append(out, _strSlice(s, start));
1721
1733
  return out;
1722
1734
  }
1723
1735
  function _splitLinkParams(rest) {
1724
1736
  var parts = [], start = 0, inQuote = false;
1725
1737
  for (var i = 0; i < rest.length; i++) {
1726
- var ch = rest.charAt(i);
1738
+ var ch = _charAtA(rest, i);
1727
1739
  if (inQuote) { if (ch === "\\") { i++; continue; } if (ch === "\"") inQuote = false; continue; }
1728
1740
  if (ch === "\"") inQuote = true;
1729
- else if (ch === ";") { parts.push(rest.slice(start, i)); start = i + 1; }
1741
+ else if (ch === ";") { intrinsic.append(parts, _strSlice(rest, start, i)); start = i + 1; }
1730
1742
  }
1731
- parts.push(rest.slice(start));
1743
+ intrinsic.append(parts, _strSlice(rest, start));
1732
1744
  var out = [];
1733
1745
  for (var j = 0; j < parts.length; j++) {
1734
1746
  var p = _trimOWS(parts[j]);
1735
1747
  if (p === "") { if (j === 0) continue; throw E("acme/bad-link", "an empty Link parameter (a ';' with no parameter) is malformed (RFC 8288): " + JSON.stringify(rest)); }
1736
- var eq = p.indexOf("=");
1737
- var name = (eq === -1 ? p : _trimOWS(p.slice(0, eq))).toLowerCase();
1748
+ /** @internal The parameter name and value are cut and folded through the captured operations.
1749
+ * A Link parameter is what names the relation this client follows, and read off the prototype a
1750
+ * replacement chose it: measured, a header stating `rel=next` parsed as `alternate`. */
1751
+ var eq = intrinsic.stringIndexOf(p, "=");
1752
+ var name = _lower(eq === -1 ? p : _trimOWS(_strSlice(p, 0, eq)));
1738
1753
  if (!pkix.allCharsIn(name, _LINK_TOKEN_TABLE)) throw E("acme/bad-link", "a Link parameter name must be a token (RFC 8288 / RFC 7230): " + JSON.stringify(p));
1739
- if (eq === -1) { out.push({ name: name, value: "", hasValue: false }); continue; }
1740
- var v = _trimOWS(p.slice(eq + 1));
1741
- if (v.length >= 2 && v.charAt(0) === "\"" && v.charAt(v.length - 1) === "\"") {
1742
- var inner = v.slice(1, -1);
1754
+ if (eq === -1) { intrinsic.append(out, { name: name, value: "", hasValue: false }); continue; }
1755
+ var v = _trimOWS(_strSlice(p, eq + 1));
1756
+ if (v.length >= 2 && _charAtA(v, 0) === "\"" && _charAtA(v, v.length - 1) === "\"") {
1757
+ var inner = _strSlice(v, 1, -1);
1743
1758
  if (!_isValidQuotedInterior(inner)) throw E("acme/bad-link", "a quoted Link parameter value is malformed (an unescaped quote or dangling backslash, RFC 7230): " + JSON.stringify(p));
1744
1759
  v = _unescapeQuotedPair(inner);
1745
1760
  } else if (!pkix.allCharsIn(v, _LINK_TOKEN_TABLE)) {
1746
1761
  throw E("acme/bad-link", "an unquoted Link parameter value must be a non-empty token (RFC 8288 / RFC 7230): " + JSON.stringify(p));
1747
1762
  }
1748
- out.push({ name: name, value: v, hasValue: true });
1763
+ intrinsic.append(out, { name: name, value: v, hasValue: true });
1749
1764
  }
1750
1765
  return out;
1751
1766
  }
1752
1767
  function _relHas(rel, relName) {
1753
- var toks = String(rel).split(" ");
1754
- for (var i = 0; i < toks.length; i++) { if (toks[i].toLowerCase() === relName) return true; }
1768
+ /** @internal The relation names are scanned on the delimiter CHARACTER, which hard rule 11 requires
1769
+ * in lib/, and each token is folded through the captured conversion: the match decides whether a
1770
+ * Link header names the resource this client then follows. */
1771
+ var toks = intrinsic.splitChar(_StringI(rel), 32);
1772
+ for (var i = 0; i < toks.length; i++) { if (_lower(toks[i]) === relName) return true; }
1755
1773
  return false;
1756
1774
  }
1757
1775
  function _parseLinkValue(raw) {
1758
1776
  var s = _trimOWS(raw);
1759
1777
  if (s === "") return null;
1760
- var gt = s.indexOf(">");
1761
- if (s.charAt(0) !== "<" || gt === -1) throw E("acme/bad-link", "a Link value must be <URI-Reference> with parameters (RFC 8288): " + JSON.stringify(raw));
1778
+ var gt = intrinsic.stringIndexOf(s, ">");
1779
+ if (_charAtA(s, 0) !== "<" || gt === -1) throw E("acme/bad-link", "a Link value must be <URI-Reference> with parameters (RFC 8288): " + JSON.stringify(raw));
1762
1780
  var rest = _stripLeadingOWS(s, gt + 1);
1763
- if (rest !== "" && rest.charAt(0) !== ";") throw E("acme/bad-link", "a Link value's parameters must be introduced by ';' (RFC 8288): " + JSON.stringify(raw));
1764
- var uri = s.slice(1, gt);
1781
+ if (rest !== "" && _charAtA(rest, 0) !== ";") throw E("acme/bad-link", "a Link value's parameters must be introduced by ';' (RFC 8288): " + JSON.stringify(raw));
1782
+ /** @internal The target this client may follow is cut through the captured operation: read off the
1783
+ * prototype, a replacement decided the URL a Link header resolves to. */
1784
+ var uri = _strSlice(s, 1, gt);
1765
1785
  if (_linkUriInvalid(uri)) throw E("acme/bad-link", "a Link URI-Reference contains a character, percent-escape, or encoded dot-segment not permitted by RFC 3986: " + JSON.stringify(raw));
1766
1786
  var params = _splitLinkParams(rest), rel = "", relSeen = false, anchor = null, anchorSeen = false;
1767
1787
  for (var i = 0; i < params.length; i++) {
@@ -1780,14 +1800,16 @@ function _parseLinkRelation(headers, base, relName, opts) {
1780
1800
  var raw = null;
1781
1801
  var hNames = guard.identifier.optionNames(headers);
1782
1802
  for (var hi = 0; hi < hNames.length; hi++) {
1783
- if (hNames[hi].toLowerCase() === "link") { raw = headers[hNames[hi]]; break; }
1803
+ if (_lower(hNames[hi]) === "link") { raw = headers[hNames[hi]]; break; }
1784
1804
  }
1785
1805
  if (raw == null) return [];
1786
1806
  var baseUrl = new _URL(base), baseOrigin = _urlOrigin(baseUrl), baseHref = _urlHref(baseUrl);
1787
1807
  var baseKey = _dedupKey(baseHref);
1788
- var fields = Array.isArray(raw) ? raw : [raw], out = [], seen = Object.create(null), distinctCount = 0, totalBytes = 0;
1808
+ var fields = intrinsic.isArray(raw) ? raw : [raw], out = [], seen = intrinsic.create(null), distinctCount = 0, totalBytes = 0;
1789
1809
  for (var fi = 0; fi < fields.length; fi++) {
1790
- var field = String(fields[fi]);
1810
+ /** @internal The header field is converted through the capture: read through the live global, a
1811
+ * replacement supplied the whole Link header and with it the target this client resolves. */
1812
+ var field = _StringI(fields[fi]);
1791
1813
  totalBytes += field.length + 1;
1792
1814
  if (totalBytes > LINK_HEADER_MAX_BYTES) throw E("acme/bad-link", "the Link header(s) exceed the " + LINK_HEADER_MAX_BYTES + "-byte aggregate cap (RFC 8288, CWE-770)");
1793
1815
  if (_hasCtlOctet(field)) throw E("acme/bad-link", "a Link header must not contain control octets (RFC 9110 field-value)");
@@ -1821,7 +1843,7 @@ function _parseLinkRelation(headers, base, relName, opts) {
1821
1843
  if (seen[key]) continue;
1822
1844
  seen[key] = true;
1823
1845
  distinctCount += 1;
1824
- if (key !== baseKey) out.push(resolved);
1846
+ if (key !== baseKey) _push(out,resolved);
1825
1847
  }
1826
1848
  }
1827
1849
  if (opts.singleton && distinctCount > 1) throw E("acme/bad-link", "the Link header names more than one distinct rel=\"" + relName + "\" target (RFC 8288 sec. 3.3, a singleton relation)");
@@ -1919,7 +1941,7 @@ function _resignKeysOf(raw) {
1919
1941
  if (!_isObject(e)) throw E("acme/bad-input", "each resignKeys entry must be { alg: string, key: CryptoKey } (RFC 8555 sec. 6.2); entry " + i + " is not");
1920
1942
  var a = e.alg, k = e.key;
1921
1943
  if (!_isString(a) || !wcEngine.isCryptoKeyLike(k)) throw E("acme/bad-input", "each resignKeys entry must be { alg: string, key: CryptoKey } (RFC 8555 sec. 6.2); entry " + i + " is not");
1922
- out[i] = intrinsic.freeze({ alg: a, key: k });
1944
+ _push(out, intrinsic.freeze({ alg: a, key: k }));
1923
1945
  }
1924
1946
  return intrinsic.freeze(out);
1925
1947
  }
@@ -2202,7 +2224,8 @@ function client(directoryUrl, opts) {
2202
2224
  function _pollAuthorization(url, budget) { return _poll("authorization", url, budget); }
2203
2225
 
2204
2226
  function _readCertChain(res) {
2205
- var ctToken = String(res.headers["content-type"] || "").split(";")[0].trim().toLowerCase();
2227
+ var ctParts = intrinsic.splitChar(_StringI(res.headers["content-type"] || ""), 59);
2228
+ var ctToken = _lower(_acmeTrim(ctParts.length ? ctParts[0] : ""));
2206
2229
  if (ctToken !== "application/pem-certificate-chain") throw E("acme/bad-certificate-chain", "the certificate download returned an unexpected media type " + JSON.stringify(ctToken) + " (expected application/pem-certificate-chain, RFC 8555 sec. 7.4.2)");
2207
2230
  var chain = _splitPemChain(_bodyText(res));
2208
2231
  return { certificate: chain[0], chain: chain.slice(1), certificates: chain };
package/lib/alt-sig.js CHANGED
@@ -73,7 +73,9 @@ var _forEach = intrinsic.forEach;
73
73
  var _map = intrinsic.map;
74
74
  var _bufferFrom = intrinsic.bufferFrom;
75
75
  var _bufferConcat = intrinsic.bufferConcat;
76
- var _push = intrinsic.push;
76
+ /** @internal `append`, not a captured `push`: a store at an index walks the prototype chain for a
77
+ * setter, so an accessor there takes the element being appended. `append` defines it. */
78
+ var _push = intrinsic.append;
77
79
 
78
80
  function _err(code, message, cause) { return new AltSigError(code, message, cause); }
79
81
 
package/lib/asn1-der.js CHANGED
@@ -41,7 +41,6 @@ var frameworkError = require("./framework-error");
41
41
  var guard = require("./guard-all");
42
42
  var intrinsic = require("./guard-intrinsic");
43
43
  var _isArray = intrinsic.isArray;
44
- var _unshift = intrinsic.unshift;
45
44
  var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
46
45
  /** @internal A Date's fields are read through the prototype's own getters, captured at load, so
47
46
  * a getter the instance carries or one replaced later cannot change what a time encodes to or
@@ -329,7 +328,12 @@ function _decodeTLV(buf, start, limit, depth, maxDepth, ber, strDepth, ctr) {
329
328
  if (cp + 2 > limit) throw new Asn1Error("asn1/truncated", "indefinite-length value is missing its end-of-contents octets");
330
329
  if (buf[cp] === 0x00 && buf[cp + 1] === 0x00) break;
331
330
  child = _decodeTLV(buf, cp, limit, depth + 1, maxDepth, ber, strDepth, ctr);
332
- children.push(child.node);
331
+ /** @internal The decoded child is APPENDED by defining its index. Stored with `push`, the
332
+ * store walks the prototype chain for a setter, so an accessor at an array index received
333
+ * each child as it was decoded and could define another node in its place: measured, a
334
+ * SEQUENCE of two INTEGERs decoded with its first value changed from 1 to 9. Every format
335
+ * module in this toolkit reads its fields out of this list. */
336
+ intrinsic.append(children, child.node);
333
337
  cp = child.end;
334
338
  }
335
339
  contentEnd = cp;
@@ -337,7 +341,7 @@ function _decodeTLV(buf, start, limit, depth, maxDepth, ber, strDepth, ctr) {
337
341
  } else {
338
342
  while (cp < contentEnd) {
339
343
  child = _decodeTLV(buf, cp, contentEnd, depth + 1, maxDepth, ber, strDepth, ctr);
340
- children.push(child.node);
344
+ intrinsic.append(children, child.node);
341
345
  cp = child.end;
342
346
  }
343
347
  end = contentEnd;
@@ -366,9 +370,9 @@ function _decodeTLV(buf, start, limit, depth, maxDepth, ber, strDepth, ctr) {
366
370
  if (children[s].tagClass !== "universal" || children[s].tagNumber !== TAGS.OCTET_STRING) {
367
371
  throw new Asn1Error("asn1/bad-constructed-string", "a constructed OCTET STRING segment must itself be an OCTET STRING");
368
372
  }
369
- segments.push(children[s].content);
373
+ intrinsic.append(segments, children[s].content);
370
374
  }
371
- node.content = Buffer.concat(segments);
375
+ node.content = intrinsic.bufferConcat(segments);
372
376
  node.constructed = false;
373
377
  node.children = null;
374
378
  }
@@ -481,9 +485,9 @@ function readOctetStringImplicit(node, tag) {
481
485
  if (seg.tagClass !== "universal" || seg.tagNumber !== TAGS.OCTET_STRING || seg.constructed || !seg.content) {
482
486
  throw new Asn1Error("asn1/bad-constructed-string", "a constructed OCTET STRING segment must itself be an OCTET STRING");
483
487
  }
484
- segments.push(seg.content);
488
+ intrinsic.append(segments, seg.content);
485
489
  }
486
- return Buffer.concat(segments);
490
+ return intrinsic.bufferConcat(segments);
487
491
  }
488
492
  _expectPrimitive(node, "readOctetStringImplicit");
489
493
  return node.content;
@@ -566,8 +570,16 @@ function decodeOidContent(buf) {
566
570
  "OBJECT IDENTIFIER carries more than " + constants.LIMITS.OID_MAX_SUBIDENTIFIERS + " sub-identifiers");
567
571
  }
568
572
  var value = 0n;
569
- for (var k = arcStart; k <= i; k++) value = value * 128n + BigInt(buf[k] & 0x7f);
570
- arcs.push(value);
573
+ /** @internal The arc is accumulated through the captured conversion. Read off the global, a
574
+ * replacement decides the sub-identifier, and therefore the dotted string every algorithm and
575
+ * extension decision in this toolkit is made on: measured, the same two bytes rendered as
576
+ * `1.2.3` and as `1.2.4`. */
577
+ for (var k = arcStart; k <= i; k++) value = value * 128n + intrinsic.BigInt(buf[k] & 0x7f);
578
+ /** @internal `append`, not a captured `push`: appending with `push` STORES at the index, and a
579
+ * store walks the prototype chain for a setter, so an accessor installed at
580
+ * `Array.prototype[0]` took the arc and answered with another. `append` defines the index on
581
+ * the array itself. */
582
+ intrinsic.append(arcs, value);
571
583
  arcStart = i + 1;
572
584
  }
573
585
  }
@@ -577,9 +589,12 @@ function decodeOidContent(buf) {
577
589
  if (first < 40n) { a1 = 0n; a2 = first; }
578
590
  else if (first < 80n) { a1 = 1n; a2 = first - 40n; }
579
591
  else { a1 = 2n; a2 = first - 80n; }
580
- var out = [a1.toString(), a2.toString()];
581
- for (var j = 1; j < arcs.length; j++) out.push(arcs[j].toString());
582
- return out.join(".");
592
+ /** @internal Each arc is rendered through the captured conversion. Read off the prototype, the
593
+ * value that becomes the dotted identifier is whatever a replacement returns, and the identifier
594
+ * is what every algorithm and extension decision in this toolkit is made on. */
595
+ var out = [intrinsic.bigIntToString(a1, 10), intrinsic.bigIntToString(a2, 10)];
596
+ for (var j = 1; j < arcs.length; j++) intrinsic.append(out, intrinsic.bigIntToString(arcs[j], 10));
597
+ return intrinsic.join(out, ".");
583
598
  }
584
599
 
585
600
  function _decodeText(buf, encoding) {
@@ -755,12 +770,22 @@ function encodeLength(n) {
755
770
  if (typeof n !== "number" || !isFinite(n) || n < 0 || Math.floor(n) !== n) {
756
771
  throw new Asn1Error("asn1/bad-length", "length must be a non-negative integer");
757
772
  }
758
- if (n < 0x80) return Buffer.from([n]);
759
- var bytes = [];
773
+ if (n < 0x80) return intrinsic.bufferFrom([n]);
774
+ /** @internal The octets are counted and then written into the output from its end, rather than
775
+ * prepended into an array. A prepend STORES at an index, and a store walks the prototype chain
776
+ * for a setter, so an accessor at an array index took each octet as it was produced and left the
777
+ * list reading back whatever its getter answers. These are the length octets of every DER element
778
+ * longer than 127 bytes: measured, a SEQUENCE wrapping a 300-byte OCTET STRING emitted as `3000`,
779
+ * an empty SEQUENCE. A typed array handles its own integer-indexed writes and consults no
780
+ * prototype accessor, which is why the output is filled directly. */
781
+ var count = 0;
782
+ for (var probe = n; probe > 0; probe = intrinsic.floor(probe / 256)) count++;
783
+ if (count > 126) throw new Asn1Error("asn1/length-too-large", "length needs more than 126 octets");
784
+ var out = intrinsic.bufferAlloc(count + 1);
785
+ out[0] = 0x80 | count;
760
786
  var v = n;
761
- while (v > 0) { _unshift(bytes, v & 0xff); v = Math.floor(v / 256); }
762
- if (bytes.length > 126) throw new Asn1Error("asn1/length-too-large", "length needs more than 126 octets");
763
- return Buffer.concat([Buffer.from([0x80 | bytes.length]), Buffer.from(bytes)]);
787
+ for (var i = count; i >= 1; i--) { out[i] = v & 0xff; v = intrinsic.floor(v / 256); }
788
+ return out;
764
789
  }
765
790
 
766
791
  /** @internal The identifier comes from the one function that composes one. Writing the octet here
@@ -831,12 +856,24 @@ function encodeIdentifier(classBits, constructed, tagNumber) {
831
856
  ") and is not assigned to any type, so no encoding of it is a DER element");
832
857
  }
833
858
  var lead = cls | (constructed ? CONSTRUCTED_BIT : 0);
834
- if (tagNumber < 0x1f) return Buffer.from([lead | tagNumber]);
835
- var body = [];
859
+ if (tagNumber < 0x1f) return intrinsic.bufferFrom([lead | tagNumber]);
860
+ /** @internal Counted, then written into the output from its end, for the reason `encodeLength` is:
861
+ * a prepend stores at an index and a store consults a setter inherited there. These are the
862
+ * identifier octets of a high-tag-number element, so they name the type every reader dispatches
863
+ * on: measured, tag 31 emitted as `1f00`. Every octet but the last carries the continuation bit. */
864
+ var count = 0;
865
+ for (var probe = tagNumber; ; probe = intrinsic.floor(probe / 128)) {
866
+ count++;
867
+ if (probe < 128) break;
868
+ }
869
+ var out = intrinsic.bufferAlloc(count + 1);
870
+ out[0] = lead | 0x1f;
836
871
  var v = tagNumber;
837
- do { _unshift(body, v & 0x7f); v = Math.floor(v / 128); } while (v > 0);
838
- for (var i = 0; i < body.length - 1; i++) body[i] |= 0x80;
839
- return Buffer.concat([Buffer.from([lead | 0x1f]), Buffer.from(body)]);
872
+ for (var i = count; i >= 1; i--) {
873
+ out[i] = (v & 0x7f) | (i === count ? 0x00 : 0x80);
874
+ v = intrinsic.floor(v / 128);
875
+ }
876
+ return out;
840
877
  }
841
878
 
842
879
  /**
@@ -933,19 +970,29 @@ function intToDer(v) {
933
970
  v = BigInt(v);
934
971
  }
935
972
  if (typeof v !== "bigint") throw new Asn1Error("asn1/bad-integer", "integer must be number or BigInt");
936
- if (v === 0n) return Buffer.from([0]);
937
- var bytes = [];
973
+ if (v === 0n) return intrinsic.bufferFrom([0]);
974
+ /** @internal The content octets are written into the output directly, in both signs. The positive
975
+ * branch prepended into an array and then read `bytes[0]` to decide whether the value needed a
976
+ * leading zero to read as positive, and the negative branch stored at each index; both are stores,
977
+ * so an accessor inherited at an array index took the octets and answered the decision. Measured:
978
+ * 128 encoded as the INTEGER 0, a nine-digit value as all zeroes, and -300 as +212. A typed array
979
+ * consults no prototype accessor for an integer-indexed write, and `bufferAlloc` zero-fills, so
980
+ * the leading zero is the allocation rather than a second prepend. */
938
981
  if (v > 0n) {
939
- var t = v;
940
- while (t > 0n) { _unshift(bytes, Number(t & 0xffn)); t >>= 8n; }
941
- if (bytes[0] & 0x80) _unshift(bytes, 0x00);
942
- } else {
943
- var len = 1;
944
- while (v < -(1n << BigInt(8 * len - 1))) len += 1;
945
- var tc = (1n << BigInt(8 * len)) + v;
946
- for (var i = len - 1; i >= 0; i--) { bytes[i] = Number(tc & 0xffn); tc >>= 8n; }
982
+ var n = 0;
983
+ for (var t = v; t > 0n; t >>= 8n) n++;
984
+ var pad = ((v >> intrinsic.BigInt(8 * (n - 1))) & 0xffn) >= 0x80n ? 1 : 0;
985
+ var outP = intrinsic.bufferAlloc(n + pad);
986
+ var tp = v;
987
+ for (var i = n + pad - 1; i >= pad; i--) { outP[i] = intrinsic.Number(tp & 0xffn); tp >>= 8n; }
988
+ return outP;
947
989
  }
948
- return Buffer.from(bytes);
990
+ var len = 1;
991
+ while (v < -(1n << intrinsic.BigInt(8 * len - 1))) len += 1;
992
+ var tc = (1n << intrinsic.BigInt(8 * len)) + v;
993
+ var outN = intrinsic.bufferAlloc(len);
994
+ for (var j = len - 1; j >= 0; j--) { outN[j] = intrinsic.Number(tc & 0xffn); tc >>= 8n; }
995
+ return outN;
949
996
  }
950
997
 
951
998
  function encodeOidContent(dotted) {
@@ -958,15 +1005,22 @@ function encodeOidContent(dotted) {
958
1005
  var subids = intrinsic.concat([a1 * 40n + a2], arcs.slice(2));
959
1006
  var out = [];
960
1007
  for (var s = 0; s < subids.length; s++) {
961
- var body = [];
962
1008
  var v = subids[s];
963
- do { _unshift(body, Number(v & 0x7fn)); v >>= 7n; } while (v > 0n);
964
- if (body.length > constants.LIMITS.OID_MAX_SUBIDENTIFIER_BYTES) {
1009
+ /** @internal Each sub-identifier's octets are counted and then appended most-significant first,
1010
+ * rather than prepended into a list and patched in place. A prepend stores at an index, and the
1011
+ * continuation bit was then set by a read-modify-write that read back through the hole a store
1012
+ * leaves under an inherited setter. This is the OID an algorithm or a signature is named by:
1013
+ * measured, sha256WithRSAEncryption emitted as `0609000000008000000000`, a different OID. */
1014
+ var count = 1;
1015
+ for (var probe = v >> 7n; probe > 0n; probe >>= 7n) count++;
1016
+ if (count > constants.LIMITS.OID_MAX_SUBIDENTIFIER_BYTES) {
965
1017
  throw new OidError("oid/subidentifier-too-large",
966
1018
  "OID sub-identifier exceeds " + constants.LIMITS.OID_MAX_SUBIDENTIFIER_BYTES + " octets");
967
1019
  }
968
- for (var k = 0; k < body.length - 1; k++) body[k] |= 0x80;
969
- for (var j = 0; j < body.length; j++) out.push(body[j]);
1020
+ for (var k = count - 1; k >= 0; k--) {
1021
+ var octet = intrinsic.Number((v >> intrinsic.BigInt(7 * k)) & 0x7fn);
1022
+ intrinsic.append(out, k === 0 ? octet : octet | 0x80);
1023
+ }
970
1024
  }
971
1025
  return Buffer.from(out);
972
1026
  }
@@ -1160,7 +1214,12 @@ function _intContent(v, who) {
1160
1214
  function _asBufferArray(arr, who) {
1161
1215
  if (!_isArray(arr)) throw new Asn1Error("asn1/bad-children", who + ": expected an array of TLV buffers");
1162
1216
  var n = arr.length, out = [];
1163
- for (var i = 0; i < n; i++) out[i] = _asBuffer(arr[i], who);
1217
+ /** @internal Each checked child is APPENDED by defining its index. Assigning at the index is a
1218
+ * store, and a store walks the prototype chain for a setter, so an accessor inherited at an array
1219
+ * index took the child and left the list reading back its getter's value instead. This list is
1220
+ * every constructed element's children: measured, a SEQUENCE wrapping a 300-byte OCTET STRING
1221
+ * emitted as `3000`, an empty SEQUENCE, with the checked child discarded. */
1222
+ for (var i = 0; i < n; i++) intrinsic.append(out, _asBuffer(arr[i], who));
1164
1223
  return out;
1165
1224
  }
1166
1225