@blamejs/pki 0.6.13 → 0.6.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/README.md +1 -1
  3. package/index.js +1 -1
  4. package/lib/acme.js +59 -815
  5. package/lib/asn1-der.js +7 -245
  6. package/lib/attrcert-sign.js +12 -260
  7. package/lib/attrcert-verify.js +1 -7
  8. package/lib/byte-reader.js +0 -8
  9. package/lib/byte-writer.js +0 -11
  10. package/lib/cbor-det.js +0 -54
  11. package/lib/cmc-build.js +15 -357
  12. package/lib/cmc-verify.js +6 -239
  13. package/lib/cmp-build.js +41 -317
  14. package/lib/cmp-session.js +73 -760
  15. package/lib/cmp-verify.js +32 -488
  16. package/lib/cms-compress.js +3 -32
  17. package/lib/cms-decrypt.js +29 -294
  18. package/lib/cms-encrypt.js +14 -234
  19. package/lib/cms-sign.js +29 -324
  20. package/lib/cms-verify.js +9 -500
  21. package/lib/composite-kem.js +2 -102
  22. package/lib/composite-sig.js +1 -77
  23. package/lib/constants.js +0 -249
  24. package/lib/crl-sign.js +9 -237
  25. package/lib/crl-verify.js +1 -10
  26. package/lib/crmf-sign.js +19 -198
  27. package/lib/crmf-verify.js +1 -12
  28. package/lib/csr-sign.js +3 -34
  29. package/lib/csr-verify.js +1 -12
  30. package/lib/ct.js +28 -253
  31. package/lib/edwards-point.js +10 -44
  32. package/lib/est.js +28 -764
  33. package/lib/framework-error.js +4 -182
  34. package/lib/guard-all.js +1 -58
  35. package/lib/guard-async.js +1 -36
  36. package/lib/guard-bytes.js +6 -650
  37. package/lib/guard-compress.js +5 -102
  38. package/lib/guard-crypto.js +1 -40
  39. package/lib/guard-der.js +2 -93
  40. package/lib/guard-encoding.js +2 -60
  41. package/lib/guard-header.js +1 -36
  42. package/lib/guard-identifier.js +7 -531
  43. package/lib/guard-intrinsic.js +1 -150
  44. package/lib/guard-json.js +10 -73
  45. package/lib/guard-limits.js +1 -77
  46. package/lib/guard-list.js +1 -49
  47. package/lib/guard-name.js +3 -209
  48. package/lib/guard-parsed.js +7 -335
  49. package/lib/guard-range.js +1 -71
  50. package/lib/guard-secret.js +1 -102
  51. package/lib/guard-text.js +1 -64
  52. package/lib/guard-time.js +1 -97
  53. package/lib/hpke.js +3 -105
  54. package/lib/http-digest.js +14 -142
  55. package/lib/http-retry-after.js +1 -47
  56. package/lib/http-transport.js +21 -131
  57. package/lib/inspect.js +30 -284
  58. package/lib/ip-utils.js +6 -47
  59. package/lib/jose.js +7 -142
  60. package/lib/key.js +2 -67
  61. package/lib/lint.js +9 -194
  62. package/lib/merkle.js +1 -27
  63. package/lib/mime.js +9 -82
  64. package/lib/ocsp-request-verify.js +1 -14
  65. package/lib/ocsp-verify.js +1 -82
  66. package/lib/ocsp.js +11 -169
  67. package/lib/oid.js +5 -337
  68. package/lib/path-validate.js +103 -1492
  69. package/lib/pbes2.js +2 -89
  70. package/lib/pkcs12-build.js +31 -379
  71. package/lib/pki-build.js +64 -290
  72. package/lib/rc2.js +2 -18
  73. package/lib/rfc3339.js +4 -27
  74. package/lib/scep.js +59 -233
  75. package/lib/schema-all.js +0 -122
  76. package/lib/schema-attrcert.js +4 -146
  77. package/lib/schema-c509.js +153 -802
  78. package/lib/schema-cmc.js +5 -220
  79. package/lib/schema-cmp.js +2 -223
  80. package/lib/schema-cms.js +13 -427
  81. package/lib/schema-crl.js +5 -68
  82. package/lib/schema-crmf.js +6 -167
  83. package/lib/schema-csr.js +1 -54
  84. package/lib/schema-csrattrs.js +2 -87
  85. package/lib/schema-engine.js +0 -152
  86. package/lib/schema-ocsp.js +0 -117
  87. package/lib/schema-pkcs12.js +2 -93
  88. package/lib/schema-pkcs8.js +3 -35
  89. package/lib/schema-pkix.js +24 -655
  90. package/lib/schema-smime.js +0 -54
  91. package/lib/schema-tsp.js +3 -99
  92. package/lib/schema-x509.js +4 -63
  93. package/lib/shbs.js +11 -95
  94. package/lib/sign-scheme.js +2 -97
  95. package/lib/sigstore.js +9 -169
  96. package/lib/sleep.js +2 -8
  97. package/lib/smime.js +15 -408
  98. package/lib/tls-cert-compress.js +0 -62
  99. package/lib/trust.js +19 -185
  100. package/lib/tsp-sign.js +16 -270
  101. package/lib/validator-all.js +1 -30
  102. package/lib/validator-attcert.js +2 -46
  103. package/lib/validator-cose.js +2 -153
  104. package/lib/validator-keydesc.js +1 -37
  105. package/lib/validator-sig.js +1 -46
  106. package/lib/validator-tls.js +1 -88
  107. package/lib/validator-tpm.js +19 -122
  108. package/lib/verify-seam.js +1 -24
  109. package/lib/webauthn-mds.js +3 -400
  110. package/lib/webauthn.js +12 -833
  111. package/lib/webcrypto.js +5 -485
  112. package/lib/x509-sign.js +4 -108
  113. package/package.json +1 -1
  114. package/sbom.cdx.json +6 -6
