@blamejs/pki 0.8.59 → 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.
- package/CHANGELOG.md +33 -0
- package/lib/acme.js +15 -7
- package/lib/alt-sig.js +3 -1
- package/lib/asn1-der.js +82 -34
- package/lib/attrcert-sign.js +19 -16
- package/lib/byte-writer.js +63 -13
- package/lib/cbor-det.js +9 -3
- package/lib/cmc-build.js +12 -9
- package/lib/cmp-build.js +35 -33
- package/lib/cmp-session.js +19 -9
- package/lib/cms-decrypt.js +22 -8
- package/lib/cms-encrypt.js +23 -17
- package/lib/cms-sign.js +65 -43
- package/lib/cms-verify.js +8 -4
- package/lib/crl-sign.js +24 -17
- package/lib/csr-sign.js +5 -2
- package/lib/ct.js +27 -16
- package/lib/est.js +12 -9
- package/lib/guard-bytes.js +5 -1
- package/lib/guard-der.js +6 -1
- package/lib/guard-intrinsic.js +12 -4
- package/lib/guard-list.js +64 -0
- package/lib/guard-name.js +1 -1
- package/lib/hpke.js +10 -3
- package/lib/http-transport.js +12 -3
- package/lib/inspect.js +10 -2
- package/lib/jose.js +3 -1
- package/lib/lint.js +236 -152
- package/lib/mime.js +9 -5
- package/lib/ocsp.js +25 -22
- package/lib/path-validate.js +43 -13
- package/lib/pbes2.js +6 -2
- package/lib/pkcs11-uri.js +3 -1
- package/lib/pkcs12-build.js +14 -11
- package/lib/pki-build.js +14 -10
- package/lib/possession.js +3 -1
- package/lib/rc2.js +6 -2
- package/lib/revocation-fetch.js +78 -19
- package/lib/scep.js +14 -6
- package/lib/schema-attrcert.js +4 -2
- package/lib/schema-c509.js +94 -78
- package/lib/schema-cmc.js +5 -2
- package/lib/schema-cmp.js +6 -3
- package/lib/schema-cms.js +7 -4
- package/lib/schema-crl.js +1 -1
- package/lib/schema-crmf.js +1 -1
- package/lib/schema-csr.js +3 -3
- package/lib/schema-engine.js +36 -18
- package/lib/schema-pkcs1.js +7 -3
- package/lib/schema-pkcs12.js +9 -6
- package/lib/schema-pkix.js +19 -3
- package/lib/schema-sec1.js +4 -1
- package/lib/schema-tsp.js +4 -1
- package/lib/shbs.js +7 -3
- package/lib/smime.js +18 -14
- package/lib/tlog.js +17 -11
- package/lib/tuf.js +3 -1
- package/lib/validator-keydesc.js +4 -1
- package/lib/validator-tls.js +7 -2
- package/lib/webcrypto.js +9 -3
- package/lib/x509-sign.js +20 -15
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,39 @@ 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
|
+
|
|
7
40
|
## v0.8.59 — 2026-10-07
|
|
8
41
|
|
|
9
42
|
Render an object identifier, match a header name and read a media type through operations a replacement cannot answer.
|
package/lib/acme.js
CHANGED
|
@@ -90,7 +90,11 @@ var _lower = intrinsic.toLowerCase;
|
|
|
90
90
|
var _StringI = intrinsic.String;
|
|
91
91
|
var _acmeTrim = intrinsic.uncurry(String.prototype.trim);
|
|
92
92
|
var _stringify = intrinsic.stringify;
|
|
93
|
-
|
|
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;
|
|
94
98
|
var _pop = intrinsic.pop;
|
|
95
99
|
var _shift = intrinsic.shift;
|
|
96
100
|
var _join = intrinsic.join;
|
|
@@ -353,7 +357,7 @@ var SPEC_NAMES = intrinsic.create(null);
|
|
|
353
357
|
var kinds = _objectKeys(SPECS);
|
|
354
358
|
for (var i = 0; i < kinds.length; i++) {
|
|
355
359
|
var fields = SPECS[kinds[i]], names = [];
|
|
356
|
-
for (var j = 0; j < fields.length; j++) names
|
|
360
|
+
for (var j = 0; j < fields.length; j++) _push(names, fields[j].name);
|
|
357
361
|
SPEC_NAMES[kinds[i]] = names;
|
|
358
362
|
}
|
|
359
363
|
})();
|
|
@@ -752,14 +756,18 @@ async function verifyTlsAlpn01(certDer, token, accountJwk, identifier) {
|
|
|
752
756
|
if (!_isString(idType)) throw E("acme/bad-input", "an identifier { type, value } is required");
|
|
753
757
|
var cert = x509.parse(certDer);
|
|
754
758
|
var exts = cert.extensions || [];
|
|
755
|
-
|
|
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; });
|
|
756
764
|
if (!acmeExt) throw E("acme/bad-tlsalpn", "the validation certificate is missing the acmeIdentifier extension (RFC 8737 sec. 3)");
|
|
757
765
|
if (!acmeExt.critical) throw E("acme/bad-tlsalpn", "the acmeIdentifier extension must be critical (RFC 8737 sec. 3)");
|
|
758
766
|
var auth = _readAcmeIdentifier(acmeExt.value);
|
|
759
767
|
var ka = await keyAuthorization(token, accountJwk);
|
|
760
768
|
var expected = await _sha256(Buffer.from(ka, "ascii"));
|
|
761
769
|
if (!auth.equals(expected)) throw E("acme/bad-tlsalpn", "the acmeIdentifier digest does not match the key authorization");
|
|
762
|
-
var sanExt =
|
|
770
|
+
var sanExt = guard.list.firstMatch(exts, function (e) { return e.oid === OID_SAN; });
|
|
763
771
|
if (!sanExt) throw E("acme/bad-tlsalpn", "the validation certificate is missing the SubjectAltName");
|
|
764
772
|
var san = _extDecoders.byOid[OID_SAN](sanExt.value);
|
|
765
773
|
if (!san.names || san.names.length !== 1) throw E("acme/bad-tlsalpn", "the SubjectAltName must carry EXACTLY ONE entry (RFC 8737 sec. 3)");
|
|
@@ -1383,7 +1391,7 @@ async function keyChange(o) {
|
|
|
1383
1391
|
function ariCertId(certDer) {
|
|
1384
1392
|
if (!guard.bytes.isByteSource(certDer)) throw E("acme/bad-input", "ariCertId requires a DER certificate BufferSource");
|
|
1385
1393
|
var cert = x509.parse(certDer);
|
|
1386
|
-
var akiExt = (cert.extensions || []
|
|
1394
|
+
var akiExt = guard.list.firstMatch(cert.extensions || [], function (e) { return e.oid === OID_AKI; });
|
|
1387
1395
|
if (!akiExt) throw E("acme/bad-certid", "the certificate has no authorityKeyIdentifier extension (RFC 9773 sec. 4.1)");
|
|
1388
1396
|
var aki = _extDecoders.byOid[OID_AKI](akiExt.value);
|
|
1389
1397
|
if (!aki || !Buffer.isBuffer(aki.keyIdentifier)) throw E("acme/bad-certid", "the authorityKeyIdentifier has no keyIdentifier field (RFC 9773 sec. 4.1)");
|
|
@@ -1835,7 +1843,7 @@ function _parseLinkRelation(headers, base, relName, opts) {
|
|
|
1835
1843
|
if (seen[key]) continue;
|
|
1836
1844
|
seen[key] = true;
|
|
1837
1845
|
distinctCount += 1;
|
|
1838
|
-
if (key !== baseKey) out
|
|
1846
|
+
if (key !== baseKey) _push(out,resolved);
|
|
1839
1847
|
}
|
|
1840
1848
|
}
|
|
1841
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)");
|
|
@@ -1933,7 +1941,7 @@ function _resignKeysOf(raw) {
|
|
|
1933
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");
|
|
1934
1942
|
var a = e.alg, k = e.key;
|
|
1935
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");
|
|
1936
|
-
out
|
|
1944
|
+
_push(out, intrinsic.freeze({ alg: a, key: k }));
|
|
1937
1945
|
}
|
|
1938
1946
|
return intrinsic.freeze(out);
|
|
1939
1947
|
}
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
373
|
+
intrinsic.append(segments, children[s].content);
|
|
370
374
|
}
|
|
371
|
-
node.content =
|
|
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
|
-
|
|
488
|
+
intrinsic.append(segments, seg.content);
|
|
485
489
|
}
|
|
486
|
-
return
|
|
490
|
+
return intrinsic.bufferConcat(segments);
|
|
487
491
|
}
|
|
488
492
|
_expectPrimitive(node, "readOctetStringImplicit");
|
|
489
493
|
return node.content;
|
|
@@ -766,12 +770,22 @@ function encodeLength(n) {
|
|
|
766
770
|
if (typeof n !== "number" || !isFinite(n) || n < 0 || Math.floor(n) !== n) {
|
|
767
771
|
throw new Asn1Error("asn1/bad-length", "length must be a non-negative integer");
|
|
768
772
|
}
|
|
769
|
-
if (n < 0x80) return
|
|
770
|
-
|
|
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;
|
|
771
786
|
var v = n;
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
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;
|
|
775
789
|
}
|
|
776
790
|
|
|
777
791
|
/** @internal The identifier comes from the one function that composes one. Writing the octet here
|
|
@@ -842,12 +856,24 @@ function encodeIdentifier(classBits, constructed, tagNumber) {
|
|
|
842
856
|
") and is not assigned to any type, so no encoding of it is a DER element");
|
|
843
857
|
}
|
|
844
858
|
var lead = cls | (constructed ? CONSTRUCTED_BIT : 0);
|
|
845
|
-
if (tagNumber < 0x1f) return
|
|
846
|
-
|
|
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;
|
|
847
871
|
var v = tagNumber;
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
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;
|
|
851
877
|
}
|
|
852
878
|
|
|
853
879
|
/**
|
|
@@ -944,19 +970,29 @@ function intToDer(v) {
|
|
|
944
970
|
v = BigInt(v);
|
|
945
971
|
}
|
|
946
972
|
if (typeof v !== "bigint") throw new Asn1Error("asn1/bad-integer", "integer must be number or BigInt");
|
|
947
|
-
if (v === 0n) return
|
|
948
|
-
|
|
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. */
|
|
949
981
|
if (v > 0n) {
|
|
950
|
-
var
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
var
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
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;
|
|
958
989
|
}
|
|
959
|
-
|
|
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;
|
|
960
996
|
}
|
|
961
997
|
|
|
962
998
|
function encodeOidContent(dotted) {
|
|
@@ -969,15 +1005,22 @@ function encodeOidContent(dotted) {
|
|
|
969
1005
|
var subids = intrinsic.concat([a1 * 40n + a2], arcs.slice(2));
|
|
970
1006
|
var out = [];
|
|
971
1007
|
for (var s = 0; s < subids.length; s++) {
|
|
972
|
-
var body = [];
|
|
973
1008
|
var v = subids[s];
|
|
974
|
-
|
|
975
|
-
|
|
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) {
|
|
976
1017
|
throw new OidError("oid/subidentifier-too-large",
|
|
977
1018
|
"OID sub-identifier exceeds " + constants.LIMITS.OID_MAX_SUBIDENTIFIER_BYTES + " octets");
|
|
978
1019
|
}
|
|
979
|
-
for (var k =
|
|
980
|
-
|
|
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
|
+
}
|
|
981
1024
|
}
|
|
982
1025
|
return Buffer.from(out);
|
|
983
1026
|
}
|
|
@@ -1171,7 +1214,12 @@ function _intContent(v, who) {
|
|
|
1171
1214
|
function _asBufferArray(arr, who) {
|
|
1172
1215
|
if (!_isArray(arr)) throw new Asn1Error("asn1/bad-children", who + ": expected an array of TLV buffers");
|
|
1173
1216
|
var n = arr.length, out = [];
|
|
1174
|
-
|
|
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));
|
|
1175
1223
|
return out;
|
|
1176
1224
|
}
|
|
1177
1225
|
|
package/lib/attrcert-sign.js
CHANGED
|
@@ -24,6 +24,9 @@ var x509 = require("./schema-x509");
|
|
|
24
24
|
var signScheme = require("./sign-scheme");
|
|
25
25
|
var guard = require("./guard-all");
|
|
26
26
|
var intrinsic = require("./guard-intrinsic");
|
|
27
|
+
/** @internal The append every list here is built with: `push` stores at the index and a store
|
|
28
|
+
* consults a setter inherited there, and these lists become the bytes that get signed. */
|
|
29
|
+
var _push = intrinsic.append;
|
|
27
30
|
/** @internal The promise a signature verdict is awaited through, built from the constructor captured at
|
|
28
31
|
* load. `Promise.resolve` builds from its RECEIVER, so reading the global binding at the call hands the
|
|
29
32
|
* construction to whatever that binding holds: one settling without awaiting its argument drops the
|
|
@@ -195,7 +198,7 @@ function _issuerSerialContent(is) {
|
|
|
195
198
|
var parts = [_b.encodeGeneralNames(_gnList(is.issuer)), _b.serialInteger(is.serial)];
|
|
196
199
|
if (is.issuerUID != null) {
|
|
197
200
|
if (!Buffer.isBuffer(is.issuerUID)) throw _err("attrcert/bad-input", "baseCertificateID.issuerUID must be a Buffer");
|
|
198
|
-
parts
|
|
201
|
+
_push(parts,b.bitString(is.issuerUID, 0));
|
|
199
202
|
}
|
|
200
203
|
return Buffer.concat(parts);
|
|
201
204
|
}
|
|
@@ -251,8 +254,8 @@ function _encodeRole(role) {
|
|
|
251
254
|
_assertFields(role, KNOWN_ROLE_KEYS, "role");
|
|
252
255
|
if (role.roleName == null) throw _err("attrcert/bad-input", "role must be an object with a roleName GeneralName");
|
|
253
256
|
var parts = [];
|
|
254
|
-
if (role.roleAuthority != null) parts
|
|
255
|
-
parts
|
|
257
|
+
if (role.roleAuthority != null) _push(parts,_b.encodeGeneralNames(_gnList(role.roleAuthority), 0));
|
|
258
|
+
_push(parts,b.explicit(1, _b.encodeGeneralName(role.roleName)));
|
|
256
259
|
return b.sequence(parts);
|
|
257
260
|
}
|
|
258
261
|
function _encodeClearance(cl) {
|
|
@@ -269,11 +272,11 @@ function _encodeClearance(cl) {
|
|
|
269
272
|
return pos;
|
|
270
273
|
});
|
|
271
274
|
var isDefault = positions.length === 1 && positions[0] === CLASSLIST_BIT.unclassified;
|
|
272
|
-
if (!isDefault) parts
|
|
275
|
+
if (!isDefault) _push(parts,b.namedBitString(positions));
|
|
273
276
|
}
|
|
274
277
|
if (cl.securityCategories != null) {
|
|
275
278
|
if (!Array.isArray(cl.securityCategories) || !cl.securityCategories.length) throw _err("attrcert/bad-input", "clearance.securityCategories must be a non-empty array");
|
|
276
|
-
parts
|
|
279
|
+
_push(parts,b.set(guard.list.copyMap(cl.securityCategories, _encodeSecurityCategory)));
|
|
277
280
|
}
|
|
278
281
|
return b.sequence(parts);
|
|
279
282
|
}
|
|
@@ -291,8 +294,8 @@ function _encodeIetfAttrSyntax(ia) {
|
|
|
291
294
|
_assertFields(ia, KNOWN_IETF_ATTR_KEYS, "IetfAttrSyntax");
|
|
292
295
|
if (!Array.isArray(ia.values) || !ia.values.length) throw _err("attrcert/bad-input", "an IetfAttrSyntax attribute must be an object with a non-empty values array");
|
|
293
296
|
var parts = [];
|
|
294
|
-
if (ia.policyAuthority != null) parts
|
|
295
|
-
parts
|
|
297
|
+
if (ia.policyAuthority != null) _push(parts,_b.encodeGeneralNames(_gnList(ia.policyAuthority), 0));
|
|
298
|
+
_push(parts,b.sequence(guard.list.copyMap(ia.values, function (v) {
|
|
296
299
|
if (v == null || typeof v !== "object") throw _err("attrcert/bad-input", "each IetfAttrSyntax value must be { octets }, { oid }, or { string }");
|
|
297
300
|
_assertFields(v, KNOWN_IETF_ATTR_VALUE_KEYS, "IetfAttrSyntax value");
|
|
298
301
|
if (v.octets != null) { if (!Buffer.isBuffer(v.octets)) throw _err("attrcert/bad-input", "IetfAttrSyntax octets must be a Buffer"); return b.octetString(v.octets); }
|
|
@@ -310,7 +313,7 @@ function _encodeSvceAuthInfo(sai, authInfoAllowed, label) {
|
|
|
310
313
|
if (sai.authInfo != null) {
|
|
311
314
|
if (!authInfoAllowed) throw _err("attrcert/bad-input", "accessIdentity must not carry authInfo (RFC 5755 sec. 4.4.2)");
|
|
312
315
|
if (!Buffer.isBuffer(sai.authInfo)) throw _err("attrcert/bad-input", label + " authInfo must be a Buffer");
|
|
313
|
-
parts
|
|
316
|
+
_push(parts,b.octetString(sai.authInfo));
|
|
314
317
|
}
|
|
315
318
|
return b.sequence(parts);
|
|
316
319
|
}
|
|
@@ -349,11 +352,11 @@ function _encodeAAControls(aac) {
|
|
|
349
352
|
var parts = [];
|
|
350
353
|
if (aac.pathLenConstraint != null) {
|
|
351
354
|
if (typeof aac.pathLenConstraint !== "number" || !Number.isInteger(aac.pathLenConstraint) || aac.pathLenConstraint < 0 || aac.pathLenConstraint > 0x7fffffff) throw _err("attrcert/bad-input", "aaControls.pathLenConstraint must be an integer in 0..2147483647 (RFC 5755 sec. 7.4)");
|
|
352
|
-
parts
|
|
355
|
+
_push(parts,b.integer(BigInt(aac.pathLenConstraint)));
|
|
353
356
|
}
|
|
354
|
-
if (aac.permittedAttrs != null) parts
|
|
355
|
-
if (aac.excludedAttrs != null) parts
|
|
356
|
-
if (aac.permitUnSpecified === false) parts
|
|
357
|
+
if (aac.permittedAttrs != null) _push(parts,_attrSpec(aac.permittedAttrs, 0, "permittedAttrs"));
|
|
358
|
+
if (aac.excludedAttrs != null) _push(parts,_attrSpec(aac.excludedAttrs, 1, "excludedAttrs"));
|
|
359
|
+
if (aac.permitUnSpecified === false) _push(parts,b.boolean(false));
|
|
357
360
|
else if (aac.permitUnSpecified != null && aac.permitUnSpecified !== true) throw _err("attrcert/bad-input", "aaControls.permitUnSpecified must be a boolean");
|
|
358
361
|
return b.sequence(parts);
|
|
359
362
|
}
|
|
@@ -388,7 +391,7 @@ function _buildAttributes(attrSpec) {
|
|
|
388
391
|
if (seen[type]) throw _err("attrcert/duplicate-attribute", "duplicate " + (oid.name(type) || type) + " attribute (RFC 5755 sec. 4.2.7)");
|
|
389
392
|
seen[type] = true;
|
|
390
393
|
_assertAttributeProfile(type, attrcert.validateAttributeValue(type, valueTlv));
|
|
391
|
-
attrs
|
|
394
|
+
_push(attrs,b.sequence([b.oid(type), b.set([valueTlv])]));
|
|
392
395
|
}
|
|
393
396
|
if (Array.isArray(attrSpec)) {
|
|
394
397
|
/** @internal Through the captured walk, never the caller's own `forEach`: these attributes are the
|
|
@@ -409,7 +412,7 @@ function _buildAttributes(attrSpec) {
|
|
|
409
412
|
catch (err) { if (err instanceof AttrCertError) throw err; throw _err("attrcert/bad-input", "pre-encoded " + (oid.name(at) || at) + " attribute value is malformed", err); }
|
|
410
413
|
_assertAttributeProfile(at, decoded);
|
|
411
414
|
});
|
|
412
|
-
attrs
|
|
415
|
+
_push(attrs,b.raw(der));
|
|
413
416
|
});
|
|
414
417
|
if (!attrs.length) throw _err("attrcert/bad-attributes", "attributes must carry at least one Attribute (RFC 5755 sec. 4.2.7)");
|
|
415
418
|
return b.sequence(attrs);
|
|
@@ -442,7 +445,7 @@ function _emitNamedExtension(out, seen, k, val, ctx) {
|
|
|
442
445
|
seen[eOid] = true;
|
|
443
446
|
var valueDer = _extensionValue(k, val, ctx);
|
|
444
447
|
_assertExtensionProfile(eOid, _decodeExtensionValue(eOid, valueDer), ctx);
|
|
445
|
-
out
|
|
448
|
+
_push(out,_b.ext(eOid, meta.critical, valueDer));
|
|
446
449
|
}
|
|
447
450
|
function _buildExtensions(extSpec, ctx) {
|
|
448
451
|
if (extSpec == null) extSpec = {};
|
|
@@ -593,7 +596,7 @@ function _sign(spec, issuer, opts) {
|
|
|
593
596
|
b.integer(1n), holderDer, issuerDer, scheme.sigAlgId,
|
|
594
597
|
_b.serialInteger(spec.serialNumber), validityDer, attributesDer,
|
|
595
598
|
];
|
|
596
|
-
if (extsDer) acinfoChildren
|
|
599
|
+
if (extsDer) _push(acinfoChildren,extsDer);
|
|
597
600
|
var acinfoDer = b.sequence(acinfoChildren);
|
|
598
601
|
|
|
599
602
|
return signScheme.signOverTbs(scheme, issuer.key, acinfoDer, _signE).then(function (sig) {
|
package/lib/byte-writer.js
CHANGED
|
@@ -18,20 +18,58 @@
|
|
|
18
18
|
* in a format module.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
+
var intrinsic = require("./guard-intrinsic");
|
|
22
|
+
/** @internal Every operation this writer decides a field's width, range or bytes with is bound at
|
|
23
|
+
* load. Each one participates in what gets emitted: the integer tests decide whether a value fits
|
|
24
|
+
* the field, the allocation decides how wide the field is, and the concatenation decides the byte
|
|
25
|
+
* string a caller signs or sends. */
|
|
26
|
+
var _isInteger = intrinsic.isInteger;
|
|
27
|
+
var _isSafeInteger = intrinsic.isSafeInteger;
|
|
28
|
+
var _bufferAlloc = intrinsic.bufferAlloc;
|
|
29
|
+
var _isBuffer = intrinsic.isBuffer;
|
|
30
|
+
var _bufferConcat = intrinsic.bufferConcat;
|
|
31
|
+
var _BigInt = intrinsic.BigInt;
|
|
32
|
+
var _taSet = intrinsic.typedArraySet;
|
|
33
|
+
/** @internal The two writes that place an integer into its field, bound at load. A bound's own
|
|
34
|
+
* `valueOf` runs inside the comparison the length is checked by, which is a window to replace a
|
|
35
|
+
* method the write that follows would otherwise read off the prototype: measured, a vector declared
|
|
36
|
+
* a length of 0 over a payload of two bytes that way. */
|
|
37
|
+
var _writeUIntBE = intrinsic.uncurry(Buffer.prototype.writeUIntBE);
|
|
38
|
+
var _writeBigUInt64BE = intrinsic.uncurry(Buffer.prototype.writeBigUInt64BE);
|
|
39
|
+
|
|
40
|
+
/** @internal A real copy of the bytes, in this writer's own allocation. A slice of a view shares the
|
|
41
|
+
* backing buffer, so a buffer that can be resized takes the slice out of bounds with it, and the
|
|
42
|
+
* copy has to be taken before anything else in the call can run code. */
|
|
43
|
+
function _copyOf(view) {
|
|
44
|
+
var n = intrinsic.sizeOf(view);
|
|
45
|
+
var out = _bufferAlloc(n);
|
|
46
|
+
if (n !== 0) _taSet(out, view, 0);
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
|
|
21
50
|
function ByteWriter(E, defaultCode) {
|
|
22
51
|
this.parts = [];
|
|
23
52
|
this.len = 0;
|
|
24
53
|
this.E = E;
|
|
25
54
|
this.defaultCode = defaultCode || "byte-writer/bad-value";
|
|
26
55
|
}
|
|
27
|
-
|
|
56
|
+
/** @internal The piece is APPENDED by defining its index, not stored by `push`. A store walks the
|
|
57
|
+
* prototype chain for a setter, so an accessor installed at an array index receives each piece as it
|
|
58
|
+
* is added and can define something else in its place, and these pieces are the bytes this writer
|
|
59
|
+
* emits. The length is accumulated from the buffer's own byte length rather than a `length`
|
|
60
|
+
* property, so a view that overstates itself cannot make the declared length disagree with the
|
|
61
|
+
* bytes. */
|
|
62
|
+
ByteWriter.prototype._push = function (buf) {
|
|
63
|
+
intrinsic.append(this.parts, buf);
|
|
64
|
+
this.len += intrinsic.sizeOf(buf);
|
|
65
|
+
};
|
|
28
66
|
ByteWriter.prototype._uint = function (v, width, code) {
|
|
29
|
-
if (!
|
|
67
|
+
if (!_isInteger(width) || width < 1 || width > 4) throw new this.E(code || this.defaultCode, "an integer width must be 1..4, got " + width);
|
|
30
68
|
var max = width === 4 ? 0xffffffff : (1 << (8 * width)) - 1;
|
|
31
|
-
if (typeof v !== "number" || !
|
|
69
|
+
if (typeof v !== "number" || !_isSafeInteger(v) || v < 0 || v > max) {
|
|
32
70
|
throw new this.E(code || this.defaultCode, "a uint" + (8 * width) + " must be an integer in 0.." + max + ", got " + v);
|
|
33
71
|
}
|
|
34
|
-
var b =
|
|
72
|
+
var b = _bufferAlloc(width); _writeUIntBE(b, v, 0, width); this._push(b);
|
|
35
73
|
};
|
|
36
74
|
ByteWriter.prototype.u8 = function (v, code) { this._uint(v, 1, code); return this; };
|
|
37
75
|
ByteWriter.prototype.u16 = function (v, code) { this._uint(v, 2, code); return this; };
|
|
@@ -40,25 +78,37 @@ ByteWriter.prototype.u32 = function (v, code) { this._uint(v, 4, code); return t
|
|
|
40
78
|
ByteWriter.prototype.u64 = function (v, code) {
|
|
41
79
|
var big;
|
|
42
80
|
if (typeof v === "bigint") big = v;
|
|
43
|
-
else if (typeof v === "number" &&
|
|
81
|
+
else if (typeof v === "number" && _isSafeInteger(v) && v >= 0) big = _BigInt(v);
|
|
44
82
|
else throw new this.E(code || this.defaultCode, "a uint64 must be a non-negative integer or BigInt, got " + v);
|
|
45
83
|
if (big < 0n || big > 0xffffffffffffffffn) throw new this.E(code || this.defaultCode, "a uint64 must be in 0..2^64-1");
|
|
46
|
-
var b =
|
|
84
|
+
var b = _bufferAlloc(8); _writeBigUInt64BE(b, big); this._push(b);
|
|
47
85
|
return this;
|
|
48
86
|
};
|
|
49
87
|
ByteWriter.prototype.bytes = function (buf, code) {
|
|
50
|
-
if (!
|
|
88
|
+
if (!_isBuffer(buf)) throw new this.E(code || this.defaultCode, "bytes() requires a Buffer");
|
|
51
89
|
this._push(buf); return this;
|
|
52
90
|
};
|
|
53
91
|
ByteWriter.prototype.vector = function (lenWidth, min, max, body, code) {
|
|
54
|
-
if (!
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
92
|
+
if (!_isBuffer(body)) throw new this.E(code || this.defaultCode, "vector() body must be a Buffer");
|
|
93
|
+
/** @internal The body is COPIED before it is measured, and the copy is what gets written. The
|
|
94
|
+
* bounds, the length prefix and the bytes then all describe one byte string that nothing else
|
|
95
|
+
* holds. Measuring the caller's view instead left a window: a resizable backing buffer shrunk
|
|
96
|
+
* between the measurement and the write (a `min` whose `valueOf` resizes it is enough) and the
|
|
97
|
+
* vector declared two bytes over a payload of one. The bounds are converted to numbers up front
|
|
98
|
+
* for the same reason, so a bound cannot run code in the middle of the comparison it is part of. */
|
|
99
|
+
var bytes = _copyOf(body);
|
|
100
|
+
var bodyLen = intrinsic.sizeOf(bytes);
|
|
101
|
+
var lo = intrinsic.Number(min);
|
|
102
|
+
if (!_isSafeInteger(lo) || lo < 0) throw new this.E(code || this.defaultCode, "a vector minimum must be a non-negative integer, got " + lo);
|
|
103
|
+
var hi = max == null ? null : intrinsic.Number(max);
|
|
104
|
+
if (hi !== null && (!_isSafeInteger(hi) || hi < lo)) throw new this.E(code || this.defaultCode, "a vector maximum must be an integer not below the minimum, got " + hi);
|
|
105
|
+
if (bodyLen < lo) throw new this.E(code || this.defaultCode, "vector body " + bodyLen + " below minimum " + lo);
|
|
106
|
+
if (hi !== null && bodyLen > hi) throw new this.E(code || this.defaultCode, "vector body " + bodyLen + " above maximum " + hi);
|
|
107
|
+
this._uint(bodyLen, lenWidth, code);
|
|
108
|
+
this._push(bytes);
|
|
59
109
|
return this;
|
|
60
110
|
};
|
|
61
111
|
ByteWriter.prototype.length = function () { return this.len; };
|
|
62
|
-
ByteWriter.prototype.build = function () { return
|
|
112
|
+
ByteWriter.prototype.build = function () { return _bufferConcat(this.parts, this.len); };
|
|
63
113
|
|
|
64
114
|
module.exports = ByteWriter;
|
package/lib/cbor-det.js
CHANGED
|
@@ -40,6 +40,12 @@ var constants = require("./constants");
|
|
|
40
40
|
var frameworkError = require("./framework-error");
|
|
41
41
|
var asn1 = require("./asn1-der");
|
|
42
42
|
var guard = require("./guard-all");
|
|
43
|
+
/** @internal The append every list in this module is built with. `push` STORES at the index, and a
|
|
44
|
+
* store walks the prototype chain for a setter, so an accessor inherited at an array index receives
|
|
45
|
+
* each element as it is appended and the index then reads back whatever its getter answers. The
|
|
46
|
+
* decoded children below are what every C509 field is read out of, so a replaced element there is a
|
|
47
|
+
* field this codec never decoded. */
|
|
48
|
+
var _push = guard.list.append;
|
|
43
49
|
|
|
44
50
|
var CborError = frameworkError.CborError;
|
|
45
51
|
|
|
@@ -141,7 +147,7 @@ function _decodeItem(buf, start, limit, depth, maxD, rules, state) {
|
|
|
141
147
|
var cp = contentStart;
|
|
142
148
|
for (var i = 0n; i < argument; i++) {
|
|
143
149
|
var el = _decodeItem(buf, cp, limit, depth + 1, maxD, rules, state);
|
|
144
|
-
children
|
|
150
|
+
_push(children, el.node);
|
|
145
151
|
cp = el.end;
|
|
146
152
|
}
|
|
147
153
|
end = cp;
|
|
@@ -158,7 +164,7 @@ function _decodeItem(buf, start, limit, depth, maxD, rules, state) {
|
|
|
158
164
|
if (cmp > 0) throw new CborError("cbor/unsorted-map-keys", "map keys are not in bytewise-ascending order");
|
|
159
165
|
if (cmp === 0) throw new CborError("cbor/duplicate-map-key", "duplicate map key");
|
|
160
166
|
}
|
|
161
|
-
children
|
|
167
|
+
_push(children, [k.node, v.node]);
|
|
162
168
|
}
|
|
163
169
|
end = mp;
|
|
164
170
|
} else if (mt === 6) {
|
|
@@ -682,7 +688,7 @@ var build = Object.assign(Object.create(null), {
|
|
|
682
688
|
enc = guard.list.sortedCopy(enc, function (a, b) { return guard.bytes.compare(a[0], b[0]); });
|
|
683
689
|
for (var i = 1; i < enc.length; i++) { if (guard.bytes.compare(enc[i - 1][0], enc[i][0]) === 0) throw new CborError("cbor/duplicate-map-key", "build.map: duplicate map key"); }
|
|
684
690
|
var flat = [_head(5, pairs.length, "build.map")];
|
|
685
|
-
enc.forEach(function (p) { flat
|
|
691
|
+
enc.forEach(function (p) { _push(flat, p[0]); _push(flat, p[1]); });
|
|
686
692
|
return _assertDecodes(Buffer.concat(flat), "build.map");
|
|
687
693
|
},
|
|
688
694
|
tag: function (tagNum, inner) { return _assertDecodes(Buffer.concat([_head(6, tagNum, "build.tag"), _cbItem(inner, "build.tag content")]), "build.tag"); },
|