@blamejs/pki 0.5.8 → 0.5.10

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.
@@ -0,0 +1,152 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) blamejs contributors
3
+ "use strict";
4
+ //
5
+ // @internal -- no operator-facing namespace. The documented surface is the consumers
6
+ // whose encoding integrity composes this guard (pki.schema.c509 reconstruct sites,
7
+ // pki.x509.sign and its sibling builders on any subjectAltName otherName value).
8
+ //
9
+ // guard-der: the single choke point for "these raw bytes are exactly one strictly-valid
10
+ // DER element", for every site that splices caller-supplied ANY bytes verbatim into
11
+ // something it then signs or reconstructs.
12
+ //
13
+ // The bug class is framing mistaken for validation. asn1.decode checks TLV framing and
14
+ // rejects trailing data, and stopping there feels sufficient. It is not: framing accepts
15
+ // a BOOLEAN whose content octet is 0x01 where DER requires 0xFF, a NumericString holding
16
+ // "@", and a SET whose members sit in no canonical order. None of those is DER. Spliced
17
+ // into a certificate and signed, the issuer emits a structure that strict relying parties
18
+ // reject, under a real signature, while the producing verb reported success (CWE-20).
19
+ //
20
+ // The rule is validate-or-refuse. A universal primitive runs through its strict content
21
+ // reader, and a universal type with no reader here is REFUSED, so the table is exhaustive
22
+ // by refusal and adding a type is a deliberate act. A non-universal element (a
23
+ // legitimately context- or application-tagged ANY) passes on its framing because no
24
+ // content rule is knowable for it, and its constructed children are still walked.
25
+ //
26
+ // Every entry point takes the CALLER's error factory and code, so a c509 splice reports in
27
+ // the c509 domain and a certificate builder in the x509 domain.
28
+ // asn1-der composes this guard family, so requiring the codec at module scope would read its
29
+ // exports mid-initialization and see an empty object. The require and the table it builds are
30
+ // therefore both deferred to first use -- the documented circular-load exception to top-of-file
31
+ // requires. Guards sit BELOW the codec in the dependency order; this is the one place that
32
+ // order is inverted, and it is inverted lazily.
33
+ var asn1 = null;
34
+ function _asn1() { if (asn1 === null) asn1 = require("./asn1-der"); return asn1; }
35
+
36
+ // The strict content reader per universal primitive tag. A tag absent from this table has no
37
+ // validator here and is refused by `element` below -- that refusal is the point.
38
+ var VALUE_READERS = null;
39
+ function _readers() {
40
+ if (VALUE_READERS !== null) return VALUE_READERS;
41
+ var asn1 = _asn1();
42
+ var m = {}, R = asn1.read, T = asn1.TAGS;
43
+ // ENUMERATED shares INTEGER's content rules and is NOT constrained to non-negative
44
+ // values. X.680 (02/2021) sec. 20.2 requires each NamedNumber's SignedNumber to be
45
+ // distinct, and sec. 19 defines SignedNumber ::= number | "-" number; the non-negativity
46
+ // in sec. 20.3 governs only an EnumerationItem written as a bare identifier, which is
47
+ // auto-assigned. `ENUMERATED { lowPriority(-1), normal(0) }` is well-formed, so rejecting
48
+ // a negative here would refuse valid input. This guard validates the ENCODING; which
49
+ // values a particular ENUMERATED admits lives in a type definition an opaque ANY does
50
+ // not carry.
51
+ m[T.BOOLEAN] = R.boolean; m[T.INTEGER] = R.integer; m[T.ENUMERATED] = R.enumerated;
52
+ m[T.BIT_STRING] = R.bitString; m[T.OCTET_STRING] = R.octetString; m[T.NULL] = R.nullValue;
53
+ m[T.OBJECT_IDENTIFIER] = R.oid; m[T.UTC_TIME] = R.time; m[T.GENERALIZED_TIME] = R.time;
54
+ // NumericString reads through its own reader: it is not a DirectoryString type, and routing
55
+ // it through read.string would fold it into the RFC 5280 sec. 7.1 name-comparison identity
56
+ // class (see asn1-der.js).
57
+ m[T.NUMERIC_STRING] = R.numericString;
58
+ [T.UTF8_STRING, T.PRINTABLE_STRING, T.IA5_STRING, T.TELETEX_STRING, T.VISIBLE_STRING,
59
+ T.BMP_STRING, T.UNIVERSAL_STRING].forEach(function (t) { m[t] = R.string; });
60
+ VALUE_READERS = m;
61
+ return VALUE_READERS;
62
+ }
63
+
64
+ // A universal SET's required member order depends on a type the ANY does not carry: X.690
65
+ // sec. 11.6 orders a SET OF by the members' full encodings, while a structured SET is ordered
66
+ // by TAG (X.680 sec. 8.6), and the two differ whenever the constructed bit does (a SEQUENCE
67
+ // member, tag 16, sorts before a PrintableString, tag 19, by tag but after it by octets). A
68
+ // structured SET cannot repeat a tag, so a repeated tag proves SET OF and the octet rule binds;
69
+ // with all-distinct tags either reading is possible, so accept a value that satisfies EITHER
70
+ // (rejecting only what is non-canonical under both readings: sound in both directions, never a
71
+ // guess).
72
+ //
73
+ // KNOWN LIMITATION, and the two consumers do not weigh it identically. The either-reading rule
74
+ // admits a SET OF that is tag-ordered but not octet-ordered, which is not canonical DER. On a
75
+ // RECONSTRUCT path the input already exists and the alternative would refuse a valid structured
76
+ // SET, so accepting is the sound direction. On a SIGNING path the caller composes the bytes and
77
+ // could be held to octet order, so the same permissiveness lets a signer emit a non-canonical
78
+ // SET under a real signature. Tightening it for signers alone would mean a mode flag on a
79
+ // security posture, which this family avoids, and tightening it for both would reject valid
80
+ // structured SETs that c509 round-trips today. The rule is therefore unchanged and the cost is
81
+ // written down rather than discovered later.
82
+ var TAG_CLASS_RANK = { universal: 0, application: 1, context: 2, private: 3 };
83
+ function setOrderOk(kids) {
84
+ var i, dup = false, seen = {};
85
+ for (i = 0; i < kids.length; i++) {
86
+ var key = kids[i].tagClass + ":" + kids[i].tagNumber;
87
+ if (seen[key]) { dup = true; break; }
88
+ seen[key] = true;
89
+ }
90
+ var octetAsc = true, tagAsc = true;
91
+ for (i = 1; i < kids.length; i++) {
92
+ if (Buffer.compare(kids[i - 1].bytes, kids[i].bytes) > 0) octetAsc = false;
93
+ // Tag order ranks by CLASS first (universal < application < context < private, X.680
94
+ // sec. 8.6), then by tag number. Compare the class's NUMBER: the names do not sort in
95
+ // class order.
96
+ var pc = TAG_CLASS_RANK[kids[i - 1].tagClass], cc = TAG_CLASS_RANK[kids[i].tagClass];
97
+ if (pc !== cc ? pc > cc : kids[i - 1].tagNumber > kids[i].tagNumber) tagAsc = false;
98
+ }
99
+ return dup ? octetAsc : (octetAsc || tagAsc);
100
+ }
101
+
102
+ // element(node, E, code, label). Strict-validate an already-decoded DER element at ANY depth.
103
+ // Rejects the reserved EOC tag 0; runs a universal primitive through its strict content reader
104
+ // (or refuses a type with none); recurses into a constructed element's children; holds a
105
+ // universal SET to a canonical order. Only SEQUENCE and SET are accepted as universal
106
+ // CONSTRUCTED types -- an EXTERNAL / EMBEDDED PDV / CHARACTER STRING has mandatory components
107
+ // this gate cannot verify (the degenerate empty form is not a valid encoding of any of them),
108
+ // so it is refused for the same reason an unvalidatable primitive is.
109
+ //
110
+ // @enforced-by guard-shape-reinlined
111
+ // @guard-shape _ANY_VALUE_READERS\[
112
+ function element(node, E, code, label) {
113
+ if (node.tagClass === "universal" && node.tagNumber === 0) {
114
+ throw E(code, label + " must not use the reserved end-of-contents encoding (tag 0)");
115
+ }
116
+ var T = _asn1().TAGS;
117
+ if (node.constructed) {
118
+ if (node.tagClass === "universal" && node.tagNumber !== T.SEQUENCE && node.tagNumber !== T.SET) {
119
+ throw E(code, label + " of universal constructed type " + node.tagNumber + " has no strict DER structure validator here");
120
+ }
121
+ var kids = node.children; // asn1.decode always sets a (possibly empty) children array
122
+ for (var i = 0; i < kids.length; i++) element(kids[i], E, code, label);
123
+ if (node.tagClass === "universal" && node.tagNumber === T.SET && !setOrderOk(kids)) {
124
+ throw E(code, label + " has a SET whose members are in no canonical DER order (X.690 sec. 11.6 / X.680 sec. 8.6)");
125
+ }
126
+ return;
127
+ }
128
+ if (node.tagClass === "universal") {
129
+ var reader = _readers()[node.tagNumber];
130
+ if (!reader) throw E(code, label + " of universal type " + node.tagNumber + " has no strict DER content validator here");
131
+ try { reader(node); }
132
+ catch (e) { throw E(code, label + " is not a valid DER element for its type", e); }
133
+ }
134
+ }
135
+
136
+ // tlv(content, E, code, label) -> content. Raw ANY bytes about to be spliced verbatim must be
137
+ // exactly one non-empty, well-formed AND strictly-valid DER element: framing + no-trailing-data
138
+ // via asn1.decode, then content / structure / SET order via `element`. Returns the bytes so a
139
+ // call site can wrap a splice inline.
140
+ //
141
+ // @enforced-by guard-shape-reinlined
142
+ // @guard-shape must be exactly one well-formed DER element
143
+ function tlv(content, E, code, label) {
144
+ if (!content || content.length === 0) throw E(code, label + " must be a non-empty DER element");
145
+ var node;
146
+ try { node = _asn1().decode(content); }
147
+ catch (e) { throw E(code, label + " must be exactly one well-formed DER element (no trailing data)", e); }
148
+ element(node, E, code, label);
149
+ return content;
150
+ }
151
+
152
+ module.exports = { element: element, tlv: tlv };
@@ -84,9 +84,9 @@ function assertCanonicalOid(str, E, code, label, boundsCode) {
84
84
  // The shape this replaces was hand-written in a dozen callers, and it is worth one home because
85
85
  // getting it wrong fails open in the quietest possible way: an options object whose key is
86
86
  // misspelled silently carries the default, so a caller who asked for a stricter check gets the
87
- // looser behaviour and no error. Two details are easy to lose in a re-inline and are fixed here.
87
+ // looser behavior and no error. Two details are easy to lose in a re-inline and are fixed here.
88
88
  // `known` is consulted with hasOwnProperty and not a truthiness test, so an inherited Object
89
- // member ("constructor", "toString") cannot read as a recognised key; and the walk is over own
89
+ // member ("constructor", "toString") cannot read as a recognized key; and the walk is over own
90
90
  // enumerable keys, so a `__proto__` arriving from JSON is inspected instead of skipped.
91
91
  //
92
92
  // @enforced-by behavioral -- an options-shape walk has no rename-proof code shape; the RED vectors
@@ -99,7 +99,7 @@ function assertCanonicalOid(str, E, code, label, boundsCode) {
99
99
  //
100
100
  // @enforced-by guard-shape-reinlined
101
101
  // The shape requires the throw: an identical walk whose body filters on the same table (copying the
102
- // recognised keys onward instead of rejecting the unrecognised ones) is a different operation with
102
+ // recognized keys onward instead of rejecting the unrecognized ones) is a different operation with
103
103
  // no fail-open risk, and must not be dragged through a guard that only knows how to reject.
104
104
  // @guard-shape Object\.keys\(\w+(?:\.\w+)*\)\.forEach\(function \((\w+)\) \{\s*if \(![\w.]+\[\1\]\) (?:\{\s*)?throw
105
105
  function assertKnownKeys(obj, known, E, code, message) {
@@ -90,7 +90,7 @@ function depthCap(value, key, dflt) {
90
90
  function counter(max, E, code, label) {
91
91
  // The ceiling is an authoring input: an undefined / NaN / fractional max
92
92
  // builds a counter whose `n > max` never fires, leaving a silently dead fanout
93
- // defence. Reject at construction (config-time TypeError).
93
+ // defense. Reject at construction (config-time TypeError).
94
94
  if (!Number.isInteger(max) || max < 0) {
95
95
  throw new TypeError("guard.limits.counter: max must be a non-negative integer");
96
96
  }
@@ -117,7 +117,7 @@ function counter(max, E, code, label) {
117
117
  // over-size RED vectors driving the composing verifiers are the guard.
118
118
  function byteCap(buf, max, E, code, label) {
119
119
  // The ceiling is an authoring input: an undefined / NaN / fractional / negative
120
- // max makes `length > max` never fire, and the size defence is then silently
120
+ // max makes `length > max` never fire, and the size defense is then silently
121
121
  // dead. Reject at config time (TypeError), regardless of the value currency E selects.
122
122
  if (!Number.isInteger(max) || max < 0) {
123
123
  throw new TypeError("guard.limits.byteCap: max must be a non-negative integer");
@@ -44,7 +44,7 @@ var bytes = require("./guard-bytes");
44
44
  // ArrayBuffer. Testing `Buffer.isBuffer(x) || x instanceof Uint8Array` is narrower than what the
45
45
  // verbs document, and the narrowness is invisible in the good case: an ArrayBuffer would fall
46
46
  // through to the object branch and be refused as a rebuilt structure, which reads to the caller as
47
- // "your certificate is malformed" when what happened is that their container was not recognised.
47
+ // "your certificate is malformed" when what happened is that their container was not recognized.
48
48
  // guard.bytes is what finally reads them, and it accepts all four, so the door has to as well.
49
49
  function _isBytes(x) {
50
50
  return Buffer.isBuffer(x) || ArrayBuffer.isView(x) || x instanceof ArrayBuffer;
@@ -105,7 +105,7 @@ function uint64(value, E, code, label) {
105
105
  // take. Accepts a bigint, or a Number that is a safe integer (isSafeInteger, not
106
106
  // isInteger, because a Number above 2^53 has already lost precision by the time it
107
107
  // arrives), so a large identifier MUST come as a bigint and cannot be silently
108
- // rounded to a neighbour. Everything else is a config-time reject.
108
+ // rounded to a neighbor. Everything else is a config-time reject.
109
109
  //
110
110
  // Distinct from int() on purpose: int() BOUNDS a value the wire decoded and narrows
111
111
  // it to Number; this one is unbounded and returns BigInt, because the field has no
@@ -72,7 +72,7 @@ function zeroize(value, ErrorClass, code, label) {
72
72
  // @enforced-by behavioral -- this is a loop over zeroize, which carries the family's only
73
73
  // rename-proof shape (the `.fill(0)` above). It introduces no shape of its own, so a lexical
74
74
  // detector here would anchor on a renameable symbol and go silently green (drift rule sec. 3).
75
- // The behavioural guards are guard-secret.test.js (holes tolerated, every member cleared) and the
75
+ // The behavioral guards are guard-secret.test.js (holes tolerated, every member cleared) and the
76
76
  // CMS vectors that assert the shared secret and KEK are wiped on both the success and failure paths.
77
77
  function zeroizeAll(list, ErrorClass, code, label) {
78
78
  if (!list) return list;
package/lib/guard-text.js CHANGED
@@ -50,7 +50,7 @@ function decode(input, maxBytes, ErrorClass, spec) {
50
50
  if (Buffer.isBuffer(input)) {
51
51
  // Re-view through the byte guard first so a detached backing ArrayBuffer (a
52
52
  // transferred / structuredClone'd Buffer, which reads as zero-length) fails
53
- // closed here -- the same detached-buffer defence the byte boundaries get --
53
+ // closed here -- the same detached-buffer defense the byte boundaries get --
54
54
  // instead of being decoded as an empty string. Then cap, then decode.
55
55
  input = bytes.view(input, ErrorClass, spec.badInput, spec.label);
56
56
  if (input.length > maxBytes) throw new ErrorClass(spec.tooLarge, spec.label + " exceeds the size cap");
package/lib/hpke.js CHANGED
@@ -390,7 +390,7 @@ function _recipPrivate(suite, sk) {
390
390
  // psk / auth / auth-psk. An unknown mode must fail closed, never key-schedule
391
391
  // with an out-of-registry mode byte.
392
392
  // The sender and the recipient read the same option object from OPPOSITE ends, so each direction
393
- // gets its own table. A shared union recognises every name at both ends and so accepts the one that
393
+ // gets its own table. A shared union recognizes every name at both ends and so accepts the one that
394
394
  // cannot do anything there: `senderPublicKey` handed to setupS, or
395
395
  // `senderKey` handed to setupR, is silently ignored, which is the exact silence these tables exist
396
396
  // to remove, in a wider form. An option that means nothing where it was passed is a misunderstanding
@@ -144,7 +144,7 @@ function _validateDigest(paramText, E, code) {
144
144
  ? p.domain.value.replace(/^\s+|\s+$/g, "").split(/\s+/) : null;
145
145
  // charset (RFC 7616 sec. 3.3): the one permitted value is the unquoted token "UTF-8". A quoted charset, or any
146
146
  // other value, is malformed, since answering it would hash the credentials in the wrong encoding, so it is
147
- // rejected (and thus skipped during multi-offer selection in favour of a conforming offer).
147
+ // rejected (and thus skipped during multi-offer selection in favor of a conforming offer).
148
148
  if (p.charset && (p.charset.quoted || String(p.charset.value).toUpperCase() !== "UTF-8")) throw E(code, "the Digest charset must be the unquoted token UTF-8 (RFC 7616 sec. 3.3)");
149
149
  // stale (RFC 7616 sec. 3.3) is the unquoted token "true" or "false". A quoted or otherwise invalid value
150
150
  // (stale=maybe) is malformed and rejected (thus skipped in selection), never parsed as a false flag that
package/lib/jose.js CHANGED
@@ -368,7 +368,7 @@ function assertPublicJwk(jwk) {
368
368
  // `key` NAMES the key this message must be signed under and `profile` selects which header rules
369
369
  // apply -- both narrow what verifies, so a misspelling of either silently widens it back to the
370
370
  // default. That is the shape this check exists for: the caller asked for something stricter and got
371
- // the looser behaviour, with nothing said.
371
+ // the looser behavior, with nothing said.
372
372
  var _VERIFY_KEYS = { key: 1, profile: 1 };
373
373
  async function verify(jws, opts) {
374
374
  opts = opts || {};
package/lib/lint.js CHANGED
@@ -231,7 +231,7 @@ function _subjectCNs(cert) {
231
231
  return out;
232
232
  }
233
233
 
234
- // An extension's criticality is honoured (RFC 5280 4.2: a consumer MUST reject a critical extension
234
+ // An extension's criticality is honored (RFC 5280 4.2: a consumer MUST reject a critical extension
235
235
  // it cannot process) only when this toolkit actually processes its semantics. That authority is
236
236
  // path-validate's PROCESSED_EXTENSIONS (the RFC 5280 sec. 6.1 path-processing set), never the decoder
237
237
  // table: certExtensionDecoders also decodes many extensions purely for display (qcStatements, the MS
package/lib/ocsp.js CHANGED
@@ -530,7 +530,7 @@ function verify(response, opts) {
530
530
  // the closed direction for `revoked`: revocation does not expire the way
531
531
  // non-revocation does, so discarding a signed, current, authorized revoked
532
532
  // verdict because it was replayed would hand a soft-fail caller the certificate
533
- // the responder just refused -- turning the anti-replay defence into the thing
533
+ // the responder just refused -- turning the anti-replay defense into the thing
534
534
  // that accepts a revoked certificate. `nonceMatched: false` still reports that
535
535
  // this response was not bound to this request.
536
536
  if (!matched && verdict.status === "good") {
package/lib/oid.js CHANGED
@@ -22,7 +22,7 @@
22
22
  * attribute types, `2.5.29` for the extensions, `2.16.840.1.101.3.4` for
23
23
  * the NIST algorithms), and each member names only its trailing arc. The
24
24
  * full OID is derived from base + leaf at load, so the arc hierarchy that
25
- * IS the OID namespace is modelled directly instead of re-spelled per
25
+ * IS the OID namespace is modeled directly instead of re-spelled per
26
26
  * entry. It covers the RFC 5280 attribute types and extensions, the
27
27
  * classical signature / public-key / digest algorithms, and the
28
28
  * NIST-assigned post-quantum arcs (ML-DSA, ML-KEM, SLH-DSA). Operators
@@ -306,7 +306,7 @@ function resolveDescriptor(sigAlg) {
306
306
  // defense). For the one-shot families whose public key shares the signature OID
307
307
  // (EdDSA, ML-DSA, SLH-DSA), Node's WebCrypto imports an SPKI of a DIFFERENT type
308
308
  // under the requested name and verifies with the real key, so an Ed25519-signed
309
- // certificate labelled SLH-DSA would otherwise validate. Enforce structurally:
309
+ // certificate labeled SLH-DSA would otherwise validate. Enforce structurally:
310
310
  // the issuer SPKI's algorithm OID MUST equal the signature algorithm OID. (For
311
311
  // RSA/ECDSA -- different key vs signature OIDs -- WebCrypto's import already rejects
312
312
  // a mismatched key type, so `sameKeyOid` is not set and this is a no-op.)
@@ -1753,7 +1753,7 @@ function classifyCrls(parsed) {
1753
1753
  // sec. 5.2.4 @3447 makes deltaCRLIndicator a MUST-be-critical extension. A non-critical one
1754
1754
  // is non-conforming, so it does not earn the NEW capability of being merged -- merging can
1755
1755
  // RELEASE a certificate, and that must rest on a conforming indicator. It is still classified
1756
- // as a delta and still consulted for revocation, which is the shipped behaviour and the
1756
+ // as a delta and still consulted for revocation, which is the shipped behavior and the
1757
1757
  // conservative direction.
1758
1758
  if (crlNumberWithinBound(n) && deltaCritical) { rec.baseCrlNumber = n; rec.mergeable = true; }
1759
1759
  } catch (_e) {
@@ -2393,7 +2393,7 @@ function ocspChecker(responses) {
2393
2393
  * `pki.schema.ocsp.parseResponse` result. A REBUILT parsed response is refused. A
2394
2394
  * signature check has three parts -- the signature, the algorithm that verifies it,
2395
2395
  * and the bytes it covers -- and on a parsed object all three are separate properties:
2396
- * a genuine CA signature over a certificate that CA issued, relabelled, verifies as a
2396
+ * a genuine CA signature over a certificate that CA issued, relabeled, verifies as a
2397
2397
  * ResponseData signature for a response that never existed. The parser marks what it
2398
2398
  * returns, so those three are known to have been derived together from one byte
2399
2399
  * string; `Object.assign`, spread and a JSON round-trip all drop the mark, which is
@@ -2795,7 +2795,7 @@ async function _fetchAiaIssuers(current, aia) {
2795
2795
  for (var c = 0; c < certs.length; c++) {
2796
2796
  var parsed;
2797
2797
  try { parsed = coerceCert(certs[c]); }
2798
- catch (_e3) { /* allow:swallow-unverified verified-unreachable: every cert here already passed the IDENTICAL x509.parse in _aiaParseBody (single-DER validates `body`; certs-only validates each via parseCertsOnly), so coerceCert re-parsing the same bytes cannot throw -- the guard stays as defence-in-depth */ continue; }
2798
+ catch (_e3) { /* allow:swallow-unverified verified-unreachable: every cert here already passed the IDENTICAL x509.parse in _aiaParseBody (single-DER validates `body`; certs-only validates each via parseCertsOnly), so coerceCert re-parsing the same bytes cannot throw -- the guard stays as defense-in-depth */ continue; }
2799
2799
  out.push(parsed);
2800
2800
  }
2801
2801
  }
@@ -3111,7 +3111,7 @@ module.exports = {
3111
3111
  // The set of extension OIDs whose CRITICAL semantics this validator processes (RFC 5280 sec. 6.1).
3112
3112
  // Exposed so a linter can distinguish "processed" from "merely decoded" and stay consistent with
3113
3113
  // the path-validation verdict on a critical extension -- a decoder in certExtensionDecoders is NOT
3114
- // by itself proof the criticality is honoured. Both sets are frozen so a caller cannot mutate them.
3114
+ // by itself proof the criticality is honored. Both sets are frozen so a caller cannot mutate them.
3115
3115
  PROCESSED_EXTENSIONS: PROCESSED_EXTENSIONS,
3116
3116
  // Extensions that ARE processed for an intermediate CA but are unprocessed on the target/leaf, so a
3117
3117
  // critical instance on the target fails closed (RFC 5280 sec. 6.1.5(f)) -- policyMappings is
package/lib/pki-build.js CHANGED
@@ -129,7 +129,33 @@ function makeBuilder(ctx) {
129
129
  if (!Buffer.isBuffer(ipBuf) || (ipBuf.length !== 4 && ipBuf.length !== 16)) throw E("bad-input", "iPAddress must be a 4- or 16-octet Buffer or an IPv4/IPv6 string");
130
130
  return b.contextPrimitive(7, ipBuf);
131
131
  case "directoryName": return b.explicit(4, encodeName(v)); // Name is a CHOICE -> the context tag is EXPLICIT
132
- default: throw E("bad-input", "unsupported GeneralName form " + JSON.stringify(k) + " (supported: rfc822Name, dNSName, uniformResourceIdentifier, iPAddress, directoryName)");
132
+ case "otherName":
133
+ // otherName ::= SEQUENCE { type-id OBJECT IDENTIFIER, value [0] EXPLICIT ANY }, tagged
134
+ // [0] IMPLICIT (RFC 5280 sec. 4.2.1.6). The value is EXPLICIT because ANY carries no tag
135
+ // of its own, so the wrapper is what makes the encoding unambiguous -- the same shape the
136
+ // decoder requires, and without it an SmtpUTF8Mailbox (RFC 8398 sec. 3) could be read but
137
+ // never written.
138
+ if (typeof v !== "object" || Buffer.isBuffer(v)) throw E("bad-input", "otherName must be an object { typeId, value }");
139
+ if (typeof v.typeId !== "string" || !v.typeId) throw E("bad-input", "otherName requires a `typeId` OID string");
140
+ if (!Buffer.isBuffer(v.value) || v.value.length === 0) {
141
+ throw E("bad-input", "otherName requires a `value` Buffer holding one DER element");
142
+ }
143
+ // The value is spliced in raw and this verb SIGNS the result, so it is strictly
144
+ // validated rather than taken on the caller's word. Framing alone is not DER: a
145
+ // BOOLEAN whose content octet is 0x01, a NumericString holding "@", or a SET in no
146
+ // canonical order all frame cleanly and would ship inside a [0] EXPLICIT wrapper
147
+ // under a real signature. guard.der.tlv is the one place that rule lives.
148
+ guard.der.tlv(v.value, E, "bad-input", "otherName `value`");
149
+ // [0] IMPLICIT on a SEQUENCE replaces the SEQUENCE tag, so the content is the two
150
+ // members concatenated -- build.contextConstructed takes content bytes, not elements.
151
+ // The type-id encode is wrapped so a malformed OID reports in the CALLER's namespace
152
+ // (x509/bad-input, csr/bad-input, ...) like every other raw-OID path here, rather than
153
+ // surfacing the codec's own oid/* code from a shared builder the caller never named.
154
+ var typeIdDer;
155
+ try { typeIdDer = b.oid(v.typeId); }
156
+ catch (e) { throw E("bad-input", "invalid otherName type-id OID " + JSON.stringify(v.typeId) + " (violates the X.660 arc bounds)", e); }
157
+ return b.contextConstructed(0, Buffer.concat([typeIdDer, b.explicit(0, v.value)]));
158
+ default: throw E("bad-input", "unsupported GeneralName form " + JSON.stringify(k) + " (supported: rfc822Name, dNSName, uniformResourceIdentifier, iPAddress, directoryName, otherName)");
133
159
  }
134
160
  }
135
161
 
@@ -1594,17 +1594,9 @@ function _extValueFromDer(name, der) {
1594
1594
  // and RFC 3161 timestamping, and must not creep into a third consumer). On the ENCODE path such a value simply
1595
1595
  // degrades to the byte-exact ~oid + byte-string form, losing nothing; on the DECODE path it is a fail-closed
1596
1596
  // verdict.
1597
- var _ANY_VALUE_READERS = (function () {
1598
- var m = {}, R = asn1.read, T = asn1.TAGS;
1599
- m[T.BOOLEAN] = R.boolean; m[T.INTEGER] = R.integer; m[T.ENUMERATED] = R.enumerated;
1600
- m[T.BIT_STRING] = R.bitString; m[T.OCTET_STRING] = R.octetString; m[T.NULL] = R.nullValue;
1601
- m[T.OBJECT_IDENTIFIER] = R.oid; m[T.UTC_TIME] = R.time; m[T.GENERALIZED_TIME] = R.time;
1602
- // NumericString reads through its own reader: it is not a DirectoryString type, and routing it through
1603
- // read.string would fold it into the RFC 5280 sec. 7.1 name-comparison identity class (see asn1-der.js).
1604
- m[T.NUMERIC_STRING] = R.numericString;
1605
- [T.UTF8_STRING, T.PRINTABLE_STRING, T.IA5_STRING, T.TELETEX_STRING, T.VISIBLE_STRING, T.BMP_STRING, T.UNIVERSAL_STRING].forEach(function (t) { m[t] = R.string; });
1606
- return m;
1607
- })();
1597
+ // The per-type strict content readers, the SET canonical-order rule and the recursive element
1598
+ // walk all moved to guard.der, so this module and pki-build cannot drift on what "one strictly
1599
+ // valid DER element" means.
1608
1600
 
1609
1601
  // A universal SET's required member order depends on a type the ANY does not carry: X.690 sec. 11.6 orders a
1610
1602
  // SET OF by the members' full encodings, while a structured SET is ordered by TAG (X.680 sec. 8.6), and the two
@@ -1612,24 +1604,6 @@ var _ANY_VALUE_READERS = (function () {
1612
1604
  // tag but after it by octets). A structured SET cannot repeat a tag, so a repeated tag proves SET OF and the
1613
1605
  // octet rule binds; with all-distinct tags either reading is possible, so accept a value that satisfies EITHER
1614
1606
  // (rejecting only what is non-canonical under both readings: sound in both directions, never a guess).
1615
- var _TAG_CLASS_RANK = { universal: 0, application: 1, context: 2, private: 3 };
1616
- function _setOrderOk(kids) {
1617
- var i, dup = false, seen = {};
1618
- for (i = 0; i < kids.length; i++) {
1619
- var key = kids[i].tagClass + ":" + kids[i].tagNumber;
1620
- if (seen[key]) { dup = true; break; }
1621
- seen[key] = true;
1622
- }
1623
- var octetAsc = true, tagAsc = true;
1624
- for (i = 1; i < kids.length; i++) {
1625
- if (Buffer.compare(kids[i - 1].bytes, kids[i].bytes) > 0) octetAsc = false;
1626
- // Tag order ranks by CLASS first (universal < application < context < private, X.680 sec. 8.6), then by tag
1627
- // number. Compare the class's NUMBER: the names do not sort in class order.
1628
- var pc = _TAG_CLASS_RANK[kids[i - 1].tagClass], cc = _TAG_CLASS_RANK[kids[i].tagClass];
1629
- if (pc !== cc ? pc > cc : kids[i - 1].tagNumber > kids[i].tagNumber) tagAsc = false;
1630
- }
1631
- return dup ? octetAsc : (octetAsc || tagAsc);
1632
- }
1633
1607
 
1634
1608
  // Strict-validate a decoded DER element at ANY depth, in the caller's error domain. Rejects the reserved EOC tag
1635
1609
  // 0; runs a universal primitive through its strict content reader (or rejects a type with none); recurses into a
@@ -1638,37 +1612,13 @@ function _setOrderOk(kids) {
1638
1612
  // components this gate cannot verify (the degenerate empty form is not a valid encoding of any of them), so it is
1639
1613
  // refused for the same reason an unvalidatable primitive is. A NON-universal element (a legitimately context- or
1640
1614
  // application-tagged ANY) passes on its framing, but its constructed children are still walked.
1641
- function _strictDerElement(node, code, label) {
1642
- if (node.tagClass === "universal" && node.tagNumber === 0) throw _err(code, label + " must not use the reserved end-of-contents encoding (tag 0)");
1643
- if (node.constructed) {
1644
- if (node.tagClass === "universal" && node.tagNumber !== asn1.TAGS.SEQUENCE && node.tagNumber !== asn1.TAGS.SET) {
1645
- throw _err(code, label + " of universal constructed type " + node.tagNumber + " has no strict DER structure validator here");
1646
- }
1647
- var kids = node.children; // asn1.decode always sets a (possibly empty) children array on a constructed node
1648
- for (var i = 0; i < kids.length; i++) _strictDerElement(kids[i], code, label);
1649
- if (node.tagClass === "universal" && node.tagNumber === asn1.TAGS.SET && !_setOrderOk(kids)) {
1650
- throw _err(code, label + " has a SET whose members are in no canonical DER order (X.690 sec. 11.6 / X.680 sec. 8.6)");
1651
- }
1652
- return;
1653
- }
1654
- if (node.tagClass === "universal") {
1655
- // Validate-or-reject: a universal primitive with no strict content reader is never accepted on framing alone
1656
- // (asn1.decode frames a malformed NumericString "12 01 40" happily), so the map is exhaustive by refusal.
1657
- var reader = _ANY_VALUE_READERS[node.tagNumber];
1658
- if (!reader) throw _err(code, label + " of universal type " + node.tagNumber + " has no strict DER content validator here");
1659
- try { reader(node); } catch (e) { throw _err(code, label + " is not a valid DER element for its type", e); }
1660
- }
1661
- }
1662
1615
  // Raw ANY bytes about to be spliced verbatim must be exactly one non-empty, well-formed AND strictly-valid DER
1663
- // element: framing + no-trailing-data via asn1.decode, then content / structure / SET order via
1664
- // _strictDerElement, both reported in the CALLER's error domain. Returns the bytes so a call site can wrap a
1665
- // splice inline. Used by every reconstruct site that emits caller-supplied ANY bytes verbatim.
1616
+ // element. The rule itself lives in guard.der -- framing + no-trailing-data, then content / structure / SET
1617
+ // order -- so this module and the certificate builder cannot drift apart on what "one DER element" means. This
1618
+ // stays as a named local because every reconstruct site here reads better naming the intent than the guard.
1619
+
1666
1620
  function _requireStrictDerTlv(content, code, label) {
1667
- if (content.length === 0) throw _err(code, label + " must be a non-empty DER element");
1668
- var node;
1669
- try { node = asn1.decode(content); } catch (e) { throw _err(code, label + " must be exactly one well-formed DER element (no trailing data)", e); }
1670
- _strictDerElement(node, code, label);
1671
- return content;
1621
+ return guard.der.tlv(content, _err, code, label);
1672
1622
  }
1673
1623
 
1674
1624
  // asn1.build.set SORTS its members, so handing it a non-canonically-ordered values list would silently rewrite
@@ -1700,7 +1650,7 @@ function _sdaToDer(node, isNative) {
1700
1650
  if (tname === undefined) throw _err("c509/bad-extensions", "a subjectDirectoryAttributes attribute type int " + ti + " has no C509 sec. 8.6 registry row");
1701
1651
  // countryName / serialNumber carry a CHARACTER restriction (draft sec. 3.1.4 "SHALL contain only
1702
1652
  // characters from the 74-character ASCII subset permitted by PrintableString"), and not a sign override --
1703
- // _reconAttrValue asserts the charset and honours the declared string type. Requiring the negative sign
1653
+ // _reconAttrValue asserts the charset and honors the declared string type. Requiring the negative sign
1704
1654
  // here would also make these attributes unrepresentable in a NATIVE certificate, whose ints SHALL all be
1705
1655
  // non-negative (same sec.), so the rule is the charset, not the sign.
1706
1656
  for (var vi = 0; vi < valuesNode.children.length; vi++) {
@@ -1896,7 +1846,7 @@ function _reconAttrValue(rdn) {
1896
1846
  if (rdn.type === "emailAddress") return b.ia5(s);
1897
1847
  // serialNumber / countryName carry a CHARACTER restriction, not a string-type override: draft sec. 3.1.4
1898
1848
  // "SHALL contain only characters from the 74-character ASCII subset permitted by PrintableString". Enforce
1899
- // that on the CHARACTERS and still honour the sign for the string type. Coercing them to PrintableString
1849
+ // that on the CHARACTERS and still honor the sign for the string type. Coercing them to PrintableString
1900
1850
  // regardless of sign would make the +N and -N encodings of one value reconstruct IDENTICAL DER, so a single
1901
1851
  // X.509 signature would cover two distinct C509 encodings (a malleability window in the type-3 transform).
1902
1852
  // b.printable IS the PrintableString charset authority -- run it for the assert even when the sign selects
package/lib/schema-cmp.js CHANGED
@@ -460,7 +460,7 @@ var CERT_RESPONSE = schema.seq([
460
460
  // accepted (0) or grantedWithMods (1). Any other status (rejection, waiting,
461
461
  // the revocation / keyUpdate warnings) denies or defers the request, so a
462
462
  // certificate under it is a malformed response even when no explicit failInfo
463
- // bit is set (a rejection is commonly signalled by status alone). Keying the
463
+ // bit is set (a rejection is commonly signaled by status alone). Keying the
464
464
  // rule off failInfo presence alone would let a bare-rejection status ship a
465
465
  // certificate (RFC 9810 sec. 5.3.4).
466
466
  if (certifiedKeyPair !== null && status.status.code !== 0 && status.status.code !== 1) {
package/lib/schema-crl.js CHANGED
@@ -41,7 +41,7 @@ var CRL_REASONS = pkix.CRL_REASON_NAMES;
41
41
 
42
42
  // Extension-value decoding is keyed off the stable dotted OID (resolved once at
43
43
  // load from the canonical name) and not the mutable display name, so a caller's
44
- // pki.oid.register() display override cannot change parse behaviour.
44
+ // pki.oid.register() display override cannot change parse behavior.
45
45
  var OID_CRL_NUMBER = oid.byName("cRLNumber");
46
46
  var OID_REASON_CODE = oid.byName("reasonCode");
47
47
  var OID_INVALIDITY_DATE = oid.byName("invalidityDate");
@@ -40,11 +40,11 @@ function _fail(ctx, code, message) {
40
40
  }
41
41
 
42
42
  // ---- shape assertions ------------------------------------------------
43
- // A schema's `assert` mode is a real behaviour-preservation control: some
43
+ // A schema's `assert` mode is a real behavior-preservation control: some
44
44
  // hand-written guards checked the universal SEQUENCE tag (algorithmIdentifier,
45
45
  // Name), others checked only that the node had children (Validity, SPKI, the
46
46
  // tbs body, an AttributeTypeAndValue). Collapsing them to one "is a SEQUENCE"
47
- // check would silently change behaviour on SET-wrapped input, so each is kept.
47
+ // check would silently change behavior on SET-wrapped input, so each is kept.
48
48
 
49
49
  function _assertShape(schema, node, ctx) {
50
50
  var mode = schema.assert || "sequence";
@@ -1228,7 +1228,7 @@ function certExtensionDecoders(ns) {
1228
1228
 
1229
1229
  // qcStatements ::= SEQUENCE OF QCStatement (RFC 3739 sec. 3.2.6). QCStatement ::= SEQUENCE {
1230
1230
  // statementId OBJECT IDENTIFIER, statementInfo ANY DEFINED BY statementId OPTIONAL }. Known statementIds
1231
- // (RFC 3739 id-qcs + the ETSI EN 319 412-5 esi4 catalog) decode their statementInfo; an unknown statementId
1231
+ // (RFC 3739 id-qcs + the ETSI EN 319 412-5 esi4 catalogue) decode their statementInfo; an unknown statementId
1232
1232
  // is preserved OPAQUE (the RFC 3739 open type), never rejected. The DEFINED-BY selection is an OID re-dispatch.
1233
1233
  function _qcStr(node, tag, C, what) {
1234
1234
  if (!node || node.tagClass !== "universal" || node.tagNumber !== tag) throw ns.E(C, what);
package/lib/smime.js CHANGED
@@ -846,7 +846,7 @@ function _capped(msg) {
846
846
  * compared under RFC 5280 sec. 7.5: the local-part exactly, the host-part case-insensitively. The
847
847
  * address is read from the `subjectAltName` `rfc822Name` entries (RFC 8550 sec. 4.4.3) and, where the
848
848
  * extension carries none, from the subject DN's PKCS #9 `emailAddress` attribute, which RFC 8550 sec. 3
849
- * requires a receiving agent to recognise. Where both are present the extension wins. Only a signer
849
+ * requires a receiving agent to recognize. Where both are present the extension wins. Only a signer
850
850
  * whose signature verified contributes an identity, so a tampered message cannot report a binding. `match` is THREE-valued and a caller enforcing sender binding tests
851
851
  * `match === true` -- `false` means every identity was comparable and none matched, and `null` means the
852
852
  * question was not answered (no `expectedSender` and no single outer `From`, a signer certificate
@@ -8,7 +8,7 @@
8
8
  // validator-cose -- the SINGLE home for "is this a conformant WebAuthn credential
9
9
  // COSE_Key" (RFC 9052 sec. 7 structure + RFC 9053 EC2/OKP/RSA key parameters + the
10
10
  // CTAP2 canonical-CBOR profile WebAuthn sec. 6.5.1 imposes). Sibling to the guard
11
- // family: where a guard owns a CVE-class fail-closed defence once, a validator owns a
11
+ // family: where a guard owns a CVE-class fail-closed defense once, a validator owns a
12
12
  // decoded TYPE's COMPLETE conformance rule set once, so a format module composes the
13
13
  // family instead of re-deriving a partial subset inline (the drift that leaks MUSTs
14
14
  // out one review round at a time). Enforced by the validator-shape-reinlined
@@ -242,10 +242,10 @@ function assertKeyMaterial(key, E, code, unsupportedCode) {
242
242
  // PROFILE: the declared alg must match the key type (and, for EC2, the curve).
243
243
  var prof = ALG_PROFILE[String(key.alg)];
244
244
  // An algorithm this verifier does not implement is not a malformed key. The key can be perfectly
245
- // well-formed (the same bytes may parse under a neighbouring algorithm id), and a relying
245
+ // well-formed (the same bytes may parse under a neighboring algorithm id), and a relying
246
246
  // party migrating credential rows written elsewhere needs to tell "I cannot check this
247
247
  // algorithm" from "these bytes are wrong", since only one of those is fixable by re-registering.
248
- // Callers that do not distinguish the two pass one code and keep the previous behaviour.
248
+ // Callers that do not distinguish the two pass one code and keep the previous behavior.
249
249
  if (!prof) throw new E(unsupportedCode || code, "unsupported credential key algorithm " + key.alg);
250
250
  if (prof.kty !== key.kty) throw bad("credential key algorithm " + key.alg + " is inconsistent with key type " + key.kty);
251
251
  if (prof.crv != null && prof.crv !== key.crv) throw bad("credential key algorithm " + key.alg + " requires a different curve");