@@ -1,97 +1,32 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // Copyright (c) blamejs contributors
3
3
  "use strict";
4
- //
5
- // @internal -- no operator-facing namespace. The documented surface is the
6
- // consumers whose decompression composes this guard (pki.cms.decompress,
7
- // pki.tls.decompressCertificate).
8
- //
9
- // guard-compress -- bounded, non-malleable decompression of an untrusted stream.
10
- // Node's zlib / brotli / zstd decompressors are by default both UNBOUNDED and
11
- // PERMISSIVE, and each is a distinct vulnerability class:
12
- //
13
- // * Unbounded output (CWE-409, decompression bomb) -- a few hundred bytes
14
- // expand to gigabytes. `maxOutputLength` refuses the moment the output WOULD
15
- // exceed the bound; it does not allocate the whole output and then measure.
16
- // So the cap has to be applied AT the call and can never be a check on the
17
- // returned value.
18
- // * Trailing input silently ignored (CWE-20, canonicalization malleability) --
19
- // all three stop at the end of the first complete frame and discard whatever
20
- // follows, so a caller may append arbitrary bytes, or a second entire frame,
21
- // and still recover byte-identical output. One content would then have
22
- // unboundedly many encodings, and a digest over the compressed object would
23
- // stop identifying what it decompresses to.
24
- //
25
- // Both defenses are applied here, once, for every algorithm, so a new consumer
26
- // cannot pick up one and miss the other. Verified uniform across zlib, brotli
27
- // and zstd: each reports consumed input as `engine.bytesWritten` under
28
- // `info: true`, each raises ERR_BUFFER_TOO_LARGE on a cap breach, and each
29
- // accepts trailing bytes when left unchecked.
30
- //
31
- // E is the caller's (code, message, cause) typed-error factory, never an error
32
- // class. Passing a class here crashes on the error path ("class cannot be invoked
33
- // without new"), which converts a rejection into a fail-open. `codes` names the
34
- // two verdicts a caller distinguishes: `tooLarge` when the output would exceed
35
- // the cap, `failed` for every other fault. Every non-cap fault collapses to one
36
- // code deliberately -- a per-errno surface is attack telemetry.
4
+ // @internal
37
5
 
38
6
  var zlib = require("zlib");
39
- // Taken at load. The capability probe below decides which algorithms are safe to expose, and the
40
- // bounded decompress measures its cap with these, so a replacement chooses both. See
41
- // guard-intrinsic for the whole captured set.
42
7
  var _intrinsic = require("./guard-intrinsic");
43
8
  var _bufferFrom = _intrinsic.bufferFrom;
44
9
  var _isBuffer = _intrinsic.isBuffer;
45
10
  var _isInteger = _intrinsic.isInteger;
46
11
  var _objectKeys = _intrinsic.keys;
47
12
  var _stringify = _intrinsic.stringify;
48
- // This probe decides which algorithms qualify as truncation-reporting, so both the round-trip
49
- // comparison and the frame-size floor below settle whether a compressor is trusted. Reading either
50
- // off a prototype lets a replacement qualify one that silently accepts a cut frame.
51
13
  var _compare = _intrinsic.compare;
52
14
  var _sizeOf = _intrinsic.sizeOf;
53
- // The cut that makes the truncated frame. A replacement returning the whole frame means the
54
- // decompressor is handed something intact, so an algorithm that ignores truncation qualifies on a
55
- // test that never truncated anything.
56
15
  var _subarray = _intrinsic.uncurry(Uint8Array.prototype.subarray);
57
16
  var _hasOwn = _intrinsic.hasOwn;
58
17
 
59
- // The decompressor per algorithm: a registry, so a new algorithm is a row rather
60
- // than a branch and an unrecognized name fails closed.
61
18
  var DECOMPRESS = {
62
- zlib: zlib.inflateSync, // RFC 1950
63
- brotli: zlib.brotliDecompressSync, // RFC 7932
64
- zstd: zlib.zstdDecompressSync, // RFC 8478
19
+ zlib: zlib.inflateSync,
20
+ brotli: zlib.brotliDecompressSync,
21
+ zstd: zlib.zstdDecompressSync,
65
22
  };
66
23
 
67
- // The compressors, used only by the truncation probe below. Producing a compressed stream is
68
- // not the guarded shape; decompressing an untrusted one is.
69
24
  var _PROBE_COMPRESS = {
70
25
  zlib: zlib.deflateSync,
71
26
  brotli: zlib.brotliCompressSync,
72
27
  zstd: zlib.zstdCompressSync,
73
28
  };
74
29
 
75
- // A decompressor must REPORT a frame it could not finish. Not all of them do: on some supported
76
- // runtimes the zstd binding returns a SHORT (often empty) result for a truncated frame instead
77
- // of faulting, and reports the entire input as consumed, so neither the returned bytes nor the
78
- // consumed-length check below can see that the frame was cut. That is a silent truncation: a peer
79
- // strips a frame's tail and the receiver processes a prefix as though it were the whole message.
80
- //
81
- // An algorithm whose runtime cannot report this cannot be decompressed safely here, so it is
82
- // dropped from the advertised set at load instead of accepted with a defect. Consumers build
83
- // their wire registries by intersecting with algorithms(), so a protocol simply does not offer
84
- // or accept it, and it returns on its own once the runtime reports truncation.
85
- // Nothing here is enabled by an exception alone: an algorithm qualifies only by positive proof
86
- // that a whole frame round-trips and that a cut frame is refused. Every other outcome (a
87
- // missing function, a compressor fault, a wrong round-trip, an unrelated error) leaves it out.
88
- //
89
- // Coverage residual: the early-return branches are reachable only on a runtime whose own
90
- // compression library is broken (a missing export, a compressor that throws on 46 ASCII bytes, a
91
- // round-trip that does not reproduce its input). The tables are captured at module load, so those
92
- // states cannot be induced from a test without replacing node:zlib beforehand, and forcing them
93
- // would assert nothing about the property this function exists to establish. They stay because
94
- // their absence is what would make a broken runtime silently qualify an algorithm.
95
30
  function _reportsTruncation(name) {
96
31
  var compress = _PROBE_COMPRESS[name];
97
32
  var decompress = DECOMPRESS[name];
@@ -100,53 +35,32 @@ function _reportsTruncation(name) {
100
35
  var frame, whole;
101
36
  try { frame = compress(sample); }
102
37
  catch (_e) { /* allow:swallow-unverified a compressor that cannot compress 46 ASCII bytes is a broken runtime; the algorithm simply does not qualify, which is the fail-closed direction */ return false; }
103
- // A cut of 2 removes the frame's end marker; the sample is long enough that what remains is
104
- // still a plausible frame head and not nothing at all.
105
38
  if (!_isBuffer(frame) || _sizeOf(frame) < 4) return false;
106
39
  try { whole = decompress(frame, { maxOutputLength: 4096 }); }
107
40
  catch (_e2) { /* allow:swallow-unverified a decompressor that rejects its own compressor's whole frame is a broken runtime; the algorithm does not qualify, again fail-closed */ return false; }
108
41
  if (!_isBuffer(whole) || _compare(whole, sample) !== 0) return false;
109
- // The cut frame must be refused. A returned value of any length, including the empty buffer
110
- // some bindings hand back, means a truncation would pass through unseen.
111
42
  var cutAccepted = false;
112
43
  try {
113
44
  decompress(_subarray(frame, 0, _sizeOf(frame) - 2), { maxOutputLength: 4096 });
114
45
  cutAccepted = true;
115
- } catch (_e3) { /* refused, as a decompressor handed an unfinished frame must */ }
46
+ } catch (_e3) { }
116
47
  return !cutAccepted;
117
48
  }
118
49
 
119
50
  var SAFE = {};
120
51
  _intrinsic.forEach(_objectKeys(DECOMPRESS), function (n) { if (_reportsTruncation(n)) SAFE[n] = true; });
121
52
 
122
- // bounded(algorithm, stream, cap, E, codes, label) -> Buffer. Decompress `stream`
123
- // under `algorithm`, refusing any output that would exceed `cap` bytes and any
124
- // input carrying bytes past the end of the compressed frame.
125
- // The shape is the DECOMPRESSOR CALL ITSELF, not the presence of a cap: an
126
- // uncapped call is precisely the dangerous form, so keying on `maxOutputLength`
127
- // would fire on the safe re-inline and stay silent on the unsafe one. Every
128
- // decompression in lib/ therefore belongs to this module or routes through it.
129
53
  // @enforced-by guard-shape-reinlined
130
54
  // @guard-shape \bzlib\.(inflate|brotliDecompress|zstdDecompress)Sync\b
131
55
  // @guard-via \bguard\.compress\.bounded\s*\(
132
56
  function bounded(algorithm, stream, cap, E, codes, label) {
133
57
  var decompress = DECOMPRESS[algorithm];
134
- // An unknown algorithm -- or one this runtime cannot decompress safely -- is an authoring
135
- // fault, not untrusted input: a caller resolves a wire value to a name against algorithms()
136
- // and rejects one it does not find, before reaching here. Arriving with a name outside that
137
- // set means the consumer skipped the step, so it faults loudly and never silently.
138
58
  if (!_hasOwn(SAFE, algorithm) || !decompress) {
139
59
  throw new TypeError("guard.compress.bounded: unknown or unsafe algorithm " + _stringify(algorithm));
140
60
  }
141
- // The byte-source normalization is guard.bytes.view's job and belongs at the
142
- // consumer's boundary; reaching here with anything else means it was skipped.
143
61
  if (!_isBuffer(stream)) {
144
62
  throw new TypeError("guard.compress.bounded: stream must be a Buffer (route the input through guard.bytes.view first)");
145
63
  }
146
- // The ceiling is an authoring input: an undefined / NaN / fractional / negative
147
- // cap would silently disable the bomb defense. It must also be at least 1 --
148
- // node rejects maxOutputLength 0 with a different fault than a cap breach, so a
149
- // zero cap would surface as a malformed-stream verdict instead of a config error.
150
64
  if (!_isInteger(cap) || cap < 1) {
151
65
  throw new TypeError("guard.compress.bounded: cap must be a positive integer");
152
66
  }
@@ -160,9 +74,6 @@ function bounded(algorithm, stream, cap, E, codes, label) {
160
74
  }
161
75
  throw E(codes.failed, what + " could not be decompressed", e);
162
76
  }
163
- // `bytesWritten` is the count of INPUT bytes the engine consumed. A frame that
164
- // ends before the input does means the remainder was ignored, which is the
165
- // malleability above, so the whole input must be exactly one frame.
166
77
  var consumed = res.engine.bytesWritten;
167
78
  var streamLen = _sizeOf(stream);
168
79
  if (consumed !== streamLen) {
@@ -172,15 +83,7 @@ function bounded(algorithm, stream, cap, E, codes, label) {
172
83
  return res.buffer;
173
84
  }
174
85
 
175
- // The algorithm names this guard can decompress safely on this runtime: the registry
176
- // minus any whose decompressor cannot report an unfinished frame. A consumer intersects
177
- // its protocol's algorithm registry with this, so it never advertises or accepts something
178
- // it could not refuse a truncation of.
179
86
  // @enforced-by behavioral -- a read-only list of the safe registry's keys has no
180
- // rename-proof code shape to re-inline; the guard's reject path is the contract,
181
- // and a consumer offering an algorithm this list omits fails closed at `bounded`.
182
87
  function algorithms() { return _objectKeys(SAFE); }
183
88
 
184
- // Frozen for the reason the family header gives: a boundary reads its guard off this object at the
185
- // call, so a writable export would let a caller replace the check rather than pass it.
186
89
  module.exports = _intrinsic.freeze({ bounded: bounded, algorithms: algorithms });
@@ -1,60 +1,23 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // Copyright (c) blamejs contributors
3
3
  "use strict";
4
- //
5
- // @internal -- no operator-facing namespace. The documented surface is the
6
- // verify paths whose comparison composes this guard (pki.merkle,
7
- // pki.webcrypto.subtle.verify).
8
- //
9
- // guard-crypto -- fail-closed cryptographic-invariant guards. Enforced choke
10
- // point: a codebase-patterns detector requires every constant-time byte
11
- // comparison to route through here, so no verify path re-implements it with a
12
- // timing leak.
13
- //
14
- // Defends the timing side-channel class (CWE-208 observable timing discrepancy;
15
- // the Lucky13 / MAC-forgery family). A secret-dependent byte comparison with an
16
- // early exit (=== or a short-circuited &&) leaks the position of the first
17
- // differing byte, letting an attacker recover a MAC / tag / tree root
18
- // byte-by-byte. constantTimeEqual runs the whole comparison: a public length
19
- // gate (lengths are not secret; crypto.timingSafeEqual RangeErrors on unequal
20
- // lengths, so the gate must precede it) then a single timingSafeEqual over the
21
- // equal-length buffers -- both operands are always read, no data-dependent
22
- // branch. A length mismatch is an honest false, never a throw.
23
- //
24
- // Also defends the signature-malleability class (CWE-347 / CWE-20): a signature /
25
- // MAC / digest BIT STRING with a non-zero unused-bits count is not whole octets,
26
- // so trailing sub-octet bits let two encodings carry "one" signature. Every RFC
27
- // 9481/4211/6960/5280 signature output is a whole number of octets, so a value
28
- // with unusedBits !== 0 fails closed before its bytes reach a verifier.
4
+ // @internal
29
5
 
30
6
  var nodeCrypto = require("node:crypto");
31
7
  var _freeze = require("./guard-intrinsic").freeze;
32
8
 
33
- // constantTimeEqual(a, b) -> boolean. Length-checked, then constant-time over
34
- // equal lengths. a and b are Buffers.
35
9
  // @enforced-by guard-shape-reinlined
36
10
  // @guard-shape \.timingSafeEqual\s*\(
37
11
  function constantTimeEqual(a, b) {
38
12
  return a.length === b.length && nodeCrypto.timingSafeEqual(a, b);
39
13
  }
40
14
 
41
- // isOctetAligned(bitString) -> boolean. True when a decoded BIT STRING (a
42
- // { bytes, unusedBits } node) is whole octets. For the never-throw verify paths
43
- // (a boolean/continue verdict) so a malleable signature is skipped, not honored.
44
15
  // @enforced-by behavioral -- shares the octet-alignment rule below; a bare
45
- // .unusedBits check has legitimate non-signature siblings, so the RED vectors
46
- // (a non-octet-aligned signature is skipped / rejected) are the guard.
47
16
  function isOctetAligned(bitString) {
48
17
  return !!bitString && bitString.unusedBits === 0;
49
18
  }
50
19
 
51
- // assertOctetAligned(bitString, E, code, label) -> bitString | throws E(code,...).
52
- // The throwing form for a parse boundary: a signature / MAC / digest BIT STRING
53
- // MUST be octet-aligned, else the malleable value is rejected fail-closed.
54
20
  // @enforced-by behavioral -- .unusedBits !== 0 is a per-field RFC rule, and the
55
- // same token legitimately tests a DIFFERENT invariant (NamedBitList minimality,
56
- // schema-engine assertMinimalNamedBits), so a blanket detector would false-fire;
57
- // the RED vectors (a non-octet-aligned signature/digest rejects) are the guard.
58
21
  function assertOctetAligned(bitString, E, code, label) {
59
22
  if (!isOctetAligned(bitString)) {
60
23
  throw E(code, (label || "signature") + " BIT STRING must be octet-aligned (0 unused bits)");
@@ -62,6 +25,4 @@ function assertOctetAligned(bitString, E, code, label) {
62
25
  return bitString;
63
26
  }
64
27
 
65
- // Frozen for the reason the family header gives: a boundary reads its guard off this object at the
66
- // call, so a writable export would let a caller replace the check rather than pass it.
67
28
  module.exports = _freeze({ constantTimeEqual: constantTimeEqual, isOctetAligned: isOctetAligned, assertOctetAligned: assertOctetAligned });
package/lib/guard-der.js CHANGED
@@ -1,73 +1,24 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // Copyright (c) blamejs contributors
3
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
- // The byte comparison SET-OF ordering is decided by, taken at load. A replacement chooses which
34
- // encodings this guard calls correctly ordered. See guard-intrinsic for the whole captured set.
4
+ // @internal
35
5
  var _compare = require("./guard-intrinsic").compare;
36
6
  var _freeze = require("./guard-intrinsic").freeze;
37
7
  var _forEach = require("./guard-intrinsic").forEach;
38
- // The emptiness gate below decides whether bytes reach the decoder at all, and on a byte view
39
- // `length` is a replaceable accessor: one answering non-zero for an empty buffer sends nothing to
40
- // asn1.decode as though it were an element.
41
8
  var _sizeOf = require("./guard-intrinsic").sizeOf;
42
9
  var _create = require("./guard-intrinsic").create;
43
10
 
44
11
  var asn1 = null;
45
12
  function _asn1() { if (asn1 === null) asn1 = require("./asn1-der"); return asn1; }
46
13
 
47
- // The strict content reader per universal primitive tag. A tag absent from this table has no
48
- // validator here and is refused by `element` below -- that refusal is the point.
49
14
  var VALUE_READERS = null;
50
15
  function _readers() {
51
16
  if (VALUE_READERS !== null) return VALUE_READERS;
52
17
  var asn1 = _asn1();
53
- // Null-prototype: this table IS the allowlist of universal tags with a strict content reader, and
54
- // a tag it does not carry is refused. Built on a `{}`, an inherited numeric property supplies a
55
- // reader for a tag that was deliberately left out, and the refusal that is the point stops.
56
18
  var m = _create(null), R = asn1.read, T = asn1.TAGS;
57
- // ENUMERATED shares INTEGER's content rules and is NOT constrained to non-negative
58
- // values. X.680 (02/2021) sec. 20.2 requires each NamedNumber's SignedNumber to be
59
- // distinct, and sec. 19 defines SignedNumber ::= number | "-" number; the non-negativity
60
- // in sec. 20.3 governs only an EnumerationItem written as a bare identifier, which is
61
- // auto-assigned. `ENUMERATED { lowPriority(-1), normal(0) }` is well-formed, so rejecting
62
- // a negative here would refuse valid input. This guard validates the ENCODING; which
63
- // values a particular ENUMERATED admits lives in a type definition an opaque ANY does
64
- // not carry.
65
19
  m[T.BOOLEAN] = R.boolean; m[T.INTEGER] = R.integer; m[T.ENUMERATED] = R.enumerated;
66
20
  m[T.BIT_STRING] = R.bitString; m[T.OCTET_STRING] = R.octetString; m[T.NULL] = R.nullValue;
67
21
  m[T.OBJECT_IDENTIFIER] = R.oid; m[T.UTC_TIME] = R.time; m[T.GENERALIZED_TIME] = R.time;
68
- // NumericString reads through its own reader: it is not a DirectoryString type, and routing
69
- // it through read.string would fold it into the RFC 5280 sec. 7.1 name-comparison identity
70
- // class (see asn1-der.js).
71
22
  m[T.NUMERIC_STRING] = R.numericString;
72
23
  _forEach([T.UTF8_STRING, T.PRINTABLE_STRING, T.IA5_STRING, T.TELETEX_STRING, T.VISIBLE_STRING,
73
24
  T.BMP_STRING, T.UNIVERSAL_STRING], function (t) { m[t] = R.string; });
@@ -75,36 +26,12 @@ function _readers() {
75
26
  return VALUE_READERS;
76
27
  }
77
28
 
78
- // A universal SET's required member order depends on a type the ANY does not carry: X.690
79
- // sec. 11.6 orders a SET OF by the members' full encodings, while a structured SET is ordered
80
- // by TAG (X.680 sec. 8.6), and the two differ whenever the constructed bit does (a SEQUENCE
81
- // member, tag 16, sorts before a PrintableString, tag 19, by tag but after it by octets). A
82
- // structured SET cannot repeat a tag, so a repeated tag proves SET OF and the octet rule binds;
83
- // with all-distinct tags either reading is possible, so accept a value that satisfies EITHER
84
- // (rejecting only what is non-canonical under both readings: sound in both directions, never a
85
- // guess).
86
- //
87
- // KNOWN LIMITATION, and the two consumers do not weigh it identically. The either-reading rule
88
- // admits a SET OF that is tag-ordered but not octet-ordered, which is not canonical DER. On a
89
- // RECONSTRUCT path the input already exists and the alternative would refuse a valid structured
90
- // SET, so accepting is the sound direction. On a SIGNING path the caller composes the bytes and
91
- // could be held to octet order, so the same permissiveness lets a signer emit a non-canonical
92
- // SET under a real signature. Tightening it for signers alone would mean a mode flag on a
93
- // security posture, which this family avoids, and tightening it for both would reject valid
94
- // structured SETs that c509 round-trips today. The rule is therefore unchanged and the cost is
95
- // written down rather than discovered later.
96
- // Null-prototype: the rank decides canonical SET order, so an inherited property answering for an
97
- // unrecognized tag class would rank it rather than leave it unranked, and a SET in no canonical
98
- // order would compare as ordered.
99
29
  var TAG_CLASS_RANK = _create(null);
100
30
  TAG_CLASS_RANK.universal = 0;
101
31
  TAG_CLASS_RANK.application = 1;
102
32
  TAG_CLASS_RANK.context = 2;
103
33
  TAG_CLASS_RANK["private"] = 3;
104
34
  function setOrderOk(kids) {
105
- // A null-prototype accumulator: on a `{}` a planted `Object.prototype["universal:16"]` answers
106
- // this membership test for a tag the SET never carried, which reports a duplicate that is not
107
- // there and selects the wrong canonical-order rule for the whole SET.
108
35
  var i, dup = false, seen = _create(null);
109
36
  for (i = 0; i < kids.length; i++) {
110
37
  var key = kids[i].tagClass + ":" + kids[i].tagNumber;
@@ -114,23 +41,12 @@ function setOrderOk(kids) {
114
41
  var octetAsc = true, tagAsc = true;
115
42
  for (i = 1; i < kids.length; i++) {
116
43
  if (_compare(kids[i - 1].bytes, kids[i].bytes) > 0) octetAsc = false;
117
- // Tag order ranks by CLASS first (universal < application < context < private, X.680
118
- // sec. 8.6), then by tag number. Compare the class's NUMBER: the names do not sort in
119
- // class order.
120
44
  var pc = TAG_CLASS_RANK[kids[i - 1].tagClass], cc = TAG_CLASS_RANK[kids[i].tagClass];
121
45
  if (pc !== cc ? pc > cc : kids[i - 1].tagNumber > kids[i].tagNumber) tagAsc = false;
122
46
  }
123
47
  return dup ? octetAsc : (octetAsc || tagAsc);
124
48
  }
125
49
 
126
- // element(node, E, code, label). Strict-validate an already-decoded DER element at ANY depth.
127
- // Rejects the reserved EOC tag 0; runs a universal primitive through its strict content reader
128
- // (or refuses a type with none); recurses into a constructed element's children; holds a
129
- // universal SET to a canonical order. Only SEQUENCE and SET are accepted as universal
130
- // CONSTRUCTED types -- an EXTERNAL / EMBEDDED PDV / CHARACTER STRING has mandatory components
131
- // this gate cannot verify (the degenerate empty form is not a valid encoding of any of them),
132
- // so it is refused for the same reason an unvalidatable primitive is.
133
- //
134
50
  // @enforced-by guard-shape-reinlined
135
51
  // @guard-shape _ANY_VALUE_READERS\[
136
52
  function element(node, E, code, label) {
@@ -142,7 +58,7 @@ function element(node, E, code, label) {
142
58
  if (node.tagClass === "universal" && node.tagNumber !== T.SEQUENCE && node.tagNumber !== T.SET) {
143
59
  throw E(code, label + " of universal constructed type " + node.tagNumber + " has no strict DER structure validator here");
144
60
  }
145
- var kids = node.children; // asn1.decode always sets a (possibly empty) children array
61
+ var kids = node.children;
146
62
  for (var i = 0; i < kids.length; i++) element(kids[i], E, code, label);
147
63
  if (node.tagClass === "universal" && node.tagNumber === T.SET && !setOrderOk(kids)) {
148
64
  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)");
@@ -157,11 +73,6 @@ function element(node, E, code, label) {
157
73
  }
158
74
  }
159
75
 
160
- // tlv(content, E, code, label) -> content. Raw ANY bytes about to be spliced verbatim must be
161
- // exactly one non-empty, well-formed AND strictly-valid DER element: framing + no-trailing-data
162
- // via asn1.decode, then content / structure / SET order via `element`. Returns the bytes so a
163
- // call site can wrap a splice inline.
164
- //
165
76
  // @enforced-by guard-shape-reinlined
166
77
  // @guard-shape must be exactly one well-formed DER element
167
78
  function tlv(content, E, code, label) {
@@ -173,6 +84,4 @@ function tlv(content, E, code, label) {
173
84
  return content;
174
85
  }
175
86
 
176
- // Frozen for the reason the family header gives: a boundary reads its guard off this object at the
177
- // call, so a writable export would let a caller replace the check rather than pass it.
178
87
  module.exports = _freeze({ element: element, tlv: tlv });
@@ -1,49 +1,14 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // Copyright (c) blamejs contributors
3
3
  "use strict";
4
- //
5
- // @internal -- no operator-facing namespace. The documented surface is the
6
- // consumers whose textual-encoding integrity composes this guard (pki.jose
7
- // base64url, pki.schema.* PEM bodies, pki.webcrypto JWK key material, pki.acme
8
- // / pki.est transfer encodings).
9
- //
10
- // guard-encoding -- strict decode of an untrusted base64 / base64url / hex text
11
- // to bytes. Node's Buffer.from(x, "base64"|"base64url"|"hex") is LENIENT: it
12
- // silently drops the first invalid character (and everything after a stray one),
13
- // accepts non-canonical trailing bits, and tolerates missing/extra padding. Two
14
- // distinct texts can therefore alias one byte string, and a malformed text can
15
- // decode to a shorter, different value than intended. And it allocates the whole
16
- // decode before any size is checked. This is CWE-172 (encoding-transformation error) /
17
- // CWE-20 (canonicalization malleability) + CWE-770 (allocate-before-cap): RFC
18
- // 4648 sec. 3.5 / sec. 5 and RFC 8555 sec. 6.1 require the UNIQUE canonical
19
- // encoding. Each decoder here gates the alphabet, rejects an impossible length,
20
- // enforces the byte cap before the copy, decodes, and verifies canonicality by a
21
- // re-encode round-trip, so a value that is not the one canonical encoding of
22
- // its bytes fails closed with the caller's typed error.
23
- //
24
- // E is the caller's (code, message) typed-error factory; `code` the frozen
25
- // domain/reason; `label` the field phrase; `maxBytes` an optional decoded-size
26
- // cap (null/undefined = uncapped, for a PEM cert body already bounded upstream).
4
+ // @internal
27
5
 
28
- // The alphabets are tables, walked one character at a time. Regular expressions
29
- // would be the obvious alternative, but a guard runs on the most hostile input
30
- // the toolkit sees, and a pattern engine's cost on a non-matching string is
31
- // decided by the pattern, which is the one thing a bound cannot be placed on from
32
- // the outside. A table lookup is one array index per character, so its cost
33
- // follows the length, which the caller already caps. It is also the more honest
34
- // statement of the rule: the set of permitted characters is the rule, written out.
35
6
  function _alphabet(chars) {
36
7
  // allow:guard-reads-runtime-live -- the three tables below are the only callers and they are
37
- // built at module load, before any caller code has run. There is no live read to remove.
38
8
  var t = new Uint8Array(128);
39
9
  for (var i = 0; i < chars.length; i++) t[_charCodeAt(chars, i)] = 1;
40
10
  return t;
41
11
  }
42
- // Both halves of the round trip, taken at load, along with the string reads the alphabet checks
43
- // are built on. Canonicality here is decided by decoding the text and re-encoding the result, so
44
- // either half is enough to decide the answer: a `toString` returning the input makes every
45
- // non-canonical encoding canonical, and a `charCodeAt` deciding what is in the alphabet chooses
46
- // which characters are there to be judged.
47
12
  var _intrinsic = require("./guard-intrinsic");
48
13
  var _bufferFrom = _intrinsic.bufferFrom;
49
14
  var _toString = _intrinsic.uncurry(Buffer.prototype.toString);
@@ -58,9 +23,6 @@ var B64URL_ALPHABET = _alphabet(UPPER + LOWER + DIGITS + "-_");
58
23
  var B64_ALPHABET = _alphabet(UPPER + LOWER + DIGITS + "+/");
59
24
  var HEX_ALPHABET = _alphabet(DIGITS + "abcdef" + "ABCDEF");
60
25
 
61
- // Every character of `text` is in `table`. A code point outside Latin-1's low half
62
- // (including every astral half) is outside all three alphabets, and the explicit
63
- // bound rejects it, so no lookup can read undefined off the end of the table.
64
26
  function _inAlphabet(text, table) {
65
27
  for (var i = 0; i < text.length; i++) {
66
28
  var c = _charCodeAt(text, i);
@@ -69,13 +31,8 @@ function _inAlphabet(text, table) {
69
31
  return true;
70
32
  }
71
33
 
72
- // Reject before Buffer.from allocates: a base64 text of N chars decodes to at
73
- // most floor(N*3/4) bytes, a hex text to N/2.
74
34
  function _capBefore(nChars, perByteChars, maxBytes, E, code, label) {
75
- if (maxBytes == null) return; // documented uncapped mode (bounded upstream)
76
- // The cap is an authoring input: a NaN / fractional / negative maxBytes makes
77
- // the comparison below always false -- the cap silently disabled. Config-time
78
- // TypeError instead (null/undefined stays the documented uncapped mode).
35
+ if (maxBytes == null) return;
79
36
  if (!_isInteger(maxBytes) || maxBytes < 0) {
80
37
  throw new TypeError("guard.encoding: maxBytes must be a non-negative integer or null");
81
38
  }
@@ -84,8 +41,6 @@ function _capBefore(nChars, perByteChars, maxBytes, E, code, label) {
84
41
  }
85
42
  }
86
43
 
87
- // base64url(text, maxBytes, E, code, label) -> Buffer. Unpadded base64url (RFC
88
- // 4648 sec. 5 / RFC 7515): no "=" padding, no "+"/"/", canonical final char.
89
44
  // @enforced-by base64-decode-not-via-guard
90
45
  function base64url(text, maxBytes, E, code, label) {
91
46
  if (typeof text !== "string") throw E(code, label + " must be a string");
@@ -97,14 +52,9 @@ function base64url(text, maxBytes, E, code, label) {
97
52
  return buf;
98
53
  }
99
54
 
100
- // base64(text, maxBytes, E, code, label) -> Buffer. Padded base64 (RFC 4648
101
- // sec. 4): whole 4-character groups, canonical padding + trailing bits.
102
55
  // @enforced-by base64-decode-not-via-guard
103
56
  function base64(text, maxBytes, E, code, label) {
104
57
  if (typeof text !== "string") throw E(code, label + " must be a string");
105
- // Padding is positional, not alphabetic: at most two "=" and only at the end, so
106
- // the body is measured first and the alphabet applies to what remains. An "="
107
- // anywhere else leaves a non-alphabet character in the body and rejects there.
108
58
  var pad = 0;
109
59
  while (pad < 2 && text.length > pad && _charCodeAt(text, text.length - 1 - pad) === 0x3d) pad++;
110
60
  if (!_inAlphabet(_strSlice(text, 0, text.length - pad), B64_ALPHABET)) throw E(code, label + " is not base64 (a non-alphabet character)");
@@ -115,13 +65,7 @@ function base64(text, maxBytes, E, code, label) {
115
65
  return buf;
116
66
  }
117
67
 
118
- // hex(text, maxBytes, E, code, label) -> Buffer. Even-length hex; canonical via
119
- // a lower-case re-encode round-trip (RFC 4514 sec. 2.4 hex-string values).
120
68
  // @enforced-by behavioral -- the hex Buffer.from token has legitimate non-decode
121
- // siblings in the tree (a hard-coded AES-KW IV constant, an internal
122
- // parser-emitted canonical serial, an internal BigInt->hex), so a blanket
123
- // Buffer.from(x,"hex") detector would false-fire; the RED conformance vectors
124
- // (a non-canonical / odd-length / non-hex #hex attribute value rejects) guard it.
125
69
  function hex(text, maxBytes, E, code, label) {
126
70
  if (typeof text !== "string") throw E(code, label + " must be a string");
127
71
  if (!_inAlphabet(text, HEX_ALPHABET)) throw E(code, label + " is not hexadecimal");
@@ -132,6 +76,4 @@ function hex(text, maxBytes, E, code, label) {
132
76
  return buf;
133
77
  }
134
78
 
135
- // Frozen for the reason the family header gives: a boundary reads its guard off this object at the
136
- // call, so a writable export would let a caller replace the check rather than pass it.
137
79
  module.exports = _intrinsic.freeze({ base64url: base64url, base64: base64, hex: hex });
@@ -1,47 +1,16 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // Copyright (c) blamejs contributors
3
3
  "use strict";
4
- //
5
- // @internal -- no operator-facing namespace. The documented surface is the
6
- // consumer whose emitted-header integrity composes this guard (pki.smime header
7
- // protection, RFC 9788).
8
- //
9
- // guard-header -- fail-closed header-field integrity for a MIME / RFC 5322 header
10
- // a composer EMITS. Defends the HEADER INJECTION class (CWE-93): a caller-supplied
11
- // header field name or value carrying a CR (0x0d) / LF (0x0a) / NUL (0x00) could
12
- // inject an extra header (a Bcc, an HP-Outer record), split the message, or forge
13
- // a multipart boundary; a field name outside RFC 5322 ftext could break the
14
- // "Name: value" header grammar. It also caps the serialized line length (RFC 5322
15
- // sec. 2.1.1, 998 octets) so an over-long emitted field cannot be re-folded by a
16
- // relay into different signed bytes. Every field a composer inlines routes through
17
- // this one guard, so an emission site cannot re-inline a weaker check and
18
- // reintroduce the injection.
4
+ // @internal
19
5
 
20
6
  var C = require("./constants");
21
- // Taken at load. `byteLength` is what the RFC 5322 line limit is measured with, so a replacement
22
- // decides whether an over-long header line is refused. See guard-intrinsic for the whole set.
23
7
  var _byteLength = require("./guard-intrinsic").byteLength;
24
8
  var _freeze = require("./guard-intrinsic").freeze;
25
- // The character-code read the RFC 5322 ftext and CR/LF/NUL checks are built on. One that never
26
- // reports a control byte is a header-injection defense that inspects nothing.
27
9
  var _charCodeAt = require("./guard-intrinsic").uncurry(String.prototype.charCodeAt);
28
10
  var _stringify = require("./guard-intrinsic").stringify;
29
- // The conversion that produces the text this guard then scans for CR and LF. A replacement returns
30
- // a clean string for a value carrying a line break, so the injection scan runs on text that is not
31
- // what gets emitted.
32
11
  var _String = require("./guard-intrinsic").String;
33
12
 
34
- // assertField(name, value, E, code) -> value | throws new E(code, ...)
35
- // name: RFC 5322 ftext, printable ASCII [0x21, 0x7e] except ':' (0x3a), non-empty
36
- // (a space, control byte, or ':' in a field name breaks the "Name: value" grammar).
37
- // value: reject CR (0x0d), LF (0x0a) and NUL (0x00), the injection bytes; TAB
38
- // (0x09, folding whitespace) and printable non-ASCII (a UTF-8 header value, RFC
39
- // 6532) pass. E is the caller's typed-error CLASS, thrown `new E(code, message)`
40
- // -- the same convention lib/mime.js (this guard's caller) and guard.bytes/text use.
41
13
  // @enforced-by behavioral -- the CR/LF/NUL value reject + the ftext name check have
42
- // no rename-proof code shape distinct from the guard-name control-byte loop / the
43
- // ASN.1 charset readers; the header-injection RED vectors (a CR / LF / NUL value
44
- // or a non-ftext field name rejects smime/bad-header) are the guard.
45
14
  function assertField(name, value, E, code) {
46
15
  if (typeof name !== "string" || name.length === 0) throw new E(code, "a header field name must be a non-empty string");
47
16
  for (var i = 0; i < name.length; i++) {
@@ -53,14 +22,10 @@ function assertField(name, value, E, code) {
53
22
  var vc = _charCodeAt(v, j);
54
23
  if (vc === 0x00 || vc === 0x0d || vc === 0x0a) throw new E(code, "a header field value must not contain CR / LF / NUL (header injection) in " + _stringify(name));
55
24
  }
56
- // RFC 5322 sec. 2.1.1: the serialized "Name: value" line (UTF-8 octets, excluding CRLF) must not exceed
57
- // the line limit, or a relay may re-fold it and silently change the signed bytes of a protected header.
58
25
  if (_byteLength(name, "utf8") + 2 + _byteLength(v, "utf8") > C.LIMITS.HEADER_LINE_MAX_OCTETS) {
59
26
  throw new E(code, "a header field line exceeds RFC 5322's " + C.LIMITS.HEADER_LINE_MAX_OCTETS + "-octet limit: " + _stringify(name));
60
27
  }
61
28
  return v;
62
29
  }
63
30
 
64
- // Frozen for the reason the family header gives: a boundary reads its guard off this object at the
65
- // call, so a writable export would let a caller replace the check rather than pass it.
66
31
  module.exports = _freeze({ assertField: assertField });