@blamejs/pki 0.6.14 → 0.6.16
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 +27 -1
- package/lib/acme.js +59 -815
- package/lib/asn1-der.js +7 -245
- package/lib/attrcert-sign.js +12 -260
- package/lib/attrcert-verify.js +1 -7
- package/lib/byte-reader.js +0 -8
- package/lib/byte-writer.js +0 -11
- package/lib/cbor-det.js +0 -54
- package/lib/cmc-build.js +15 -357
- package/lib/cmc-verify.js +6 -239
- package/lib/cmp-build.js +41 -317
- package/lib/cmp-session.js +73 -760
- package/lib/cmp-verify.js +32 -488
- package/lib/cms-compress.js +3 -32
- package/lib/cms-decrypt.js +29 -294
- package/lib/cms-encrypt.js +14 -234
- package/lib/cms-sign.js +29 -324
- package/lib/cms-verify.js +9 -500
- package/lib/composite-kem.js +2 -102
- package/lib/composite-sig.js +1 -77
- package/lib/constants.js +0 -249
- package/lib/crl-sign.js +9 -237
- package/lib/crl-verify.js +1 -10
- package/lib/crmf-sign.js +19 -198
- package/lib/crmf-verify.js +1 -12
- package/lib/csr-sign.js +3 -34
- package/lib/csr-verify.js +1 -12
- package/lib/ct.js +28 -253
- package/lib/edwards-point.js +10 -44
- package/lib/est.js +28 -764
- package/lib/framework-error.js +4 -182
- package/lib/guard-all.js +1 -58
- package/lib/guard-async.js +1 -36
- package/lib/guard-bytes.js +6 -650
- package/lib/guard-compress.js +5 -102
- package/lib/guard-crypto.js +1 -40
- package/lib/guard-der.js +2 -93
- package/lib/guard-encoding.js +2 -60
- package/lib/guard-header.js +1 -36
- package/lib/guard-identifier.js +7 -531
- package/lib/guard-intrinsic.js +1 -150
- package/lib/guard-json.js +10 -73
- package/lib/guard-limits.js +1 -77
- package/lib/guard-list.js +1 -49
- package/lib/guard-name.js +3 -209
- package/lib/guard-parsed.js +7 -335
- package/lib/guard-range.js +1 -71
- package/lib/guard-secret.js +1 -102
- package/lib/guard-text.js +1 -64
- package/lib/guard-time.js +1 -97
- package/lib/hpke.js +3 -105
- package/lib/http-digest.js +14 -142
- package/lib/http-retry-after.js +1 -47
- package/lib/http-transport.js +21 -131
- package/lib/inspect.js +30 -284
- package/lib/ip-utils.js +6 -47
- package/lib/jose.js +7 -142
- package/lib/key.js +2 -67
- package/lib/lint.js +9 -194
- package/lib/merkle.js +1 -27
- package/lib/mime.js +9 -82
- package/lib/ocsp-request-verify.js +1 -14
- package/lib/ocsp-verify.js +1 -82
- package/lib/ocsp.js +11 -169
- package/lib/oid.js +5 -337
- package/lib/path-validate.js +103 -1492
- package/lib/pbes2.js +2 -89
- package/lib/pkcs12-build.js +31 -379
- package/lib/pki-build.js +64 -290
- package/lib/rc2.js +2 -18
- package/lib/rfc3339.js +4 -27
- package/lib/scep.js +140 -286
- package/lib/schema-all.js +0 -122
- package/lib/schema-attrcert.js +4 -146
- package/lib/schema-c509.js +153 -802
- package/lib/schema-cmc.js +5 -220
- package/lib/schema-cmp.js +2 -223
- package/lib/schema-cms.js +13 -427
- package/lib/schema-crl.js +5 -68
- package/lib/schema-crmf.js +6 -167
- package/lib/schema-csr.js +1 -54
- package/lib/schema-csrattrs.js +2 -87
- package/lib/schema-engine.js +0 -152
- package/lib/schema-ocsp.js +0 -117
- package/lib/schema-pkcs12.js +2 -93
- package/lib/schema-pkcs8.js +3 -35
- package/lib/schema-pkix.js +24 -655
- package/lib/schema-smime.js +0 -54
- package/lib/schema-tsp.js +3 -99
- package/lib/schema-x509.js +4 -63
- package/lib/shbs.js +11 -95
- package/lib/sign-scheme.js +2 -97
- package/lib/sigstore.js +9 -169
- package/lib/sleep.js +2 -8
- package/lib/smime.js +15 -408
- package/lib/tls-cert-compress.js +0 -62
- package/lib/trust.js +19 -185
- package/lib/tsp-sign.js +16 -270
- package/lib/validator-all.js +1 -30
- package/lib/validator-attcert.js +2 -46
- package/lib/validator-cose.js +2 -153
- package/lib/validator-keydesc.js +1 -37
- package/lib/validator-sig.js +1 -46
- package/lib/validator-tls.js +1 -88
- package/lib/validator-tpm.js +19 -122
- package/lib/verify-seam.js +1 -24
- package/lib/webauthn-mds.js +3 -400
- package/lib/webauthn.js +12 -833
- package/lib/webcrypto.js +5 -485
- package/lib/x509-sign.js +4 -108
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/schema-pkix.js
CHANGED
|
@@ -1,32 +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
|
-
// parsers that compose these factories (pki.schema.x509, pki.schema.crl, ...).
|
|
7
|
-
//
|
|
8
|
-
// Shared PKIX structure-schema factories (RFC 5280). Each is a namespace-
|
|
9
|
-
// parameterized FACTORY: given an error namespace `ns` ({ prefix, E, oid }) it
|
|
10
|
-
// returns an asn1-schema that walks the corresponding ASN.1 structure and emits
|
|
11
|
-
// the caller's own <prefix>/* error codes. x509.js, crl.js, and future CMS/CSR
|
|
12
|
-
// parsers compose these so AlgorithmIdentifier / Name / Extension are defined
|
|
13
|
-
// once, not re-derived per format. This module is internal infrastructure: the
|
|
14
|
-
// operator-facing surface is the parsers that consume it.
|
|
4
|
+
// @internal
|
|
15
5
|
|
|
16
6
|
var asn1 = require("./asn1-der");
|
|
17
7
|
var constants = require("./constants");
|
|
18
8
|
var schema = require("./schema-engine");
|
|
19
9
|
var guard = require("./guard-all");
|
|
20
10
|
var intrinsic = require("./guard-intrinsic");
|
|
21
|
-
// Own-key membership through an operation taken at load. Written out, the question reads two
|
|
22
|
-
// replaceable properties to answer one thing, and here it decides which version numbers a format
|
|
23
|
-
// accepts -- shared by every format module, so one admitted name widens all of them at once.
|
|
24
11
|
var _hasOwn = intrinsic.hasOwn;
|
|
25
|
-
// The rest of the operations this module decides with, taken at load for the same reason. What
|
|
26
|
-
// these factories produce is what a consumer compares an identity against -- a certificate's
|
|
27
|
-
// subject alternative names against an enrollment's own, a distinguished name against an issuer's
|
|
28
|
-
// -- so a name that traversal, folding or a byte copy invents here is a name the encoded structure
|
|
29
|
-
// never carried, and the comparison downstream still answers true.
|
|
30
12
|
var _forEach = intrinsic.forEach;
|
|
31
13
|
var _map = intrinsic.map;
|
|
32
14
|
var _push = intrinsic.push;
|
|
@@ -53,10 +35,6 @@ var _WeakMapI = intrinsic.WeakMap;
|
|
|
53
35
|
var _arrayFrom = Array.from;
|
|
54
36
|
var _getUTCFullYear = intrinsic.uncurry(Date.prototype.getUTCFullYear);
|
|
55
37
|
|
|
56
|
-
// The JavaScript regex `\s` class, decided by code point, so the whitespace tests below match
|
|
57
|
-
// exactly what `/\s/` and `/\S/` matched with no caller-replaceable match protocol standing in for
|
|
58
|
-
// them. Set = ECMAScript WhiteSpace + LineTerminator: TAB LF VT FF CR SP, U+00A0, U+1680,
|
|
59
|
-
// U+2000..U+200A, U+2028, U+2029, U+202F, U+205F, U+3000, U+FEFF.
|
|
60
38
|
function _isJsWhitespace(c) {
|
|
61
39
|
return c === 0x09 || c === 0x0a || c === 0x0b || c === 0x0c || c === 0x0d || c === 0x20 ||
|
|
62
40
|
c === 0xa0 || c === 0x1680 || (c >= 0x2000 && c <= 0x200a) || c === 0x2028 || c === 0x2029 ||
|
|
@@ -70,33 +48,18 @@ function _hasNonWhitespace(s) {
|
|
|
70
48
|
for (var i = 0; i < s.length; i++) { if (!_isJsWhitespace(_charCodeAt(s, i))) return true; }
|
|
71
49
|
return false;
|
|
72
50
|
}
|
|
73
|
-
// Charset validation by character scan (the shared home for the `/^[chars]+$/`-family regexes format
|
|
74
|
-
// modules used to carry): `charTable` builds a code-point membership table from a string of the allowed
|
|
75
|
-
// characters, and `allCharsIn` checks that a NON-EMPTY string is composed only of them (`+`, not `*`).
|
|
76
|
-
// One data-table loop, not a per-charset inlined character-class chain.
|
|
77
51
|
function _charTable(chars) { var t = []; for (var i = 0; i < chars.length; i++) t[_charCodeAt(chars, i)] = true; return t; }
|
|
78
52
|
function _allCharsIn(s, table) {
|
|
79
53
|
if (s.length === 0) return false;
|
|
80
54
|
for (var i = 0; i < s.length; i++) { if (!table[_charCodeAt(s, i)]) return false; }
|
|
81
55
|
return true;
|
|
82
56
|
}
|
|
83
|
-
// ct is a leaf format module (requires only the codec / oid / constants / error
|
|
84
|
-
// core, never pkix) so the SCT-list extension VALUE decoder registers here as a
|
|
85
|
-
// registry row, not a switch. The one-directional edge keeps the layering acyclic.
|
|
86
57
|
var ct = require("./ct");
|
|
87
58
|
|
|
88
|
-
// PEM armor markers, matched literally so no regex -- with its live, replaceable match protocol and
|
|
89
|
-
// the backreference the END repeats -- stands between the parser and the wire.
|
|
90
59
|
var _PEM_BEGIN = "-----BEGIN ";
|
|
91
60
|
var _PEM_END = "-----END ";
|
|
92
61
|
var _PEM_DASHES = "-----";
|
|
93
62
|
|
|
94
|
-
// The next PEM block at or after `from`: "-----BEGIN <label>-----<body>-----END <label>-----" where
|
|
95
|
-
// the label is one or more of [A-Z0-9 ] (RFC 7468 label octets, spaces included) and the END label
|
|
96
|
-
// repeats the BEGIN label exactly (the backreference). Returns { index, label, body, end } -- `index`
|
|
97
|
-
// the BEGIN offset, `end` the offset past the closing dashes -- or null when no such block remains.
|
|
98
|
-
// A "-----BEGIN " with no valid label or no matching END is skipped, mirroring the regex trying each
|
|
99
|
-
// start position; the body is the shortest span to the first matching END (the lazy quantifier).
|
|
100
63
|
function _findPemBlock(text, from) {
|
|
101
64
|
var searchFrom = from;
|
|
102
65
|
for (;;) {
|
|
@@ -105,7 +68,7 @@ function _findPemBlock(text, from) {
|
|
|
105
68
|
var p = bi + _PEM_BEGIN.length, ls = p;
|
|
106
69
|
while (p < text.length) {
|
|
107
70
|
var c = _charCodeAt(text, p);
|
|
108
|
-
if (!((c >= 65 && c <= 90) || (c >= 48 && c <= 57) || c === 32)) break;
|
|
71
|
+
if (!((c >= 65 && c <= 90) || (c >= 48 && c <= 57) || c === 32)) break;
|
|
109
72
|
p += 1;
|
|
110
73
|
}
|
|
111
74
|
if (p === ls || _strSlice(text, p, p + 5) !== _PEM_DASHES) { searchFrom = bi + 1; continue; }
|
|
@@ -118,7 +81,6 @@ function _findPemBlock(text, from) {
|
|
|
118
81
|
}
|
|
119
82
|
}
|
|
120
83
|
|
|
121
|
-
// All ASCII CR / LF / TAB / SPACE removed (the base64 body's permitted layout whitespace).
|
|
122
84
|
function _stripB64Whitespace(s) {
|
|
123
85
|
var out = "", runStart = 0, i;
|
|
124
86
|
for (i = 0; i < s.length; i++) {
|
|
@@ -132,8 +94,6 @@ function _stripB64Whitespace(s) {
|
|
|
132
94
|
return out;
|
|
133
95
|
}
|
|
134
96
|
|
|
135
|
-
// A base64 string wrapped to 64-column lines joined by LF, no trailing LF (matching (.{64})->"$1\n"
|
|
136
|
-
// then a trailing-newline strip; base64 output carries no newline of its own to strip).
|
|
137
97
|
function _wrap64(s) {
|
|
138
98
|
var out = "";
|
|
139
99
|
for (var i = 0; i < s.length; i += 64) {
|
|
@@ -143,22 +103,8 @@ function _wrap64(s) {
|
|
|
143
103
|
return out;
|
|
144
104
|
}
|
|
145
105
|
|
|
146
|
-
|
|
147
|
-
// Input handling, PEM unwrapping (with the size cap), DER-decode wrapping, and
|
|
148
|
-
// the walk are defined ONCE here so no format can diverge on a guard. Each
|
|
149
|
-
// format supplies only its labels, error class, code prefix, and top schema.
|
|
150
|
-
|
|
151
|
-
// pemDecode(text, label, PemError): `label` (when truthy) is enforced, else the
|
|
152
|
-
// first block is taken. Applies the LIMITS.PEM_MAX_BYTES cap before scanning.
|
|
153
|
-
// The body must be CANONICAL base64 (RFC 4648 sec. 3.5): whole 4-character
|
|
154
|
-
// groups, no stray padding, zero trailing bits in the final symbol. Node's
|
|
155
|
-
// lenient Buffer.from would otherwise let several distinct PEM texts alias one
|
|
156
|
-
// DER (PEM-layer malleability) and silently drop trailing content bits.
|
|
106
|
+
|
|
157
107
|
function pemDecode(text, label, PemError) {
|
|
158
|
-
// guard.text.decode caps the raw byte length before the latin1 string copy:
|
|
159
|
-
// converting first would allocate a full-size string for input the cap is
|
|
160
|
-
// about to reject, and a buffer above Node's max string length would escape as
|
|
161
|
-
// an untyped ERR_STRING_TOO_LONG instead of pem/too-large.
|
|
162
108
|
text = guard.text.decode(text, constants.LIMITS.PEM_MAX_BYTES, PemError, {
|
|
163
109
|
charset: "latin1", tooLarge: "pem/too-large", badInput: "pem/bad-input", label: "PEM input",
|
|
164
110
|
});
|
|
@@ -166,16 +112,9 @@ function pemDecode(text, label, PemError) {
|
|
|
166
112
|
if (!m) throw new PemError("pem/no-block", "no PEM block found");
|
|
167
113
|
if (label && m.label !== label) throw new PemError("pem/label-mismatch", "expected " + _stringify(label) + " block, got " + _stringify(m.label));
|
|
168
114
|
var b64 = _stripB64Whitespace(m.body);
|
|
169
|
-
// Strict canonical base64 (RFC 4648 sec. 3.5) via the shared encoding guard.
|
|
170
115
|
return guard.encoding.base64(b64, null, function (c, msg) { return new PemError(c, msg); }, "pem/bad-base64", "PEM base64 body");
|
|
171
116
|
}
|
|
172
117
|
|
|
173
|
-
// pemDecodeAll(text, label, PemError): decode every PEM block under the strict
|
|
174
|
-
// RFC 7468 profile for application/pem-certificate-chain (RFC 8555 sec. 9.1) --
|
|
175
|
-
// each block must carry `label` (default CERTIFICATE), explanatory text MUST NOT
|
|
176
|
-
// appear before/between/after blocks (errata 5983: certchain = stricttextualmsg
|
|
177
|
-
// *(eol stricttextualmsg)), and at least one block is required. Every block's
|
|
178
|
-
// base64 body is canonical (the pemDecode rule); returns an array of DER buffers.
|
|
179
118
|
function pemDecodeAll(text, label, PemError) {
|
|
180
119
|
label = label || "CERTIFICATE";
|
|
181
120
|
text = guard.text.decode(text, constants.LIMITS.PEM_MAX_BYTES, PemError, {
|
|
@@ -195,14 +134,6 @@ function pemDecodeAll(text, label, PemError) {
|
|
|
195
134
|
return blocks;
|
|
196
135
|
}
|
|
197
136
|
|
|
198
|
-
// The label must be re-readable by this module's own PEM label scan (uppercase
|
|
199
|
-
// alphanumeric words separated by single spaces, a subset of the RFC 7468
|
|
200
|
-
// sec. 3 label grammar). Anything else (lowercase, a leading/trailing space,
|
|
201
|
-
// an embedded "-----" or newline) would emit armor pemDecode itself rejects
|
|
202
|
-
// or corrupt the armor grammar: a config-time authoring fault, thrown here.
|
|
203
|
-
// The armor label grammar, decided character by character (no regex protocol on the emit path):
|
|
204
|
-
// one or more A-Z0-9 words joined by single spaces -- so no leading/trailing space and no doubled
|
|
205
|
-
// space, and every other character in [A-Z0-9].
|
|
206
137
|
function _isPemLabel(s) {
|
|
207
138
|
var n = s.length;
|
|
208
139
|
if (n === 0) return false;
|
|
@@ -221,16 +152,11 @@ function pemEncode(der, label, PemError) {
|
|
|
221
152
|
if (typeof label !== "string" || !_isPemLabel(label)) {
|
|
222
153
|
throw new PemError("pem/bad-label", "pemEncode requires an uppercase A-Z0-9 label with single spaces");
|
|
223
154
|
}
|
|
224
|
-
// Re-view through the byte guard: a string input would silently utf8-armor into
|
|
225
|
-
// a bogus PEM, and a detached-backed Buffer would armor an empty body; both
|
|
226
|
-
// fail closed (a Uint8Array / Buffer of real DER re-views cleanly).
|
|
227
155
|
var buf = guard.bytes.view(der, PemError, "pem/bad-input", "pemEncode DER input");
|
|
228
156
|
var b64 = _wrap64(_bufToString(buf, "base64"));
|
|
229
157
|
return "-----BEGIN " + label + "-----\n" + b64 + "\n-----END " + label + "-----\n";
|
|
230
158
|
}
|
|
231
159
|
|
|
232
|
-
// Only the base64 alphabet (A-Z a-z 0-9 + / =) kept, every other character dropped -- the lenient
|
|
233
|
-
// filter of the historic signer-certificate path, by character scan.
|
|
234
160
|
function _keepBase64(s) {
|
|
235
161
|
var out = "", runStart = 0, i;
|
|
236
162
|
for (i = 0; i < s.length; i++) {
|
|
@@ -244,11 +170,7 @@ function _keepBase64(s) {
|
|
|
244
170
|
return out;
|
|
245
171
|
}
|
|
246
172
|
|
|
247
|
-
//
|
|
248
|
-
// distinct from the strict canonical `pemDecode` above): the body of the first
|
|
249
|
-
// "-----BEGIN <label>-----...-----END <label>-----" block with any non-base64 character stripped,
|
|
250
|
-
// decoded through Node's lenient base64. Returns the DER Buffer, or null when no such block exists so
|
|
251
|
-
// the caller keeps its own domain error. NOT for verified input -- pemDecode is the fail-closed door.
|
|
173
|
+
// @internal
|
|
252
174
|
function pemDecodeLenient(text, label) {
|
|
253
175
|
var begin = _PEM_BEGIN + label + _PEM_DASHES;
|
|
254
176
|
var end = _PEM_END + label + _PEM_DASHES;
|
|
@@ -260,27 +182,15 @@ function pemDecodeLenient(text, label) {
|
|
|
260
182
|
return _bufferFrom(_keepBase64(_strSlice(text, bodyStart, ei)), "base64");
|
|
261
183
|
}
|
|
262
184
|
|
|
263
|
-
// Coerce parse input to DER bytes. A PEM string OR a PEM Buffer (a .pem file
|
|
264
|
-
// read with fs.readFileSync) is unwrapped with opts.pemLabel; a DER Buffer or
|
|
265
|
-
// Uint8Array is taken as bytes. opts: { pemLabel, PemError, ErrorClass, prefix }.
|
|
266
185
|
function coerceToDer(input, opts) {
|
|
267
186
|
if (typeof input === "string") return pemDecode(input, opts.pemLabel, opts.PemError);
|
|
268
187
|
if (guard.bytes.isByteSource(input)) {
|
|
269
|
-
// Route any DER BufferSource (Buffer, typed array, DataView, or ArrayBuffer) through the shared byte
|
|
270
|
-
// guard so a detached backing store fails closed here for every format that composes this boundary
|
|
271
|
-
// (x509/crl/csr/pkcs8/cms/pkcs12), not per-format, and its byte view is read from the slot -- a
|
|
272
|
-
// cross-realm ArrayBuffer or an offset DataView reaches the decoder exactly as a Buffer does.
|
|
273
188
|
var buf = guard.bytes.source(input, opts.ErrorClass, opts.prefix + "/bad-input", "parse");
|
|
274
189
|
return _isPemArmor(buf) ? pemDecode(buf, opts.pemLabel, opts.PemError) : buf;
|
|
275
190
|
}
|
|
276
191
|
throw new opts.ErrorClass(opts.prefix + "/bad-input", "parse expects a DER BufferSource (Buffer, typed array, DataView, or ArrayBuffer) or a PEM string");
|
|
277
192
|
}
|
|
278
193
|
|
|
279
|
-
// Does a Buffer carry PEM armor (a .pem read with fs.readFileSync) instead of
|
|
280
|
-
// raw DER? It does iff "-----BEGIN" appears and everything before it is TEXT
|
|
281
|
-
// (UTF-8 BOM / whitespace / RFC 7468 explanatory preamble). DER is binary and its
|
|
282
|
-
// leading tag+length bytes are non-printable, so a non-SEQUENCE DER (a bare
|
|
283
|
-
// SET / INTEGER) is not misrouted here; it decodes and fails closed structurally.
|
|
284
194
|
function _isPemArmor(buf) {
|
|
285
195
|
var head = _bufToString(_subarray(buf, 0, 4096), "latin1");
|
|
286
196
|
var idx = _strIndexOf(head, "-----BEGIN");
|
|
@@ -288,56 +198,29 @@ function _isPemArmor(buf) {
|
|
|
288
198
|
for (var i = 0; i < idx; i++) {
|
|
289
199
|
var c = buf[i];
|
|
290
200
|
var textByte = (c >= 0x20 && c < 0x7f) || c === 0x09 || c === 0x0a || c === 0x0d ||
|
|
291
|
-
c === 0xef || c === 0xbb || c === 0xbf;
|
|
201
|
+
c === 0xef || c === 0xbb || c === 0xbf;
|
|
292
202
|
if (!textByte) return false;
|
|
293
203
|
}
|
|
294
204
|
return true;
|
|
295
205
|
}
|
|
296
206
|
|
|
297
|
-
// Decode the DER root, wrapping a codec fault in the caller's <prefix>/bad-der.
|
|
298
207
|
function decodeRoot(der, opts) {
|
|
299
|
-
// A format whose wire encoding is normatively BER (RFC 7292 PKCS#12) opts
|
|
300
|
-
// in to the codec's ber mode for the whole decode. BER-vs-DER divergence is
|
|
301
|
-
// not always a decode-time throw: a definite-length constructed IMPLICIT
|
|
302
|
-
// OCTET STRING decodes strictly and only diverges at the typed reader, so
|
|
303
|
-
// a strict-first-retry-on-throw boundary would miss legal BER shapes. A
|
|
304
|
-
// strict-DER input decodes identically under the ber mode, every other
|
|
305
|
-
// strictness verdict included.
|
|
306
208
|
try { return asn1.decode(der, opts.ber ? { ber: true } : undefined); }
|
|
307
209
|
catch (e) {
|
|
308
210
|
throw new opts.ErrorClass(opts.prefix + "/bad-der", (opts.what || "input") + " DER did not decode: " + ((e && e.message) || _String(e)), e);
|
|
309
211
|
}
|
|
310
212
|
}
|
|
311
213
|
|
|
312
|
-
// The shared parse entry: coerce -> decode -> walk the top schema. A format's
|
|
313
|
-
// parse() is one call to this; the guard-parity bug class (a new format not
|
|
314
|
-
// mirroring an existing format's input handling) is structurally impossible.
|
|
315
214
|
function runParse(input, opts) {
|
|
316
215
|
return schema.walk(opts.topSchema, decodeRoot(coerceToDer(input, opts), opts), opts.ns).result;
|
|
317
216
|
}
|
|
318
217
|
|
|
319
|
-
// Distinguished-name attribute short labels (RFC 4514 sec. 3 + common use).
|
|
320
|
-
// RFC 4514 short names for the DN string representation; the single source is
|
|
321
|
-
// pki.C.NAMES.DN_SHORT (shared with the human-readable renderer so the two can't drift).
|
|
322
218
|
var DN_SHORT = constants.NAMES.DN_SHORT;
|
|
323
219
|
|
|
324
|
-
// AlgorithmIdentifier ::= SEQUENCE { algorithm OBJECT IDENTIFIER, parameters ANY OPTIONAL }
|
|
325
|
-
// The error/OID namespace every format module walks its schema under:
|
|
326
|
-
// { prefix, E:(code,message,cause)=>new ErrorClass(...), oid }. Factored so a
|
|
327
|
-
// format declares it in one line instead of repeating the error-constructor
|
|
328
|
-
// closure (a caller that never passes a cause is unaffected, since withCause ignores
|
|
329
|
-
// an undefined third arg).
|
|
330
220
|
function makeNS(prefix, ErrorClass, oidModule) {
|
|
331
221
|
return { prefix: prefix, E: function (code, message, cause) { return new ErrorClass(code, message, cause); }, oid: oidModule };
|
|
332
222
|
}
|
|
333
223
|
|
|
334
|
-
// A bounded universal-INTEGER version reader. `accept` maps each legal wire value
|
|
335
|
-
// (as a decimal string) to its surfaced version number; any other value is a
|
|
336
|
-
// <prefix>/bad-version fault. The one genuine per-format divergence, the cert
|
|
337
|
-
// rejects 0 and maps 1->2/2->3, a CRL accepts only 1->2, a CSR only 0->1, a PKCS#8
|
|
338
|
-
// 0->1/1->2, is expressed purely as the accept map (RFC 5280 sec. 4.1.2.1 / sec. 5.1.2.1,
|
|
339
|
-
// RFC 2986 sec. 4.1, RFC 5958 sec. 2). read.integer is strict, so an ENUMERATED-tagged
|
|
340
|
-
// version is rejected at the leaf (asn1/*).
|
|
341
224
|
function versionReader(ns, accept) {
|
|
342
225
|
return schema.decode(function (n) {
|
|
343
226
|
var key = _bigIntToString(asn1.read.integer(n));
|
|
@@ -346,22 +229,6 @@ function versionReader(ns, accept) {
|
|
|
346
229
|
});
|
|
347
230
|
}
|
|
348
231
|
|
|
349
|
-
// opts.implicitTag (optional): read the AlgorithmIdentifier as a [tag] IMPLICIT
|
|
350
|
-
// SEQUENCE (a context-class constructed node whose children are algorithm + parameters),
|
|
351
|
-
// for the RFC 5652 sec. 6.2.4 pwri.keyDerivationAlgorithm [0]. With no opts the shape is a
|
|
352
|
-
// universal SEQUENCE, byte-identical to every existing caller.
|
|
353
|
-
//
|
|
354
|
-
// Parameter-rule tiering: only the params-MUST-BE-ABSENT families (ML-DSA,
|
|
355
|
-
// SLH-DSA, the Edwards/Montgomery curves), new registrations with no legacy
|
|
356
|
-
// corpus) are enforced at parse time, in the build below. The CLASSICAL
|
|
357
|
-
// per-OID rules (RSA parameters MUST be NULL, RFC 3279 sec. 2.2.1/2.3.1;
|
|
358
|
-
// ecdsa-with-SHA2 parameters MUST be omitted, RFC 5758 sec. 3.2) are
|
|
359
|
-
// deliberately not parse-time rejects: a long tail of deployed certificates
|
|
360
|
-
// omits the RSA NULL, so the parse tier stays interop-lenient for those
|
|
361
|
-
// families and the strict classical check runs on the signature-verification
|
|
362
|
-
// path (path-validate's signature-algorithm registry), where the verdict
|
|
363
|
-
// matters. SPKI key AlgorithmIdentifiers follow the same tiering: lenient at
|
|
364
|
-
// parse, validated by the key-import layer that consumes them.
|
|
365
232
|
function algorithmIdentifier(ns, opts) {
|
|
366
233
|
opts = opts || {};
|
|
367
234
|
return schema.seq([
|
|
@@ -372,13 +239,6 @@ function algorithmIdentifier(ns, opts) {
|
|
|
372
239
|
arity: { min: 1 }, code: ns.prefix + "/bad-algorithm-identifier", what: "AlgorithmIdentifier",
|
|
373
240
|
build: function (m, ctx) {
|
|
374
241
|
var dotted = m.fields.algorithm.value;
|
|
375
|
-
// The registry marks several algorithm families parameters-absent: the PQC
|
|
376
|
-
// signature families (ML-DSA RFC 9881, SLH-DSA RFC 9909/9814), the
|
|
377
|
-
// Edwards/Montgomery curves (RFC 8410), ML-KEM (RFC 9936), and the HKDF
|
|
378
|
-
// identifiers (RFC 8619). For all of them the `parameters` field MUST be
|
|
379
|
-
// absent: no explicit NULL, no bytes. Enforced ONCE here so every consumer of the
|
|
380
|
-
// shared AlgorithmIdentifier inherits the rule; the guard-parity bug
|
|
381
|
-
// class is structurally impossible.
|
|
382
242
|
if (m.fields.parameters.present && ctx.oid.paramsMustBeAbsent(dotted)) {
|
|
383
243
|
throw ctx.E(ctx.prefix + "/bad-algorithm-parameters",
|
|
384
244
|
"the " + (ctx.oid.name(dotted) || dotted) + " AlgorithmIdentifier parameters field MUST be absent");
|
|
@@ -388,10 +248,6 @@ function algorithmIdentifier(ns, opts) {
|
|
|
388
248
|
});
|
|
389
249
|
}
|
|
390
250
|
|
|
391
|
-
// pbkdf2Params(ns): PBKDF2-params (RFC 8018 sec. 5.2) constrained to the RFC 9579 PBMAC1 profile, where the
|
|
392
|
-
// salt uses the OCTET STRING choice and keyLength MUST be present (a MacData / PKIProtection consumer
|
|
393
|
-
// cannot infer the derived MAC key size, RFC 9579 sec. 4.b/5). Shared by the PKCS#12 MacData reader and
|
|
394
|
-
// the CMP PBMAC1 protection reader: one ns-parameterized decoder so neither format re-derives the shape.
|
|
395
251
|
function pbkdf2Params(ns) {
|
|
396
252
|
return schema.seq([
|
|
397
253
|
schema.field("salt", schema.octetString()),
|
|
@@ -405,28 +261,13 @@ function pbkdf2Params(ns) {
|
|
|
405
261
|
if (!m.fields.keyLength.present) {
|
|
406
262
|
throw ctx.E(ctx.prefix + "/bad-mac-data", "PBMAC1 PBKDF2-params must carry keyLength (RFC 9579 sec. 5)");
|
|
407
263
|
}
|
|
408
|
-
// guard.range.positiveInt31 bounds + narrows each counter atomically, so a value past the bound
|
|
409
|
-
// would round silently and hand a verifier wrong inputs.
|
|
410
264
|
var iterationCount = guard.range.positiveInt31(m.fields.iterationCount.value, ctx.E, ctx.prefix + "/bad-mac-data", "PBKDF2 iterationCount");
|
|
411
265
|
var keyLength = guard.range.positiveInt31(m.fields.keyLength.value, ctx.E, ctx.prefix + "/bad-mac-data", "PBKDF2 keyLength");
|
|
412
266
|
var prf = m.fields.prf.present ? m.fields.prf.value.result : null;
|
|
413
|
-
// X.690 sec. 11.5: the prf DEFAULT is algid-hmacWithSHA1 (hmacWithSHA1 with NULL parameters,
|
|
414
|
-
// i.e. the 2-byte DER 05 00, RFC 8018 sec. 5.2); an explicit prf byte-equal to that default is
|
|
415
|
-
// non-canonical and rejects. hmacWithSHA1 with ABSENT parameters is a different value and decodes.
|
|
416
|
-
// This is a public structural check on the algorithm parameters, not a secret compare, so a
|
|
417
|
-
// direct byte test on the (fixed 2-octet) NULL encoding, not a timing-safe comparison.
|
|
418
267
|
var pp = prf ? prf.parameters : null;
|
|
419
|
-
// pp is the public algorithm-identifier parameters field, a fixed 2-octet NULL encoding.
|
|
420
|
-
// No MAC, tag or secret is involved, so a timing-safe compare has nothing to protect.
|
|
421
|
-
// nosemgrep: pki-non-constant-time-secret-compare
|
|
422
268
|
if (prf && prf.oid === hmacSha1 && pp !== null && pp.length === 2 && pp[0] === 0x05 && pp[1] === 0x00) {
|
|
423
269
|
throw ctx.E(ctx.prefix + "/bad-mac-data", "a PBKDF2 prf equal to its DEFAULT algid-hmacWithSHA1 must be omitted (X.690 sec. 11.5, RFC 8018 sec. 5.2)");
|
|
424
270
|
}
|
|
425
|
-
// The PBKDF2 prf HMAC AlgorithmIdentifier likewise carries NULL (or absent) parameters (RFC 8018 App. B.1):
|
|
426
|
-
// reject a present-but-non-NULL prf parameter (e.g. an INTEGER) instead of discarding it, since the prf
|
|
427
|
-
// hash is dispatched by OID alone. (An absent prf, and hmacWithSHA1 with absent params, remain valid above.)
|
|
428
|
-
// pp is the public algorithm-identifier parameters node.
|
|
429
|
-
// nosemgrep: pki-non-constant-time-secret-compare
|
|
430
271
|
if (prf && pp !== null && !(pp.length === 2 && pp[0] === 0x05 && pp[1] === 0x00)) {
|
|
431
272
|
throw ctx.E(ctx.prefix + "/bad-mac-data", "the PBKDF2 prf parameters must be absent or NULL (RFC 8018 App. B.1)");
|
|
432
273
|
}
|
|
@@ -441,11 +282,6 @@ function pbkdf2Params(ns) {
|
|
|
441
282
|
});
|
|
442
283
|
}
|
|
443
284
|
|
|
444
|
-
// pbmac1Params(ns): PBMAC1-params ::= SEQUENCE { keyDerivationFunc AlgorithmIdentifier{PBKDF2},
|
|
445
|
-
// messageAuthScheme AlgorithmIdentifier } (RFC 8018 App. A.5 / RFC 9579 sec. 4). The PBKDF2 prf and the
|
|
446
|
-
// messageAuthScheme HMAC are INDEPENDENT (a SHA-512 prf with a SHA-256 HMAC is legal). Shared by PKCS#12
|
|
447
|
-
// MacData and CMP PBMAC1 protection; returns { kdf: { salt, iterationCount, keyLength, prfOid, prfName },
|
|
448
|
-
// schemeOid, schemeName }.
|
|
449
285
|
function pbmac1Params(ns) {
|
|
450
286
|
return schema.seq([
|
|
451
287
|
schema.field("keyDerivationFunc", schema.seq([
|
|
@@ -461,14 +297,7 @@ function pbmac1Params(ns) {
|
|
|
461
297
|
throw ctx.E(ctx.prefix + "/bad-mac-data", "PBMAC1 keyDerivationFunc must be PBKDF2 (RFC 9579 sec. 4)");
|
|
462
298
|
}
|
|
463
299
|
var scheme = m.fields.messageAuthScheme.value.result;
|
|
464
|
-
// RFC 8018 App. B.1: an HMAC messageAuthScheme AlgorithmIdentifier carries NULL (or absent) parameters,
|
|
465
|
-
// matching the builder (_hmacAlgId emits the 2-byte 05 00). Any other parameter encoding is
|
|
466
|
-
// rejected: _verifyMac dispatches by the scheme OID alone, so a mismatched / malformed
|
|
467
|
-
// parameter (e.g. an INTEGER) must not slip through unvalidated. A direct byte test on the fixed 2-octet
|
|
468
|
-
// NULL encoding of the PUBLIC parameters, not a secret compare.
|
|
469
300
|
var sp = scheme.parameters;
|
|
470
|
-
// sp is the public algorithm-identifier parameters node.
|
|
471
|
-
// nosemgrep: pki-non-constant-time-secret-compare
|
|
472
301
|
if (sp !== null && !(sp.length === 2 && sp[0] === 0x05 && sp[1] === 0x00)) {
|
|
473
302
|
throw ctx.E(ctx.prefix + "/bad-mac-data", "the PBMAC1 messageAuthScheme parameters must be absent or NULL (RFC 8018 App. B.1)");
|
|
474
303
|
}
|
|
@@ -481,18 +310,6 @@ function pbmac1Params(ns) {
|
|
|
481
310
|
});
|
|
482
311
|
}
|
|
483
312
|
|
|
484
|
-
// attrValueToString(ns): the AttributeValue decode-leaf. A malformed KNOWN
|
|
485
|
-
// string type (invalid UTF-8, a non-IA5 byte, a PrintableString character
|
|
486
|
-
// outside its set, ...) surfaces as an asn1/bad-* content error and must fail
|
|
487
|
-
// closed. Do not hex-encode it away, or the decoder's strict string validation
|
|
488
|
-
// is silently bypassed on the DN path. A value that is simply not a decodable
|
|
489
|
-
// primitive string is not malformed and stays representable: an ANY-typed
|
|
490
|
-
// non-string tag (asn1/expected-string) or a constructed universal type such as
|
|
491
|
-
// a SEQUENCE (asn1/expected-primitive) renders per RFC 4514 sec. 2.4 as "#" plus the
|
|
492
|
-
// hex of its complete DER encoding (node.bytes), round-tripping intact.
|
|
493
|
-
// A GENUINE string whose first character is '#' or '\' is surfaced with a
|
|
494
|
-
// leading '\' escape (RFC 4514 sec. 2.4) so it can never collide with the
|
|
495
|
-
// '#'+hex form; the encode direction strips that one leading escape back off.
|
|
496
313
|
function attrValueToString(ns) {
|
|
497
314
|
return schema.decode(function (node) {
|
|
498
315
|
var s;
|
|
@@ -501,12 +318,6 @@ function attrValueToString(ns) {
|
|
|
501
318
|
if (!e || (e.code !== "asn1/expected-string" && e.code !== "asn1/expected-primitive")) {
|
|
502
319
|
throw ns.E(ns.prefix + "/bad-atv", "malformed string in attribute value: " + ((e && e.message) || _String(e)));
|
|
503
320
|
}
|
|
504
|
-
// asn1.read.string does not read NumericString, and that is deliberate: a value that returns a plain
|
|
505
|
-
// string joins the RFC 5280 sec. 7.1 name-comparison identity class, and NumericString is not a
|
|
506
|
-
// DirectoryString type.
|
|
507
|
-
// It does have its own strict reader though, so VALIDATE the content here (an out-of-alphabet value is
|
|
508
|
-
// malformed DER and must not be accepted merely because it falls to the opaque form) while still
|
|
509
|
-
// surfacing it in the type-distinct RFC 4514 hex form, which keeps it out of that identity class.
|
|
510
321
|
if (node.tagClass === "universal" && node.tagNumber === asn1.TAGS.NUMERIC_STRING) {
|
|
511
322
|
try { asn1.read.numericString(node); }
|
|
512
323
|
catch (e2) { throw ns.E(ns.prefix + "/bad-atv", "malformed NumericString in attribute value: " + ((e2 && e2.message) || _String(e2))); }
|
|
@@ -516,21 +327,12 @@ function attrValueToString(ns) {
|
|
|
516
327
|
if (_charAt(s, 0) === "#" || _charAt(s, 0) === "\\") return "\\" + s;
|
|
517
328
|
return s;
|
|
518
329
|
}, function (value) {
|
|
519
|
-
// encode: a leading '\' escapes the next character (the decode's escape of a
|
|
520
|
-
// literal '#'/'\'); a "#hex" form (RFC 4514 sec. 2.4) round-trips its raw DER
|
|
521
|
-
// verbatim. The hex is validated (even-length hex digits encoding exactly one
|
|
522
|
-
// DER TLV) and throws on anything else, since Buffer.from(str, "hex") stops at
|
|
523
|
-
// the first invalid character and would emit truncated bytes. Any other
|
|
524
|
-
// string encodes as UTF8String (the decode does not preserve the original
|
|
525
|
-
// string type, so this is the canonical re-encoding, not a byte-exact one).
|
|
526
330
|
if (typeof value === "string" && _charAt(value, 0) === "\\") return asn1.build.utf8(_strSlice(value, 1));
|
|
527
331
|
if (typeof value === "string" && _charAt(value, 0) === "#") {
|
|
528
332
|
var hex = _strSlice(value, 1);
|
|
529
333
|
if (hex.length === 0) {
|
|
530
334
|
throw ns.E(ns.prefix + "/bad-atv", "a #hex attribute value must be a non-empty even run of hex digits (RFC 4514 sec. 2.4)");
|
|
531
335
|
}
|
|
532
|
-
// Strict canonical hex via the shared encoding guard (even-length, canonical,
|
|
533
|
-
// no silent truncation at the first non-hex character).
|
|
534
336
|
var raw = guard.encoding.hex(hex, null, ns.E, ns.prefix + "/bad-atv", "a #hex attribute value");
|
|
535
337
|
try { asn1.decode(raw); }
|
|
536
338
|
catch (e) { throw ns.E(ns.prefix + "/bad-atv", "a #hex attribute value must encode exactly one DER TLV", e); }
|
|
@@ -540,38 +342,14 @@ function attrValueToString(ns) {
|
|
|
540
342
|
});
|
|
541
343
|
}
|
|
542
344
|
|
|
543
|
-
// The always-escape specials of RFC 4514 sec. 2.4, plus its positional rules:
|
|
544
|
-
// NUL escapes as \00, a trailing space as '\ ', and a leading '#' or space as
|
|
545
|
-
// '\#' / '\ '. Without them the rendered dn is ambiguous (a literal '#0500'
|
|
546
|
-
// value would be indistinguishable from the hexstring form) and leading /
|
|
547
|
-
// trailing spaces would be silently significant.
|
|
548
|
-
// RFC 4514 sec. 2.4 attribute-value escaping is the single guard-name primitive
|
|
549
|
-
// (pki.inspect reuses the dn this produces and does not re-escape the already-
|
|
550
|
-
// escaped value), so the display escaper can't diverge from a re-inline elsewhere.
|
|
551
345
|
var _escapeDnValue = guard.name.escapeDnValue;
|
|
552
346
|
|
|
553
|
-
// The dn RENDERING of a surfaced AttributeValue: the '#'-leading hex form (a
|
|
554
|
-
// non-string value) renders bare, since escaping its '#' would turn the RFC 4514
|
|
555
|
-
// hexstring form into an escaped literal. A genuine string (surfaced
|
|
556
|
-
// with its leading '#'/'\' escape, see attrValueToString) is unescaped back to
|
|
557
|
-
// the semantic string and then rendered with the full escape set.
|
|
558
347
|
function _dnDisplayValue(v) {
|
|
559
348
|
if (typeof v !== "string") return v;
|
|
560
349
|
if (_charAt(v, 0) === "#") return v;
|
|
561
350
|
return _escapeDnValue(_charAt(v, 0) === "\\" ? _strSlice(v, 1) : v);
|
|
562
351
|
}
|
|
563
352
|
|
|
564
|
-
// time(ns): the RFC 5280 Time production under its profile encoding rule
|
|
565
|
-
// (sec. 4.1.2.5 for certificate validity, restated for the CRL fields in
|
|
566
|
-
// sec. 5.1.2.4-5.1.2.6): a date through the year 2049 MUST be UTCTime and
|
|
567
|
-
// GeneralizedTime is reserved for 2050 onward, so a GeneralizedTime carrying a
|
|
568
|
-
// 1950..2049 date is a non-conforming second encoding of a representable value
|
|
569
|
-
// and is rejected (<prefix>/bad-time); accepting it would let two wire forms
|
|
570
|
-
// alias one abstract time (a parser-differential channel). The engine's
|
|
571
|
-
// schema.time already applies the cutover on ENCODE; this leaf composes it and
|
|
572
|
-
// enforces the same rule on DECODE. A GeneralizedTime-only field (an RFC 3161
|
|
573
|
-
// genTime, a CRL invalidityDate, an RFC 5755 AttCertValidityPeriod) must not
|
|
574
|
-
// take this leaf, whose syntax has no UTCTime alternative to cut over to.
|
|
575
353
|
function time(ns) {
|
|
576
354
|
var base = schema.time(ns);
|
|
577
355
|
return schema.decode(function (n, ctx) {
|
|
@@ -586,10 +364,6 @@ function time(ns) {
|
|
|
586
364
|
}, base.write);
|
|
587
365
|
}
|
|
588
366
|
|
|
589
|
-
// Name ::= RDNSequence ::= SEQUENCE OF RelativeDistinguishedName; RDN ::= SET OF
|
|
590
|
-
// AttributeTypeAndValue ::= SEQUENCE { type OID, value ANY }. The atv asserts
|
|
591
|
-
// its SEQUENCE tag (a SET-tagged body is a different ASN.1 type), and repeated
|
|
592
|
-
// RDN attribute types stay legal (no uniqueness).
|
|
593
367
|
function attributeTypeAndValue(ns) {
|
|
594
368
|
return schema.seq([
|
|
595
369
|
schema.field("type", schema.oidLeaf()),
|
|
@@ -603,25 +377,16 @@ function attributeTypeAndValue(ns) {
|
|
|
603
377
|
});
|
|
604
378
|
}
|
|
605
379
|
function relativeDistinguishedName(ns) {
|
|
606
|
-
// RelativeDistinguishedName ::= SET SIZE (1..MAX), so an empty SET {} is malformed.
|
|
607
380
|
return schema.setOf(attributeTypeAndValue(ns), { assert: "set", min: 1, code: ns.prefix + "/bad-rdn", what: "RelativeDistinguishedName" });
|
|
608
381
|
}
|
|
609
|
-
// opts.implicitTag (optional): read the Name as a [tag] IMPLICIT RDNSequence. The
|
|
610
|
-
// context tag REPLACES the universal SEQUENCE tag, so the node is a context-class
|
|
611
|
-
// constructed [tag] whose children ARE the RDN SETs (the CRMF CertTemplate issuer
|
|
612
|
-
// [3] / subject [5] IMPLICIT arm, RFC 4211 sec. 5). With no opts the shape is a bare
|
|
613
|
-
// universal SEQUENCE OF, byte-identical to every existing caller.
|
|
614
382
|
function name(ns, opts) {
|
|
615
383
|
opts = opts || {};
|
|
616
384
|
function nameBuild(m) {
|
|
617
385
|
var rdns = [], parts = [];
|
|
618
|
-
// Through the captured traversal: `rdns` is the parsed subject a consumer reads a common name
|
|
619
|
-
// out of, so a replaced Array.prototype.forEach or push could drop the name the certificate
|
|
620
|
-
// carries and seat one it does not.
|
|
621
386
|
_forEach(m.items, function (rdnItem) {
|
|
622
387
|
var atvs = [], atvParts = [];
|
|
623
388
|
_forEach(rdnItem.value.items, function (atvItem) {
|
|
624
|
-
var a = atvItem.value.result;
|
|
389
|
+
var a = atvItem.value.result;
|
|
625
390
|
_push(atvs, a);
|
|
626
391
|
var label = (a.name && _hasOwn(DN_SHORT, a.name) && DN_SHORT[a.name]) || a.name || a.type;
|
|
627
392
|
_push(atvParts, label + "=" + _dnDisplayValue(a.value));
|
|
@@ -629,15 +394,6 @@ function name(ns, opts) {
|
|
|
629
394
|
_push(rdns, atvs);
|
|
630
395
|
_push(parts, _join(atvParts, "+"));
|
|
631
396
|
});
|
|
632
|
-
// `dn` is a DISPLAY string, and its spelling is `openssl x509 -subject`: the RDNs in the
|
|
633
|
-
// certificate's own order, joined by ", ". That is deliberately not the RFC 4514 / LDAP
|
|
634
|
-
// distinguishedName, which reverses the RDNs and joins them with a bare comma (`openssl -nameopt
|
|
635
|
-
// RFC2253`), so this string must never be handed to an LDAP client. What IS taken from RFC 4514
|
|
636
|
-
// is sec. 2.4 value escaping, so an attribute value holding a comma cannot read as two RDNs. The
|
|
637
|
-
// interop gate pins this against openssl's own output. Identity never flows through this string:
|
|
638
|
-
// guard.name.dnEqual compares the DER under RFC 5280 sec. 7.1, and `bytes` below carries the raw
|
|
639
|
-
// Name DER for a consumer that needs the exact octets (the EST re-enroll subject MUST). Every
|
|
640
|
-
// renderer in the toolkit uses this one spelling, so a certificate has one dn whichever parsed it.
|
|
641
397
|
return { rdns: rdns, dn: _join(parts, ", "), bytes: opts.implicitTag != null ? asn1.sequenceTlv(m.node) : m.node.bytes };
|
|
642
398
|
}
|
|
643
399
|
if (opts.implicitTag != null) {
|
|
@@ -648,32 +404,6 @@ function name(ns, opts) {
|
|
|
648
404
|
assert: "sequence", code: ns.prefix + "/bad-name", what: "Name", build: nameBuild });
|
|
649
405
|
}
|
|
650
406
|
|
|
651
|
-
// GeneralName ::= CHOICE (RFC 5280 sec. 4.2.1.6). A validate-and-surface-raw leaf for a
|
|
652
|
-
// caller that keeps the value raw but needs it to be a well-formed GeneralName: it
|
|
653
|
-
// checks the chosen alternative's tag, form (constructed vs primitive per X.690
|
|
654
|
-
// sec. 10.2), and content, then surfaces the value raw ({ bytes, tagClass, tagNumber }).
|
|
655
|
-
// The content rule per alternative: otherName [0] is a SEQUENCE { type-id OID, value [0] EXPLICIT
|
|
656
|
-
// }; x400Address [3] / ediPartyName [5] are non-empty constructed; directoryName [4]
|
|
657
|
-
// EXPLICIT wraps a valid Name; rfc822Name [1] / dNSName [2] / uniformResourceIdentifier
|
|
658
|
-
// [6] are primitive non-empty IA5String (7-bit); iPAddress [7] is a 4- or 16-octet
|
|
659
|
-
// OCTET STRING; registeredID [8] is a primitive OBJECT IDENTIFIER.
|
|
660
|
-
// `opts.code` is the caller's error code
|
|
661
|
-
// (e.g. ocsp/bad-requestor-name, tsp/bad-tsa). Shared so the two parsers cannot drift.
|
|
662
|
-
// opts.decodeValue (default false): in addition to the raw { bytes, tagClass,
|
|
663
|
-
// tagNumber }, surface the DECODED `value` per arm: IA5 text (string), an
|
|
664
|
-
// iPAddress Buffer, a directoryName { rdns, dn }, a registeredID OID string,
|
|
665
|
-
// or an otherName { typeId, valueBytes }. The path validator's name-constraint
|
|
666
|
-
// matcher needs the decoded value; the tsp/ocsp/attrcert consumers pass no
|
|
667
|
-
// flag and get the byte-identical raw-only shape.
|
|
668
|
-
// opts.subtreeBase (default false): the GeneralSubtree.base form (RFC 5280
|
|
669
|
-
// sec. 4.2.1.10). An iPAddress base is an address+mask (8 octets IPv4 / 32 IPv6),
|
|
670
|
-
// and not the 4/16-octet SAN address form. Only the iPAddress size rule changes.
|
|
671
|
-
// A GeneralizedTime-only leaf. Several formats fix their times to
|
|
672
|
-
// GeneralizedTime and reject UTCTime (OCSP RFC 6960, TSP RFC 3161 sec. 2.4.2,
|
|
673
|
-
// CMP RFC 9810 sec. 5.1.1, attribute-certificate validity RFC 5755 sec. 4.2.6);
|
|
674
|
-
// a single ns-parameterized factory owns the tag assert, so the per-format code and
|
|
675
|
-
// message differ but the check cannot drift. opts.allowFractional enables the
|
|
676
|
-
// X.690 sec. 11.7 fractional-seconds profile (the RFC 3161 genTime).
|
|
677
407
|
function generalizedTime(ns, opts) {
|
|
678
408
|
opts = opts || {};
|
|
679
409
|
var code = opts.code || (ns.prefix + "/bad-time");
|
|
@@ -687,8 +417,6 @@ function generalizedTime(ns, opts) {
|
|
|
687
417
|
});
|
|
688
418
|
}
|
|
689
419
|
|
|
690
|
-
// A UTF8String-only leaf -- the PKIFreeText element shape (RFC 3161 sec. 2.4.2,
|
|
691
|
-
// RFC 9810 sec. 5.1.1) shared by the TSP and CMP parsers.
|
|
692
420
|
function utf8Text(ns, opts) {
|
|
693
421
|
opts = opts || {};
|
|
694
422
|
var code = opts.code || (ns.prefix + "/bad-freetext");
|
|
@@ -701,11 +429,6 @@ function utf8Text(ns, opts) {
|
|
|
701
429
|
});
|
|
702
430
|
}
|
|
703
431
|
|
|
704
|
-
// A raw NON-EMPTY universal SEQUENCE leaf -- surfaces the exact TLV bytes of an
|
|
705
|
-
// embedded element (a Certificate, a CertificateList, a PKIPublicationInfo)
|
|
706
|
-
// after asserting the SEQUENCE tag and at least one child. An empty SEQUENCE
|
|
707
|
-
// (30 00) has the right tag but is none of those structures, so it rejects and
|
|
708
|
-
// no degenerate bytes reach certificate processing.
|
|
709
432
|
function rawNonEmptySequence(ns, opts) {
|
|
710
433
|
opts = opts || {};
|
|
711
434
|
var code = opts.code || (ns.prefix + "/bad-sequence");
|
|
@@ -718,19 +441,8 @@ function rawNonEmptySequence(ns, opts) {
|
|
|
718
441
|
});
|
|
719
442
|
}
|
|
720
443
|
|
|
721
|
-
// CRLReason ::= ENUMERATED (RFC 5280 sec. 5.3.1). Value 7 is unused/reserved.
|
|
722
|
-
// The single canonical value->name table every consumer validates against: the
|
|
723
|
-
// CRL reasonCode decoder surfaces the numeric code, the OCSP RevokedInfo
|
|
724
|
-
// decoder the name; both draw the legal set from here so they cannot drift.
|
|
725
|
-
// CRLReason ENUMERATED value names -- the single source is pki.C.NAMES.CRL_REASON
|
|
726
|
-
// (shared with the OCSP revoked-status decoder, which surfaces the same numeric code).
|
|
727
444
|
var CRL_REASON_NAMES = constants.NAMES.CRL_REASON;
|
|
728
445
|
|
|
729
|
-
// Which GeneralName alternatives are constructed, and which carry an IA5String. Both tables answer
|
|
730
|
-
// off a tag number the encoded value supplies, and both decide which validation an alternative
|
|
731
|
-
// gets, so neither may inherit: with an ordinary prototype a planted numeric property routes a tag
|
|
732
|
-
// down another alternative's arm, and a dNSName then reaches the IA5 arm's control-byte reject as
|
|
733
|
-
// a constructed value, or misses it altogether.
|
|
734
446
|
var GN_CONSTRUCTED = _assign(_create(null), { 0: 1, 3: 1, 4: 1, 5: 1 });
|
|
735
447
|
var GN_IA5 = _assign(_create(null), { 1: 1, 2: 1, 6: 1 });
|
|
736
448
|
function generalName(ns, opts) {
|
|
@@ -749,7 +461,6 @@ function generalName(ns, opts) {
|
|
|
749
461
|
if (GN_CONSTRUCTED[t]) {
|
|
750
462
|
if (!constructed || n.children.length < 1) throw ctx.E(code, "GeneralName [" + t + "] must be a non-empty constructed value (RFC 5280 sec. 4.2.1.6)");
|
|
751
463
|
if (t === 0) {
|
|
752
|
-
// otherName ::= SEQUENCE { type-id OBJECT IDENTIFIER, value [0] EXPLICIT ANY }.
|
|
753
464
|
if (n.children.length !== 2) throw ctx.E(code, "GeneralName otherName [0] must be a SEQUENCE { type-id, value [0] }");
|
|
754
465
|
var typeId;
|
|
755
466
|
try { typeId = asn1.read.oid(n.children[0]); }
|
|
@@ -760,7 +471,6 @@ function generalName(ns, opts) {
|
|
|
760
471
|
}
|
|
761
472
|
if (decodeValue) value = { typeId: typeId, valueBytes: ov.children[0].bytes };
|
|
762
473
|
} else if (t === 4) {
|
|
763
|
-
// directoryName [4] EXPLICIT Name: validate the wrapped RDNSequence.
|
|
764
474
|
if (n.children.length !== 1) throw ctx.E(code, "GeneralName directoryName [4] must wrap exactly one Name");
|
|
765
475
|
var dnMatch = schema.walk(NAME, n.children[0], ctx);
|
|
766
476
|
if (decodeValue) value = dnMatch.result;
|
|
@@ -769,8 +479,6 @@ function generalName(ns, opts) {
|
|
|
769
479
|
if (constructed) throw ctx.E(code, "GeneralName [" + t + "] must be primitive (X.690 sec. 10.2)");
|
|
770
480
|
if (GN_IA5[t]) {
|
|
771
481
|
if (n.content.length === 0) throw ctx.E(code, "GeneralName [" + t + "] must be a non-empty IA5String");
|
|
772
|
-
// 7-bit IA5, no C0/DEL control byte. An embedded NUL/control in a
|
|
773
|
-
// dNSName/rfc822Name/URI enables a name-truncation bypass (CVE-2009-2408).
|
|
774
482
|
guard.name.assertPrintableIa5(n.content, ctx.E, code, "GeneralName [" + t + "]");
|
|
775
483
|
if (decodeValue) value = _bufToString(n.content, "latin1");
|
|
776
484
|
} else if (t === 7) {
|
|
@@ -779,8 +487,6 @@ function generalName(ns, opts) {
|
|
|
779
487
|
} else if (n.content.length !== 4 && n.content.length !== 16) {
|
|
780
488
|
throw ctx.E(code, "GeneralName iPAddress [7] must be a 4- or 16-octet address");
|
|
781
489
|
}
|
|
782
|
-
// n.content is proven non-null here (the primitive-form guard above
|
|
783
|
-
// threw on a constructed node); copy the octets out.
|
|
784
490
|
if (decodeValue) value = _bufferConcat([n.content]);
|
|
785
491
|
} else if (t === 8) {
|
|
786
492
|
var regId;
|
|
@@ -795,26 +501,10 @@ function generalName(ns, opts) {
|
|
|
795
501
|
});
|
|
796
502
|
}
|
|
797
503
|
|
|
798
|
-
// GeneralNames ::= SEQUENCE OF GeneralName (RFC 5280 sec. 4.2.1.6), SIZE (1..MAX). Every
|
|
799
|
-
// element is validated by generalName (its CHOICE alternative's form + content) and
|
|
800
|
-
// surfaced raw, so a caller carrying a GeneralNames field cannot accept a malformed
|
|
801
|
-
// element by treating the whole sequence as opaque bytes. Returns { names, bytes }
|
|
802
|
-
// where each `names[i]` is the generalName leaf's return: { bytes, tagClass,
|
|
803
|
-
// tagNumber } plus, when opts.decodeValue is set, the decoded `value`. `bytes`
|
|
804
|
-
// is the raw outer DER.
|
|
805
|
-
// `opts.implicitTag` handles a [tag] IMPLICIT GeneralNames, where the context tag REPLACES
|
|
806
|
-
// the universal SEQUENCE tag (RFC 5755 Holder.entityName [1]); otherwise it is a bare
|
|
807
|
-
// universal SEQUENCE OF. `opts.code` is the caller's error code. Shared so the x509 /
|
|
808
|
-
// attribute-certificate / (future) CRMF parsers validate a GeneralNames identically.
|
|
809
504
|
function generalNames(ns, opts) {
|
|
810
505
|
opts = opts || {};
|
|
811
506
|
var code = opts.code || (ns.prefix + "/bad-general-names");
|
|
812
|
-
// decodeValue / subtreeBase thread through to each element (the path
|
|
813
|
-
// validator's constraint matcher passes decodeValue:true).
|
|
814
507
|
var gn = generalName(ns, { code: code, decodeValue: opts.decodeValue === true, subtreeBase: opts.subtreeBase === true });
|
|
815
|
-
// Through the captured traversal: the names this yields are what a consumer compares an identity
|
|
816
|
-
// against -- a certificate's subject alternative names against an enrollment's own -- and a
|
|
817
|
-
// replaced Array.prototype.map could present a name the encoded GeneralNames does not carry.
|
|
818
508
|
function build(m) { return { names: _map(m.items, function (it) { return it.value; }), bytes: m.node.bytes }; }
|
|
819
509
|
if (opts.implicitTag != null) {
|
|
820
510
|
return schema.implicitSeqOf(opts.implicitTag, gn, { min: 1, code: code, what: opts.what || "GeneralNames", build: build });
|
|
@@ -822,29 +512,12 @@ function generalNames(ns, opts) {
|
|
|
822
512
|
return schema.seqOf(gn, { assert: "sequence", min: 1, code: code, what: opts.what || "GeneralNames", build: build });
|
|
823
513
|
}
|
|
824
514
|
|
|
825
|
-
// DistributionPointName ::= CHOICE { fullName [0] IMPLICIT GeneralNames,
|
|
826
|
-
// nameRelativeToCRLIssuer [1] IMPLICIT RelativeDistinguishedName } (RFC 5280
|
|
827
|
-
// sec. 4.2.1.13). The caller hands the CHOICE node itself: the alternative
|
|
828
|
-
// inside the [0]-tagged distributionPoint field wrapper (a context tag on a
|
|
829
|
-
// CHOICE-typed field is always EXPLICIT, so the wire nests [0]{ [0]|[1] ... }).
|
|
830
|
-
// fullName surfaces each element's raw GeneralName DER as { kind: "fullName",
|
|
831
|
-
// names: [Buffer, ...] }, the byte-exact comparison key the sec. 5.2.5
|
|
832
|
-
// "identical encoding MUST be used" correspondence rule requires;
|
|
833
|
-
// nameRelativeToCRLIssuer surfaces its full [1]-tagged TLV as { kind: "rdn",
|
|
834
|
-
// bytes: Buffer } (byte-identical [1] TLVs are byte-identical RDN fragments).
|
|
835
|
-
// An empty fullName (GeneralNames is SIZE 1..MAX), a malformed element, or any
|
|
836
|
-
// other alternative throws the caller's `code`. Shared by the
|
|
837
|
-
// cRLDistributionPoints / freshestCRL extension decoders and the CRL checker's
|
|
838
|
-
// IssuingDistributionPoint decode, so the two sides of the sec. 6.3.3(b)(2)(i)
|
|
839
|
-
// correspondence comparison cannot drift.
|
|
840
515
|
function distributionPointName(ns, node, code) {
|
|
841
516
|
if (node && node.tagClass === "context" && node.tagNumber === 0) {
|
|
842
517
|
var gns = schema.walk(generalNames(ns, { implicitTag: 0, code: code, what: "DistributionPointName fullName" }), node, ns).result;
|
|
843
518
|
return { kind: "fullName", names: _map(gns.names, function (n) { return n.bytes; }) };
|
|
844
519
|
}
|
|
845
520
|
if (node && node.tagClass === "context" && node.tagNumber === 1) {
|
|
846
|
-
// In [1] IMPLICIT RelativeDistinguishedName the context tag replaces the
|
|
847
|
-
// SET tag, so the node's direct children are the AttributeTypeAndValues.
|
|
848
521
|
schema.walk(schema.implicitSetOf(1, attributeTypeAndValue(ns), {
|
|
849
522
|
min: 1, code: code, what: "DistributionPointName nameRelativeToCRLIssuer" }), node, ns);
|
|
850
523
|
return { kind: "rdn", bytes: node.bytes };
|
|
@@ -852,20 +525,7 @@ function distributionPointName(ns, node, code) {
|
|
|
852
525
|
throw ns.E(code, "DistributionPointName must be fullName [0] or nameRelativeToCRLIssuer [1] (RFC 5280 sec. 4.2.1.13)");
|
|
853
526
|
}
|
|
854
527
|
|
|
855
|
-
|
|
856
|
-
// when the certificate carries no keyUsage extension. An absent extension places no restriction
|
|
857
|
-
// (sec. 4.2.1.3), a distinct answer from "carries one that permits nothing".
|
|
858
|
-
//
|
|
859
|
-
// Declared here because "may this certificate do X" is asked at five boundaries and the answer has
|
|
860
|
-
// to be the same at all of them. keyUsage is a NamedBitList: DER drops its trailing zero bits
|
|
861
|
-
// (X.690 sec. 11.2.2) and sec. 4.2.1.3 requires at least one bit set. A boundary that reads the
|
|
862
|
-
// bits with a plain BIT STRING read applies neither, so the same certificate is authorized there
|
|
863
|
-
// and rejected as malformed by the issuing side and the path validator. One extension gets two
|
|
864
|
-
// readings, and the permissive one decides.
|
|
865
|
-
//
|
|
866
|
-
// `E(code, message, cause)` is the caller's typed error factory, so each boundary keeps its own
|
|
867
|
-
// domain while the RULE stays single-homed.
|
|
868
|
-
var _KU_DECODER = new _WeakMapI(); // one decoder table per namespace, built on first use
|
|
528
|
+
var _KU_DECODER = new _WeakMapI();
|
|
869
529
|
function keyUsageOf(ns, cert, E, code, label) {
|
|
870
530
|
var exts = (cert && cert.extensions) || [];
|
|
871
531
|
var want = ns.oid.byName("keyUsage");
|
|
@@ -879,26 +539,6 @@ function keyUsageOf(ns, cert, E, code, label) {
|
|
|
879
539
|
return null;
|
|
880
540
|
}
|
|
881
541
|
|
|
882
|
-
// IssuingDistributionPoint ::= SEQUENCE { distributionPoint [0] OPTIONAL,
|
|
883
|
-
// onlyContainsUserCerts [1] DEFAULT FALSE, onlyContainsCACerts [2] DEFAULT FALSE,
|
|
884
|
-
// onlySomeReasons [3] ReasonFlags OPTIONAL, indirectCRL [4] DEFAULT FALSE,
|
|
885
|
-
// onlyContainsAttributeCerts [5] DEFAULT FALSE } (RFC 5280 sec. 5.2.5).
|
|
886
|
-
//
|
|
887
|
-
// It is declared here and not in a consumer because the extension answers the
|
|
888
|
-
// question "which certificates does this CRL speak for", which both the path
|
|
889
|
-
// validator and the standalone CRL verbs act on, and a scope flag read by a hand
|
|
890
|
-
// walk over the raw children is read by the reader's own rules instead of the
|
|
891
|
-
// encoding's. Each flag is an IMPLICIT BOOLEAN, so `implicitBoolean` is what
|
|
892
|
-
// holds it to one content octet of 0x00 or 0xFF (X.690 sec. 11.1); a hand walk
|
|
893
|
-
// testing a content byte reads an EMPTY [4] as absent and a multi-octet one by
|
|
894
|
-
// whichever byte it happens to index, and "absent" is the reading that grants
|
|
895
|
-
// permission to answer. `trailing` supplies the rest of the grammar: strictly
|
|
896
|
-
// ascending tags, each field at most once, nothing outside 0..5.
|
|
897
|
-
//
|
|
898
|
-
// A present DEFAULT-FALSE flag encoding FALSE is well-formed at the leaf but is
|
|
899
|
-
// still an encoding DER forbids (X.690 sec. 11.5 omits the default), so the
|
|
900
|
-
// caller decides what that means for it. It is reported as a value of `false`
|
|
901
|
-
// against a `present` of true, never silently normalized away.
|
|
902
542
|
function issuingDistributionPoint(code) {
|
|
903
543
|
return schema.seq([
|
|
904
544
|
schema.trailing([
|
|
@@ -912,28 +552,8 @@ function issuingDistributionPoint(code) {
|
|
|
912
552
|
], { assert: "sequence", code: code, what: "IssuingDistributionPoint" });
|
|
913
553
|
}
|
|
914
554
|
|
|
915
|
-
// certExtensionDecoders(ns) -- the ns-parameterized RFC 5280 sec. 4.2.1 extension
|
|
916
|
-
// VALUE decoders. `x509.parse` surfaces each extension as { oid, name, critical,
|
|
917
|
-
// value } with `value` the raw inner OCTET-STRING content (a Buffer); the path
|
|
918
|
-
// validator and the future complete-extension-set surface need the STRUCTURE
|
|
919
|
-
// inside. Each decoder takes that raw Buffer and returns the decoded value, or
|
|
920
|
-
// throws a typed `<prefix>/bad-*` code, mirroring the CRL's fail-closed
|
|
921
|
-
// `decodeExt` pattern (asn1.decode -> read.* / schema.walk, wrapped). Returns
|
|
922
|
-
// { byOid } keyed by dotted OID so a consumer dispatches by ext.oid. The decoded
|
|
923
|
-
// set spans the RFC 5280 sec. 4.2.1 extensions plus the RFC 3739 sec. 3.2.6 /
|
|
924
|
-
// ETSI EN 319 412-5 qualified-certificate `qcStatements` extension (each known
|
|
925
|
-
// statement decoded, an unknown statementId preserved opaque).
|
|
926
555
|
var _T = asn1.TAGS;
|
|
927
556
|
|
|
928
|
-
// assertPolicyQualifiers(qNode, fail) -- RFC 5280 sec. 4.2.1.4: a PolicyInformation's
|
|
929
|
-
// policyQualifiers is a non-empty SEQUENCE OF PolicyQualifierInfo, each a SEQUENCE
|
|
930
|
-
// { policyQualifierId OID, qualifier } of exactly two members led by an OID. The
|
|
931
|
-
// structure is validated fail-closed; the qualifier body stays opaque (surfaced
|
|
932
|
-
// raw by the caller). `fail(msg)` throws the caller's typed code. Shared by the
|
|
933
|
-
// certificatePolicies extension decoder and the RFC 5035 ESS SigningCertificate
|
|
934
|
-
// policies field so the same PolicyInformation shape cannot drift between them.
|
|
935
|
-
// `fail(msg, cause?)` throws the caller's typed code, carrying the underlying
|
|
936
|
-
// leaf fault as the error cause when one is available.
|
|
937
557
|
function assertPolicyQualifiers(qNode, fail) {
|
|
938
558
|
if (qNode.tagClass !== "universal" || qNode.tagNumber !== _T.SEQUENCE || !qNode.children || !qNode.children.length) {
|
|
939
559
|
fail("policyQualifiers must be a non-empty SEQUENCE (RFC 5280 sec. 4.2.1.4)");
|
|
@@ -946,68 +566,25 @@ function assertPolicyQualifiers(qNode, fail) {
|
|
|
946
566
|
});
|
|
947
567
|
}
|
|
948
568
|
|
|
949
|
-
// DisplayText ::= CHOICE { ia5String, visibleString, bmpString, utf8String } each SIZE (1..200)
|
|
950
|
-
// -- RFC 5280 sec. 4.2.1.4. The bound is deliberately not enforced here. That is a normative
|
|
951
|
-
// requirement and not a gap: the same section closes with "While the explicitText has a maximum
|
|
952
|
-
// size of 200 characters, some non-conforming CAs exceed this limit. Therefore, certificate users
|
|
953
|
-
// SHOULD gracefully handle explicitText with more than 200 characters." A decoder IS the certificate
|
|
954
|
-
// user that note addresses, so refusing an over-long notice would violate a SHOULD and reject
|
|
955
|
-
// certificates that are in the wild and otherwise valid. The bound belongs to the two layers that
|
|
956
|
-
// can act on it: an issuer, which must not MINT one, and pki.lint, which reports it as an advisory.
|
|
957
|
-
//
|
|
958
|
-
// Measured in CHARACTERS, not octets: a conforming 200-character UTF8String notice can occupy 800
|
|
959
|
-
// octets, so `.length` (UTF-16 units) or a byte count would misjudge a valid value. Exported so
|
|
960
|
-
// every layer that reports the bound counts it identically instead of keeping its own copy.
|
|
961
569
|
var DISPLAY_TEXT_MAX = 200;
|
|
962
570
|
function displayTextChars(str) { return _arrayFrom(str).length; }
|
|
963
571
|
|
|
964
572
|
// @internal
|
|
965
|
-
// userNoticeTexts(qualifier) -- the DisplayText values of a decoded UserNotice qualifier, as
|
|
966
|
-
// [{ field, tagNumber, text, chars }] in encounter order. `field` is "explicitText" or
|
|
967
|
-
// "organization" (the NoticeReference member), so a caller that must treat the two alike (the
|
|
968
|
-
// SIZE bound applies to both) and one that must not (only explicitText carries the encoding and
|
|
969
|
-
// normalization rules) can each select correctly.
|
|
970
|
-
//
|
|
971
|
-
// UserNotice ::= SEQUENCE { noticeRef NoticeReference OPTIONAL, explicitText DisplayText OPTIONAL }
|
|
972
|
-
// NoticeReference ::= SEQUENCE { organization DisplayText, noticeNumbers SEQUENCE OF INTEGER }
|
|
973
|
-
// Both members are OPTIONAL and distinguished by tag, so a leading SEQUENCE is the noticeRef and any
|
|
974
|
-
// DisplayText member is the explicitText. Returns [] for a qualifier that is not a UserNotice --
|
|
975
|
-
// this reads an already-decoded node for display and reporting, and is never a validity verdict.
|
|
976
573
|
var _DISPLAY_TEXT_TAGS = null;
|
|
977
574
|
function _isDisplayTextNode(n) {
|
|
978
575
|
if (!_DISPLAY_TEXT_TAGS) {
|
|
979
|
-
// Indexed by a tag number the encoded value supplies, so it holds no prototype: an inherited
|
|
980
|
-
// numeric name would make an unregistered universal tag read as a DisplayText alternative.
|
|
981
576
|
_DISPLAY_TEXT_TAGS = _create(null);
|
|
982
577
|
_DISPLAY_TEXT_TAGS[_T.IA5_STRING] = 1; _DISPLAY_TEXT_TAGS[_T.VISIBLE_STRING] = 1;
|
|
983
578
|
_DISPLAY_TEXT_TAGS[_T.BMP_STRING] = 1; _DISPLAY_TEXT_TAGS[_T.UTF8_STRING] = 1;
|
|
984
579
|
}
|
|
985
580
|
return !!n && n.tagClass === "universal" && _DISPLAY_TEXT_TAGS[n.tagNumber] === 1;
|
|
986
581
|
}
|
|
987
|
-
// Decoded through the STRICT reader, which validates each arm against its own declared string type
|
|
988
|
-
// (invalid UTF-8, a high bit in an IA5String, an odd-length or lone-surrogate BMPString all throw).
|
|
989
|
-
// Reading the content bytes directly would repair the value instead: `toString("utf8")` substitutes
|
|
990
|
-
// U+FFFD for invalid sequences and a hand-rolled UCS-2 loop drops a trailing odd octet, so a caller
|
|
991
|
-
// would render or measure text the certificate does not contain. Returns null when the value does
|
|
992
|
-
// not decode, leaving the fallback to the caller.
|
|
993
|
-
// An undecodable value yields an entry whose `text` and `chars` are null but whose `field` and
|
|
994
|
-
// `tagNumber` are still present. The two are separable facts: which ASN.1 string type was used is
|
|
995
|
-
// established by the tag alone, so a rule about the ENCODING stays answerable even when the contents
|
|
996
|
-
// do not decode, while every rule that reads the TEXT must skip the entry and not analyze a
|
|
997
|
-
// repair. Collapsing both into "not analyzable" would let a prohibited encoding escape its finding
|
|
998
|
-
// by also being malformed inside.
|
|
999
582
|
function _dtEntry(field, node) {
|
|
1000
583
|
var text;
|
|
1001
584
|
// allow:swallow-unverified an undecodable DisplayText keeps its tag and drops its text; the callers branch on text === null
|
|
1002
585
|
try { text = asn1.read.string(node); } catch (_e) { text = null; }
|
|
1003
586
|
return { field: field, tagNumber: node.tagNumber, text: text, chars: text === null ? null : displayTextChars(text) };
|
|
1004
587
|
}
|
|
1005
|
-
// A NoticeReference names its notice by ORGANIZATION **and** NUMBER. The organization alone does not
|
|
1006
|
-
// identify which notice is meant, so a consumer that surfaced only the text would lose the lookup key.
|
|
1007
|
-
// Returns null when the numbers are absent, wrongly shaped, or contain a member that is not a readable
|
|
1008
|
-
// INTEGER: an incompletely decoded reference must not be presentable, because dropping the members that
|
|
1009
|
-
// failed would render a partial reference indistinguishable from a whole one. That is the same defect as
|
|
1010
|
-
// omitting the numbers entirely, in a form that is harder to notice. Numbers are decimal strings.
|
|
1011
588
|
function _noticeNumbers(node) {
|
|
1012
589
|
if (!node || node.tagClass !== "universal" || node.tagNumber !== _T.SEQUENCE || !node.children) return null;
|
|
1013
590
|
var nums = [], ok = true;
|
|
@@ -1017,22 +594,11 @@ function _noticeNumbers(node) {
|
|
|
1017
594
|
});
|
|
1018
595
|
return ok ? nums : null;
|
|
1019
596
|
}
|
|
1020
|
-
// Every DisplayText member is returned, including one whose contents did not decode; that entry
|
|
1021
|
-
// carries its tag with a null `text`. A caller that RENDERS the notice must check that every entry
|
|
1022
|
-
// decoded before showing any of it (a partial notice is indistinguishable from a complete one); a
|
|
1023
|
-
// caller that only classifies the encoding can read the tags regardless.
|
|
1024
|
-
// The LAYOUT is validated before any member is read: UserNotice fixes the order (noticeRef first) and
|
|
1025
|
-
// the cardinality (each member at most once, nothing else present). Collecting every recognized member
|
|
1026
|
-
// and ignoring the rest would accept a duplicated explicitText, a reversed order, or an extra member.
|
|
1027
|
-
// The caller can only see whether the entries it got look complete, so it would then present
|
|
1028
|
-
// a structurally invalid notice as a whole one. A malformed layout yields NO entries, so a renderer
|
|
1029
|
-
// falls back to hex and a reporter measures nothing.
|
|
1030
597
|
function userNoticeTexts(qualifier) {
|
|
1031
598
|
if (!qualifier || qualifier.tagClass !== "universal" || qualifier.tagNumber !== _T.SEQUENCE || !qualifier.children) return [];
|
|
1032
599
|
var kids = qualifier.children;
|
|
1033
600
|
if (kids.length > 2) return [];
|
|
1034
601
|
var i = 0, out = [];
|
|
1035
|
-
// noticeRef [absent | first]: a SEQUENCE of { organization DisplayText, noticeNumbers }.
|
|
1036
602
|
if (i < kids.length && kids[i].tagClass === "universal" && kids[i].tagNumber === _T.SEQUENCE) {
|
|
1037
603
|
var nr = kids[i];
|
|
1038
604
|
if (!nr.children || nr.children.length !== 2 || !_isDisplayTextNode(nr.children[0])) return [];
|
|
@@ -1041,7 +607,6 @@ function userNoticeTexts(qualifier) {
|
|
|
1041
607
|
_push(out, org);
|
|
1042
608
|
i++;
|
|
1043
609
|
}
|
|
1044
|
-
// explicitText [absent | last]: a DisplayText. Anything else remaining is not a UserNotice member.
|
|
1045
610
|
if (i < kids.length) {
|
|
1046
611
|
if (!_isDisplayTextNode(kids[i])) return [];
|
|
1047
612
|
_push(out, _dtEntry("explicitText", kids[i]));
|
|
@@ -1050,9 +615,6 @@ function userNoticeTexts(qualifier) {
|
|
|
1050
615
|
return i === kids.length ? out : [];
|
|
1051
616
|
}
|
|
1052
617
|
|
|
1053
|
-
// Shared imperative decode helpers for the certExtensionDecoders + attrValueDecoders factories. Both
|
|
1054
|
-
// take the caller's ns, so the helpers close over ns.E / ns.oid; extracted once so the two factories
|
|
1055
|
-
// share one copy (a per-OID value-decoder body is the identical idiom to a per-OID extension body).
|
|
1056
618
|
function _decodeHelpers(ns) {
|
|
1057
619
|
function decodeTop(buf, code, what) {
|
|
1058
620
|
var n;
|
|
@@ -1071,8 +633,6 @@ function _decodeHelpers(ns) {
|
|
|
1071
633
|
try { return asn1.read.integer(node); }
|
|
1072
634
|
catch (e) { throw ns.E(code, what + " must be an INTEGER", e); }
|
|
1073
635
|
}
|
|
1074
|
-
// Resolve a registered OID name -> dotted string, throwing at module load on a typo (a decoder keyed
|
|
1075
|
-
// by an unresolved name would silently never match, treating a real extension/attribute as opaque).
|
|
1076
636
|
function O(nm) {
|
|
1077
637
|
var d = ns.oid.byName(nm);
|
|
1078
638
|
if (typeof d !== "string") throw new TypeError("pki.schema.pkix: " + _stringify(nm) + " is not a registered OID name");
|
|
@@ -1085,7 +645,6 @@ function certExtensionDecoders(ns) {
|
|
|
1085
645
|
var GN_SUBTREE = generalName(ns, { decodeValue: true, subtreeBase: true, code: ns.prefix + "/bad-name-constraints" });
|
|
1086
646
|
var _h = _decodeHelpers(ns), decodeTop = _h.decodeTop, seqChildren = _h.seqChildren, readInt = _h.readInt, O = _h.O;
|
|
1087
647
|
|
|
1088
|
-
// basicConstraints ::= SEQUENCE { cA BOOLEAN DEFAULT FALSE, pathLen INTEGER (0..MAX) OPTIONAL }
|
|
1089
648
|
function basicConstraints(buf) {
|
|
1090
649
|
var C = ns.prefix + "/bad-basic-constraints";
|
|
1091
650
|
var kids = seqChildren(buf, C, "BasicConstraints");
|
|
@@ -1099,16 +658,12 @@ function certExtensionDecoders(ns) {
|
|
|
1099
658
|
if (kids[i] && kids[i].tagClass === "universal" && kids[i].tagNumber === _T.INTEGER) {
|
|
1100
659
|
if (!cA) throw ns.E(C, "BasicConstraints pathLenConstraint is only permitted when cA is TRUE (RFC 5280 sec. 4.2.1.9)");
|
|
1101
660
|
var pl = readInt(kids[i], C, "pathLenConstraint");
|
|
1102
|
-
// guard.range.uint31 bounds the decoded BigInt and narrows atomically. A
|
|
1103
|
-
// value past the safe-integer range would otherwise round silently and be
|
|
1104
|
-
// compared as a path-length ceiling.
|
|
1105
661
|
pathLen = guard.range.uint31(pl, ns.E, C, "BasicConstraints pathLenConstraint (RFC 5280 sec. 4.2.1.9)"); i++;
|
|
1106
662
|
}
|
|
1107
663
|
if (i !== kids.length) throw ns.E(C, "BasicConstraints has unexpected trailing fields");
|
|
1108
664
|
return { cA: cA, pathLenConstraint: pathLen };
|
|
1109
665
|
}
|
|
1110
666
|
|
|
1111
|
-
// keyUsage ::= BIT STRING (named bits). At least one bit MUST be set.
|
|
1112
667
|
var KU_BITS = ["digitalSignature", "nonRepudiation", "keyEncipherment", "dataEncipherment",
|
|
1113
668
|
"keyAgreement", "keyCertSign", "cRLSign", "encipherOnly", "decipherOnly"];
|
|
1114
669
|
function keyUsage(buf) {
|
|
@@ -1117,21 +672,15 @@ function certExtensionDecoders(ns) {
|
|
|
1117
672
|
if (n.tagClass !== "universal" || n.tagNumber !== _T.BIT_STRING) throw ns.E(C, "KeyUsage must be a BIT STRING (RFC 5280 sec. 4.2.1.3)");
|
|
1118
673
|
var bs;
|
|
1119
674
|
try { bs = asn1.read.bitString(n); } catch (e) { throw ns.E(C, "KeyUsage must be a well-formed BIT STRING", e); }
|
|
1120
|
-
// At least one bit MUST be set (RFC 5280 sec. 4.2.1.3), so an all-zero value (a
|
|
1121
|
-
// non-empty byte run of only zero bits, e.g. 03 02 07 00) is malformed too.
|
|
1122
675
|
var anyBit = false;
|
|
1123
676
|
for (var z = 0; z < bs.bytes.length; z++) { if (bs.bytes[z] !== 0) { anyBit = true; break; } }
|
|
1124
677
|
if (!anyBit) throw ns.E(C, "KeyUsage must assert at least one bit (RFC 5280 sec. 4.2.1.3)");
|
|
1125
|
-
// KeyUsage is a NamedBitList -- enforce the shared X.690 sec. 11.2.2 minimal-DER rule.
|
|
1126
678
|
schema.assertMinimalNamedBits(bs.unusedBits, bs.bytes, function (m) { throw ns.E(C, m); });
|
|
1127
679
|
var out = {};
|
|
1128
680
|
_forEach(KU_BITS, function (nm, bit) {
|
|
1129
681
|
var byte = bit >> 3, mask = 0x80 >> (bit & 7);
|
|
1130
682
|
out[nm] = byte < bs.bytes.length && (bs.bytes[byte] & mask) !== 0;
|
|
1131
683
|
});
|
|
1132
|
-
// Any bit beyond the 9 named positions is a reserved/unknown usage. RFC 5280 tolerates it,
|
|
1133
|
-
// but a "MUST be the ONLY bit" rule (e.g. RFC 9935 sec. 5 ML-KEM keyEncipherment-only) needs
|
|
1134
|
-
// to see it, so bits >= 9 are surfaced on `reservedBitsSet`.
|
|
1135
684
|
var reserved = false;
|
|
1136
685
|
for (var bit2 = KU_BITS.length; bit2 < bs.bytes.length * 8 && !reserved; bit2++) {
|
|
1137
686
|
if ((bs.bytes[bit2 >> 3] & (0x80 >> (bit2 & 7))) !== 0) reserved = true;
|
|
@@ -1140,11 +689,7 @@ function certExtensionDecoders(ns) {
|
|
|
1140
689
|
return out;
|
|
1141
690
|
}
|
|
1142
691
|
|
|
1143
|
-
// GeneralSubtree ::= SEQUENCE { base GeneralName, minimum [0] DEFAULT 0, maximum [1] OPTIONAL }
|
|
1144
692
|
function subtreeList(node, C) {
|
|
1145
|
-
// node is the [0]/[1] IMPLICIT SEQUENCE OF GeneralSubtree (context-constructed).
|
|
1146
|
-
// GeneralSubtrees is SIZE(1..MAX) -- an explicitly present but empty subtree
|
|
1147
|
-
// list (a0 00) absorbs no constraint and is malformed, not a no-op.
|
|
1148
693
|
if (!node.children || node.children.length < 1) throw ns.E(C, "NameConstraints permittedSubtrees/excludedSubtrees must be a non-empty GeneralSubtrees (SIZE 1..MAX, RFC 5280 sec. 4.2.1.10)");
|
|
1149
694
|
return _map(node.children, function (st) {
|
|
1150
695
|
if (st.tagClass !== "universal" || st.tagNumber !== _T.SEQUENCE || !st.children || st.children.length < 1) {
|
|
@@ -1176,18 +721,12 @@ function certExtensionDecoders(ns) {
|
|
|
1176
721
|
return { permittedSubtrees: permitted, excludedSubtrees: excluded };
|
|
1177
722
|
}
|
|
1178
723
|
|
|
1179
|
-
// certificatePolicies ::= SEQUENCE SIZE(1..MAX) OF PolicyInformation
|
|
1180
724
|
function certificatePolicies(buf) {
|
|
1181
725
|
var C = ns.prefix + "/bad-policy";
|
|
1182
726
|
var kids = seqChildren(buf, C, "CertificatePolicies");
|
|
1183
727
|
if (kids.length < 1) throw ns.E(C, "CertificatePolicies must contain at least one PolicyInformation (RFC 5280 sec. 4.2.1.4)");
|
|
1184
|
-
// The duplicate-policy set answers off a dotted OID the encoded value supplies, so it holds no
|
|
1185
|
-
// prototype: an inherited name would report a policy as already seen and reject a valid one.
|
|
1186
728
|
var seen = _create(null);
|
|
1187
729
|
return _map(kids, function (pi) {
|
|
1188
|
-
// PolicyInformation ::= SEQUENCE { policyIdentifier, policyQualifiers
|
|
1189
|
-
// SEQUENCE SIZE(1..MAX) OPTIONAL }. Exactly one or two fields; a second
|
|
1190
|
-
// field, if present, MUST be a SEQUENCE. Extra/mis-typed fields are malformed.
|
|
1191
730
|
if (pi.tagClass !== "universal" || pi.tagNumber !== _T.SEQUENCE || !pi.children || pi.children.length < 1 || pi.children.length > 2) {
|
|
1192
731
|
throw ns.E(C, "PolicyInformation must be a SEQUENCE { policyIdentifier, policyQualifiers? }");
|
|
1193
732
|
}
|
|
@@ -1198,8 +737,6 @@ function certExtensionDecoders(ns) {
|
|
|
1198
737
|
var qualifiers = null;
|
|
1199
738
|
if (pi.children.length > 1) {
|
|
1200
739
|
var q = pi.children[1];
|
|
1201
|
-
// PolicyQualifierInfo structure validated by the shared assertion; the
|
|
1202
|
-
// qualifier body stays opaque (surfaced raw).
|
|
1203
740
|
assertPolicyQualifiers(q, function (msg, cause) { throw ns.E(C, msg, cause); });
|
|
1204
741
|
qualifiers = q.bytes;
|
|
1205
742
|
}
|
|
@@ -1207,7 +744,6 @@ function certExtensionDecoders(ns) {
|
|
|
1207
744
|
});
|
|
1208
745
|
}
|
|
1209
746
|
|
|
1210
|
-
// policyMappings ::= SEQUENCE SIZE(1..MAX) OF SEQUENCE { issuerDomainPolicy, subjectDomainPolicy }
|
|
1211
747
|
function policyMappings(buf) {
|
|
1212
748
|
var C = ns.prefix + "/bad-policy";
|
|
1213
749
|
var kids = seqChildren(buf, C, "PolicyMappings");
|
|
@@ -1223,7 +759,6 @@ function certExtensionDecoders(ns) {
|
|
|
1223
759
|
});
|
|
1224
760
|
}
|
|
1225
761
|
|
|
1226
|
-
// policyConstraints ::= SEQUENCE { requireExplicitPolicy [0] INTEGER OPT, inhibitPolicyMapping [1] INTEGER OPT }
|
|
1227
762
|
function policyConstraints(buf) {
|
|
1228
763
|
var C = ns.prefix + "/bad-policy";
|
|
1229
764
|
var kids = seqChildren(buf, C, "PolicyConstraints");
|
|
@@ -1235,8 +770,6 @@ function certExtensionDecoders(ns) {
|
|
|
1235
770
|
pcLastTag = k.tagNumber;
|
|
1236
771
|
var v;
|
|
1237
772
|
try { v = asn1.read.integerImplicit(k, k.tagNumber); } catch (e) { throw ns.E(C, "PolicyConstraints field must be an INTEGER", e); }
|
|
1238
|
-
// guard.range.uint31 bounds + narrows atomically (a skip count past the
|
|
1239
|
-
// safe-integer range would round silently).
|
|
1240
773
|
var skip = guard.range.uint31(v, ns.E, C, "PolicyConstraints skip count (RFC 5280 sec. 4.2.1.11)");
|
|
1241
774
|
if (k.tagNumber === 0) rep = skip;
|
|
1242
775
|
else if (k.tagNumber === 1) ipm = skip;
|
|
@@ -1245,31 +778,20 @@ function certExtensionDecoders(ns) {
|
|
|
1245
778
|
return { requireExplicitPolicy: rep, inhibitPolicyMapping: ipm };
|
|
1246
779
|
}
|
|
1247
780
|
|
|
1248
|
-
// inhibitAnyPolicy ::= INTEGER (0..MAX)
|
|
1249
781
|
function inhibitAnyPolicy(buf) {
|
|
1250
782
|
var C = ns.prefix + "/bad-policy";
|
|
1251
783
|
var n = decodeTop(buf, C, "InhibitAnyPolicy");
|
|
1252
784
|
if (n.tagClass !== "universal" || n.tagNumber !== _T.INTEGER) throw ns.E(C, "InhibitAnyPolicy must be an INTEGER (RFC 5280 sec. 4.2.1.14)");
|
|
1253
785
|
var v = readInt(n, C, "InhibitAnyPolicy");
|
|
1254
|
-
// guard.range.uint31 bounds + narrows atomically (a skip count past the
|
|
1255
|
-
// safe-integer range would round silently).
|
|
1256
786
|
return guard.range.uint31(v, ns.E, C, "InhibitAnyPolicy skip count (RFC 5280 sec. 4.2.1.14)");
|
|
1257
787
|
}
|
|
1258
788
|
|
|
1259
|
-
// subjectAltName / issuerAltName ::= GeneralNames, with decoded values surfaced.
|
|
1260
789
|
function altName(buf) {
|
|
1261
790
|
var C = ns.prefix + "/bad-extension-value";
|
|
1262
791
|
var n = decodeTop(buf, C, "GeneralNames");
|
|
1263
792
|
return schema.walk(generalNames(ns, { decodeValue: true, code: C }), n, ns).result;
|
|
1264
793
|
}
|
|
1265
794
|
|
|
1266
|
-
// authorityInfoAccess ::= AuthorityInfoAccessSyntax ::= SEQUENCE SIZE(1..MAX) OF AccessDescription
|
|
1267
|
-
// (RFC 5280 sec. 4.2.2.1); AccessDescription ::= SEQUENCE { accessMethod OBJECT IDENTIFIER, accessLocation
|
|
1268
|
-
// GeneralName }. Surfaces [{ accessMethod: <dotted OID>, accessLocation: { tag, value } }] in wire order --
|
|
1269
|
-
// accessLocation is the shared generalName leaf (its context tag number + decoded value, so a control-byte
|
|
1270
|
-
// URI is rejected by the CVE-2009-2408 guard). Both accessMethods surface (id-ad-caIssuers AND id-ad-ocsp):
|
|
1271
|
-
// a consumer filters by accessMethod (caIssuers for issuer fetching, ocsp for responder discovery). An empty
|
|
1272
|
-
// SEQUENCE violates SIZE(1..MAX) and is malformed. A composed decoder + registry row, not a hand-roll.
|
|
1273
795
|
function authorityInfoAccess(buf) {
|
|
1274
796
|
var C = ns.prefix + "/bad-extension-value";
|
|
1275
797
|
var descs = seqChildren(buf, C, "AuthorityInfoAccessSyntax");
|
|
@@ -1282,12 +804,11 @@ function certExtensionDecoders(ns) {
|
|
|
1282
804
|
var method;
|
|
1283
805
|
try { method = asn1.read.oid(d.children[0]); }
|
|
1284
806
|
catch (e) { throw ns.E(C, "AccessDescription accessMethod must be an OBJECT IDENTIFIER", e); }
|
|
1285
|
-
var loc = schema.walk(GN, d.children[1], ns);
|
|
807
|
+
var loc = schema.walk(GN, d.children[1], ns);
|
|
1286
808
|
return { accessMethod: method, accessLocation: { tag: loc.tagNumber, value: loc.value } };
|
|
1287
809
|
});
|
|
1288
810
|
}
|
|
1289
811
|
|
|
1290
|
-
// extKeyUsage ::= SEQUENCE SIZE(1..MAX) OF KeyPurposeId (OID)
|
|
1291
812
|
function extKeyUsage(buf) {
|
|
1292
813
|
var C = ns.prefix + "/bad-extension-value";
|
|
1293
814
|
var kids = seqChildren(buf, C, "ExtKeyUsage");
|
|
@@ -1297,7 +818,6 @@ function certExtensionDecoders(ns) {
|
|
|
1297
818
|
});
|
|
1298
819
|
}
|
|
1299
820
|
|
|
1300
|
-
// authorityKeyIdentifier ::= SEQUENCE { keyIdentifier [0] OPT, authorityCertIssuer [1] OPT, authorityCertSerialNumber [2] OPT }
|
|
1301
821
|
function authorityKeyIdentifier(buf) {
|
|
1302
822
|
var C = ns.prefix + "/bad-extension-value";
|
|
1303
823
|
var kids = seqChildren(buf, C, "AuthorityKeyIdentifier");
|
|
@@ -1305,13 +825,8 @@ function certExtensionDecoders(ns) {
|
|
|
1305
825
|
var lastTag = -1;
|
|
1306
826
|
_forEach(kids, function (k) {
|
|
1307
827
|
if (k.tagClass !== "context") throw ns.E(C, "AuthorityKeyIdentifier fields are context-tagged");
|
|
1308
|
-
// DER: the OPTIONAL fields appear at most once and in ascending tag order.
|
|
1309
828
|
if (k.tagNumber <= lastTag) throw ns.E(C, "AuthorityKeyIdentifier fields must be unique and in ascending order (DER)");
|
|
1310
829
|
lastTag = k.tagNumber;
|
|
1311
|
-
// keyIdentifier [0] IMPLICIT OCTET STRING and authorityCertSerialNumber
|
|
1312
|
-
// [2] IMPLICIT INTEGER are primitive leaves; the readers enforce the
|
|
1313
|
-
// primitive form fail-closed (a constructed context node is rejected,
|
|
1314
|
-
// never dereferenced for its absent content).
|
|
1315
830
|
if (k.tagNumber === 0) { try { out.keyIdentifier = _bufferFrom(asn1.read.octetStringImplicit(k, 0)); } catch (e) { throw ns.E(C, "AuthorityKeyIdentifier keyIdentifier [0] must be an IMPLICIT OCTET STRING", e); } }
|
|
1316
831
|
else if (k.tagNumber === 1) out.authorityCertIssuer = schema.walk(generalNames(ns, { implicitTag: 1, decodeValue: true, code: C }), k, ns).result;
|
|
1317
832
|
else if (k.tagNumber === 2) { try { out.authorityCertSerialNumber = asn1.read.integerImplicit(k, 2); } catch (e) { throw ns.E(C, "authorityCertSerialNumber must be an INTEGER", e); } }
|
|
@@ -1323,7 +838,6 @@ function certExtensionDecoders(ns) {
|
|
|
1323
838
|
return out;
|
|
1324
839
|
}
|
|
1325
840
|
|
|
1326
|
-
// subjectKeyIdentifier ::= OCTET STRING
|
|
1327
841
|
function subjectKeyIdentifier(buf) {
|
|
1328
842
|
var C = ns.prefix + "/bad-extension-value";
|
|
1329
843
|
var n = decodeTop(buf, C, "SubjectKeyIdentifier");
|
|
@@ -1331,21 +845,12 @@ function certExtensionDecoders(ns) {
|
|
|
1331
845
|
catch (e) { throw ns.E(C, "SubjectKeyIdentifier must be an OCTET STRING (RFC 5280 sec. 4.2.1.2)", e); }
|
|
1332
846
|
}
|
|
1333
847
|
|
|
1334
|
-
// signedCertificateTimestampList (RFC 6962 sec. 3.3) -- the extension value is a DER
|
|
1335
|
-
// OCTET STRING wrapping the TLS-encoded SCT list. Delegated to the CT module,
|
|
1336
|
-
// which owns the inner peel + the bounded TLS decode; structure is decoded, the
|
|
1337
|
-
// signature stays raw and unverified. The registry contract is that a decoder
|
|
1338
|
-
// throws a `<prefix>/bad-*` code, so the CT module's own `ct/*` fault is wrapped
|
|
1339
|
-
// and carried as `.cause`, so a foreign namespace never leaks out here.
|
|
1340
848
|
function sctList(buf) {
|
|
1341
849
|
var C = ns.prefix + "/bad-extension-value";
|
|
1342
850
|
try { return ct.parseSctList(buf); }
|
|
1343
851
|
catch (e) { throw ns.E(C, "malformed signedCertificateTimestampList extension value (RFC 6962 sec. 3.3)", e); }
|
|
1344
852
|
}
|
|
1345
853
|
|
|
1346
|
-
// precertificatePoison (RFC 6962 sec. 3.1) -- a critical extension whose value is
|
|
1347
|
-
// exactly ASN.1 NULL (05 00), the marker distinguishing a precertificate from
|
|
1348
|
-
// a final certificate. Content-validated, not merely tag-checked.
|
|
1349
854
|
function precertPoison(buf) {
|
|
1350
855
|
var C = ns.prefix + "/bad-extension-value";
|
|
1351
856
|
var n = decodeTop(buf, C, "PrecertificatePoison");
|
|
@@ -1354,18 +859,6 @@ function certExtensionDecoders(ns) {
|
|
|
1354
859
|
return { poison: true };
|
|
1355
860
|
}
|
|
1356
861
|
|
|
1357
|
-
// cRLDistributionPoints ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint
|
|
1358
|
-
// (RFC 5280 sec. 4.2.1.13); DistributionPoint ::= SEQUENCE {
|
|
1359
|
-
// distributionPoint [0] DistributionPointName OPTIONAL, reasons [1]
|
|
1360
|
-
// ReasonFlags OPTIONAL, cRLIssuer [2] GeneralNames OPTIONAL }. "a
|
|
1361
|
-
// DistributionPoint MUST NOT consist of only the reasons field; either
|
|
1362
|
-
// distributionPoint or cRLIssuer MUST be present". A reasons-only (or
|
|
1363
|
-
// empty) DistributionPoint is malformed. The DistributionPointName is
|
|
1364
|
-
// surfaced through the shared helper (raw name encodings, the sec. 5.2.5
|
|
1365
|
-
// correspondence key); `reasons` stays a raw BIT STRING ({ unusedBits,
|
|
1366
|
-
// bytes }); cRLIssuer is a validated GeneralNames with decoded values.
|
|
1367
|
-
// FreshestCRL ::= CRLDistributionPoints (sec. 4.2.1.15), so the same decoder
|
|
1368
|
-
// serves both OIDs: one codec, both registry rows.
|
|
1369
862
|
function crlDistributionPoints(buf) {
|
|
1370
863
|
var C = ns.prefix + "/bad-crl-distribution-points";
|
|
1371
864
|
var kids = seqChildren(buf, C, "CRLDistributionPoints");
|
|
@@ -1381,18 +874,12 @@ function certExtensionDecoders(ns) {
|
|
|
1381
874
|
if (f.tagNumber <= lastTag) throw ns.E(C, "DistributionPoint fields must be unique and in ascending order (DER)");
|
|
1382
875
|
lastTag = f.tagNumber;
|
|
1383
876
|
if (f.tagNumber === 0) {
|
|
1384
|
-
// distributionPoint [0] EXPLICIT-wraps the DistributionPointName CHOICE.
|
|
1385
877
|
if (!f.children || f.children.length !== 1) throw ns.E(C, "DistributionPoint distributionPoint [0] must wrap exactly one DistributionPointName");
|
|
1386
878
|
out.distributionPoint = distributionPointName(ns, f.children[0], C);
|
|
1387
879
|
} else if (f.tagNumber === 1) {
|
|
1388
880
|
var bs;
|
|
1389
881
|
try { bs = asn1.read.bitStringImplicit(f, 1); }
|
|
1390
882
|
catch (e) { throw ns.E(C, "DistributionPoint reasons [1] must be an IMPLICIT ReasonFlags BIT STRING", e); }
|
|
1391
|
-
// ReasonFlags is a NamedBitList, so DER requires trailing zero bits be
|
|
1392
|
-
// dropped (X.690 sec. 11.2.2), the same rule the keyUsage decoder
|
|
1393
|
-
// enforces above. Without it two different encodings of one reason set
|
|
1394
|
-
// both parse, and RFC 5280 sec. 6.3.3(d)(1) would intersect the CRL's
|
|
1395
|
-
// onlySomeReasons against a value the encoding rules forbid.
|
|
1396
883
|
schema.assertMinimalNamedBits(bs.unusedBits, bs.bytes, function (msg) { throw ns.E(C, "DistributionPoint reasons: " + msg); });
|
|
1397
884
|
out.reasons = { unusedBits: bs.unusedBits, bytes: bs.bytes };
|
|
1398
885
|
} else if (f.tagNumber === 2) {
|
|
@@ -1408,15 +895,11 @@ function certExtensionDecoders(ns) {
|
|
|
1408
895
|
});
|
|
1409
896
|
}
|
|
1410
897
|
|
|
1411
|
-
// qcStatements ::= SEQUENCE OF QCStatement (RFC 3739 sec. 3.2.6). QCStatement ::= SEQUENCE {
|
|
1412
|
-
// statementId OBJECT IDENTIFIER, statementInfo ANY DEFINED BY statementId OPTIONAL }. Known statementIds
|
|
1413
|
-
// (RFC 3739 id-qcs + the ETSI EN 319 412-5 esi4 catalogue) decode their statementInfo; an unknown statementId
|
|
1414
|
-
// is preserved OPAQUE (the RFC 3739 open type), never rejected. The DEFINED-BY selection is an OID re-dispatch.
|
|
1415
898
|
function _qcStr(node, tag, C, what) {
|
|
1416
899
|
if (!node || node.tagClass !== "universal" || node.tagNumber !== tag) throw ns.E(C, what);
|
|
1417
900
|
try { return asn1.read.string(node); } catch (e) { throw ns.E(C, what, e); }
|
|
1418
901
|
}
|
|
1419
|
-
function _qcOidSeq(node, C, what) {
|
|
902
|
+
function _qcOidSeq(node, C, what) {
|
|
1420
903
|
if (!node || node.tagClass !== "universal" || node.tagNumber !== _T.SEQUENCE || !node.children || !node.children.length) throw ns.E(C, what + " must be a non-empty SEQUENCE OF OBJECT IDENTIFIER");
|
|
1421
904
|
return _map(node.children, function (c) {
|
|
1422
905
|
if (c.tagClass !== "universal" || c.tagNumber !== _T.OBJECT_IDENTIFIER) throw ns.E(C, what + " members must be OBJECT IDENTIFIERs");
|
|
@@ -1424,8 +907,8 @@ function certExtensionDecoders(ns) {
|
|
|
1424
907
|
});
|
|
1425
908
|
}
|
|
1426
909
|
function _qcPresenceOnly(nm) { return function (node, C) { if (node) throw ns.E(C, nm + " carries no statementInfo (ETSI EN 319 412-5)"); return null; }; }
|
|
1427
|
-
function _qcSemantics(node, C) {
|
|
1428
|
-
if (node === null) return null;
|
|
910
|
+
function _qcSemantics(node, C) {
|
|
911
|
+
if (node === null) return null;
|
|
1429
912
|
if (node.tagClass !== "universal" || node.tagNumber !== _T.SEQUENCE || !node.children || !node.children.length) throw ns.E(C, "SemanticsInformation must be a SEQUENCE with at least one field (RFC 3739 sec. 3.2.6.1)");
|
|
1430
913
|
var sid = null, i = 0;
|
|
1431
914
|
if (node.children[0].tagClass === "universal" && node.children[0].tagNumber === _T.OBJECT_IDENTIFIER) {
|
|
@@ -1434,36 +917,29 @@ function certExtensionDecoders(ns) {
|
|
|
1434
917
|
}
|
|
1435
918
|
var nras = [];
|
|
1436
919
|
if (node.children[i]) {
|
|
1437
|
-
// nameRegistrationAuthorities ::= SEQUENCE SIZE (1..MAX) OF GeneralName. Decode and fully validate
|
|
1438
|
-
// each element through the shared GeneralName decoder (context tag [0]..[8] + per-alternative
|
|
1439
|
-
// structure); arbitrary bytes never surface as a name-registration authority.
|
|
1440
920
|
nras = schema.walk(generalNames(ns, { decodeValue: true, code: C }), node.children[i], ns).result.names;
|
|
1441
921
|
i++;
|
|
1442
922
|
}
|
|
1443
923
|
if (i !== node.children.length) throw ns.E(C, "SemanticsInformation has unexpected trailing fields");
|
|
1444
924
|
return { semanticsIdentifier: sid, nameRegistrationAuthorities: nras };
|
|
1445
925
|
}
|
|
1446
|
-
// The per-statement decoder table answers off a dotted OID the encoded value supplies, so it holds
|
|
1447
|
-
// no prototype: an inherited name would hand a statement a decoder nothing registered.
|
|
1448
926
|
var qcInfoByOid = _create(null);
|
|
1449
927
|
qcInfoByOid[O("qcCompliance")] = _qcPresenceOnly("QcCompliance");
|
|
1450
928
|
qcInfoByOid[O("qcSSCD")] = _qcPresenceOnly("QcSSCD");
|
|
1451
929
|
qcInfoByOid[O("qcsPkixQCSyntaxV1")] = _qcSemantics;
|
|
1452
930
|
qcInfoByOid[O("qcsPkixQCSyntaxV2")] = _qcSemantics;
|
|
1453
931
|
qcInfoByOid[O("qcType")] = function (node, C) {
|
|
1454
|
-
var oids = _qcOidSeq(node, C, "QcType");
|
|
932
|
+
var oids = _qcOidSeq(node, C, "QcType");
|
|
1455
933
|
return { types: oids, typeNames: _map(oids, function (d) { return ns.oid.name(d) || null; }) };
|
|
1456
934
|
};
|
|
1457
935
|
qcInfoByOid[O("qcRetentionPeriod")] = function (node, C) {
|
|
1458
936
|
if (!node) throw ns.E(C, "QcRetentionPeriod requires an INTEGER statementInfo");
|
|
1459
937
|
return { years: guard.range.uint31(readInt(node, C, "QcRetentionPeriod"), ns.E, C, "QcRetentionPeriod (years)") };
|
|
1460
938
|
};
|
|
1461
|
-
qcInfoByOid[O("qcLimitValue")] = function (node, C) {
|
|
939
|
+
qcInfoByOid[O("qcLimitValue")] = function (node, C) {
|
|
1462
940
|
if (!node || node.tagClass !== "universal" || node.tagNumber !== _T.SEQUENCE || !node.children || node.children.length !== 3) throw ns.E(C, "QcLimitValue must be a MonetaryValue SEQUENCE { currency, amount, exponent }");
|
|
1463
941
|
var cur = node.children[0], currency;
|
|
1464
942
|
if (cur.tagClass === "universal" && cur.tagNumber === _T.PRINTABLE_STRING) {
|
|
1465
|
-
// Route through _qcStr so a disallowed PrintableString byte surfaces the decoder's typed
|
|
1466
|
-
// bad-qc-statement error, not a leaked asn1/bad-printable-string from the raw leaf read.
|
|
1467
943
|
currency = _qcStr(cur, _T.PRINTABLE_STRING, C, "QcLimitValue alphabetic currency must be a PrintableString");
|
|
1468
944
|
if (currency.length !== 3) throw ns.E(C, "QcLimitValue alphabetic currency must be a 3-letter ISO 4217 code");
|
|
1469
945
|
} else if (cur.tagClass === "universal" && cur.tagNumber === _T.INTEGER) {
|
|
@@ -1471,19 +947,15 @@ function certExtensionDecoders(ns) {
|
|
|
1471
947
|
} else throw ns.E(C, "QcLimitValue currency must be a PrintableString(3) or INTEGER(1..999)");
|
|
1472
948
|
return {
|
|
1473
949
|
currency: currency,
|
|
1474
|
-
// MonetaryValue amount and exponent are unconstrained INTEGERs (the amount non-negative in practice);
|
|
1475
|
-
// bound each only to the safe-integer range so it narrows to a Number losslessly. An artificial
|
|
1476
|
-
// tighter bound (uint31, or a small exponent window) would false-reject a valid MonetaryValue, and a
|
|
1477
|
-
// wider one would round silently and mis-state the limit.
|
|
1478
950
|
amount: guard.range.int(readInt(node.children[1], C, "QcLimitValue amount"), 0n, 9007199254740991n, ns.E, C, "QcLimitValue amount"),
|
|
1479
951
|
exponent: guard.range.int(readInt(node.children[2], C, "QcLimitValue exponent"), -9007199254740991n, 9007199254740991n, ns.E, C, "QcLimitValue exponent"),
|
|
1480
952
|
};
|
|
1481
953
|
};
|
|
1482
|
-
qcInfoByOid[O("qcCClegislation")] = function (node, C) {
|
|
954
|
+
qcInfoByOid[O("qcCClegislation")] = function (node, C) {
|
|
1483
955
|
if (!node || node.tagClass !== "universal" || node.tagNumber !== _T.SEQUENCE || !node.children || !node.children.length) throw ns.E(C, "QcCClegislation must be a non-empty SEQUENCE OF CountryName");
|
|
1484
956
|
return { countries: _map(node.children, function (c) { var v = _qcStr(c, _T.PRINTABLE_STRING, C, "QcCClegislation CountryName must be a PrintableString"); if (v.length !== 2) throw ns.E(C, "QcCClegislation CountryName must be a 2-letter ISO 3166-1 code"); return v; }) };
|
|
1485
957
|
};
|
|
1486
|
-
qcInfoByOid[O("qcPDS")] = function (node, C) {
|
|
958
|
+
qcInfoByOid[O("qcPDS")] = function (node, C) {
|
|
1487
959
|
if (!node || node.tagClass !== "universal" || node.tagNumber !== _T.SEQUENCE || !node.children || !node.children.length) throw ns.E(C, "QcPDS must be a non-empty SEQUENCE OF PdsLocation");
|
|
1488
960
|
return { locations: _map(node.children, function (loc) {
|
|
1489
961
|
if (loc.tagClass !== "universal" || loc.tagNumber !== _T.SEQUENCE || !loc.children || loc.children.length !== 2) throw ns.E(C, "a QcPDS PdsLocation must be a SEQUENCE { url, language }");
|
|
@@ -1492,8 +964,6 @@ function certExtensionDecoders(ns) {
|
|
|
1492
964
|
return { url: _qcStr(loc.children[0], _T.IA5_STRING, C, "a QcPDS url must be an IA5String"), language: lang };
|
|
1493
965
|
}) };
|
|
1494
966
|
};
|
|
1495
|
-
// ETSI EN 319 412-5 V2.5.1 additions: QcIdentMethod (SEQUENCE OF OID, like QcType) and
|
|
1496
|
-
// QcQSCDlegislation (SEQUENCE OF CountryName, like QcCClegislation). Both are registered ids, decoded.
|
|
1497
967
|
qcInfoByOid[O("qcIdentMethod")] = function (node, C) {
|
|
1498
968
|
var oids = _qcOidSeq(node, C, "QcIdentMethod");
|
|
1499
969
|
return { methods: oids, methodNames: _map(oids, function (d) { return ns.oid.name(d) || null; }) };
|
|
@@ -1506,7 +976,7 @@ function certExtensionDecoders(ns) {
|
|
|
1506
976
|
var C = ns.prefix + "/bad-qc-statements", CS = ns.prefix + "/bad-qc-statement";
|
|
1507
977
|
var kids = seqChildren(buf, C, "QCStatements");
|
|
1508
978
|
if (!kids.length) throw ns.E(C, "QCStatements must contain at least one QCStatement (RFC 3739 sec. 3.2.6)");
|
|
1509
|
-
var out = [];
|
|
979
|
+
var out = [];
|
|
1510
980
|
for (var i = 0; i < kids.length; i++) {
|
|
1511
981
|
var st = kids[i];
|
|
1512
982
|
if (st.tagClass !== "universal" || st.tagNumber !== _T.SEQUENCE || !st.children || st.children.length < 1 || st.children.length > 2) {
|
|
@@ -1522,10 +992,6 @@ function certExtensionDecoders(ns) {
|
|
|
1522
992
|
return out;
|
|
1523
993
|
}
|
|
1524
994
|
|
|
1525
|
-
// [MS-WCCE] szOID-CERTIFICATE_TEMPLATE. CertificateTemplateOID ::= SEQUENCE {
|
|
1526
|
-
// templateID OID, templateMajorVersion INTEGER OPTIONAL, templateMinorVersion INTEGER OPTIONAL }.
|
|
1527
|
-
// The two versions are DWORDs (0..2^32-1) and not uint31: a CA key/template revision with bit 31
|
|
1528
|
-
// set is a legal DWORD, so bound to the whole safe-integer range, never guard.range.uint31.
|
|
1529
995
|
function msCertificateTemplate(buf) {
|
|
1530
996
|
var C = ns.prefix + "/bad-ms-certificate-template";
|
|
1531
997
|
var kids = seqChildren(buf, C, "CertificateTemplate");
|
|
@@ -1541,8 +1007,6 @@ function certExtensionDecoders(ns) {
|
|
|
1541
1007
|
return { templateID: id, name: ns.oid.name(id) || null, templateMajorVersion: ver(1, "templateMajorVersion"), templateMinorVersion: ver(2, "templateMinorVersion") };
|
|
1542
1008
|
}
|
|
1543
1009
|
|
|
1544
|
-
// [MS-CRTD] szOID-ENROLL_CERTTYPE_EXTENSION carries the legacy v1 template name as a BARE BMPString.
|
|
1545
|
-
// asn1.read.string accepts any universal string tag, so the explicit BMP_STRING assert is the guard.
|
|
1546
1010
|
function msEnrollCertType(buf) {
|
|
1547
1011
|
var C = ns.prefix + "/bad-ms-enroll-cert-type";
|
|
1548
1012
|
var n = decodeTop(buf, C, "EnrollCertType");
|
|
@@ -1550,7 +1014,6 @@ function certExtensionDecoders(ns) {
|
|
|
1550
1014
|
try { return asn1.read.string(n); } catch (e) { throw ns.E(C, "the enroll cert-type name must be a well-formed BMPString", e); }
|
|
1551
1015
|
}
|
|
1552
1016
|
|
|
1553
|
-
// [MS-WCCE] szOID-CERTSRV_CA_VERSION is an INTEGER (0..2^32-1) = (caKeyIndex << 16) | certIndex.
|
|
1554
1017
|
function msCaVersion(buf) {
|
|
1555
1018
|
var C = ns.prefix + "/bad-ms-ca-version";
|
|
1556
1019
|
var n = decodeTop(buf, C, "CACertVersion");
|
|
@@ -1559,10 +1022,6 @@ function certExtensionDecoders(ns) {
|
|
|
1559
1022
|
return { caVersion: v, caKeyIndex: v >>> 16, certIndex: v & 0xffff };
|
|
1560
1023
|
}
|
|
1561
1024
|
|
|
1562
|
-
// [MS-WCCE] szOID-CERTSRV_PREVIOUS_CERT_HASH holds the SHA-1 thumbprint of the prior CA certificate.
|
|
1563
|
-
// AD CS stamps the Windows certificate THUMBPRINT here, which Windows computes with SHA-1 regardless
|
|
1564
|
-
// of the certificate's signature algorithm, so the OCTET STRING is exactly 20 octets. An empty or
|
|
1565
|
-
// other-length value is malformed renewal metadata and fails closed.
|
|
1566
1025
|
function msPreviousCertHash(buf) {
|
|
1567
1026
|
var C = ns.prefix + "/bad-ms-previous-cert-hash";
|
|
1568
1027
|
var n = decodeTop(buf, C, "CAPrevCertHash");
|
|
@@ -1573,9 +1032,6 @@ function certExtensionDecoders(ns) {
|
|
|
1573
1032
|
return hash;
|
|
1574
1033
|
}
|
|
1575
1034
|
|
|
1576
|
-
// The extension-decoder registry every consumer indexes with an OID read off the wire
|
|
1577
|
-
// (path validation, inspection, the ACME certificate binding), so it holds no prototype: a
|
|
1578
|
-
// planted dotted name would otherwise hand an extension a decoder nothing registered.
|
|
1579
1035
|
var byOid = _create(null);
|
|
1580
1036
|
byOid[O("qcStatements")] = qcStatements;
|
|
1581
1037
|
byOid[O("basicConstraints")] = basicConstraints;
|
|
@@ -1599,21 +1055,12 @@ function certExtensionDecoders(ns) {
|
|
|
1599
1055
|
byOid[O("msEnrollCertType")] = msEnrollCertType;
|
|
1600
1056
|
byOid[O("msCaVersion")] = msCaVersion;
|
|
1601
1057
|
byOid[O("msPreviousCertHash")] = msPreviousCertHash;
|
|
1602
|
-
byOid[O("msApplicationPolicies")] = certificatePolicies;
|
|
1058
|
+
byOid[O("msApplicationPolicies")] = certificatePolicies;
|
|
1603
1059
|
return { byOid: byOid };
|
|
1604
1060
|
}
|
|
1605
1061
|
|
|
1606
|
-
// Extension ::= SEQUENCE { extnID OID, critical BOOLEAN DEFAULT FALSE,
|
|
1607
|
-
// extnValue OCTET STRING }. `critical` is a universal BOOLEAN present-by-count
|
|
1608
|
-
// (not context-tagged), so the per-extension decode handles the 2-vs-3-child
|
|
1609
|
-
// shape directly; the seqOf centralizes the SEQUENCE / SIZE(1..MAX) assertion
|
|
1610
|
-
// and the RFC 5280 sec. 4.2 per-OID uniqueness.
|
|
1611
1062
|
function extension(ns) {
|
|
1612
1063
|
return schema.decode(function (ext) {
|
|
1613
|
-
// Extension ::= SEQUENCE { extnID, critical DEFAULT FALSE, extnValue } is a
|
|
1614
|
-
// UNIVERSAL SEQUENCE of exactly 2 (critical omitted) or 3 children. A
|
|
1615
|
-
// context-tagged item (e.g. [5]{OID, OCTET STRING}) or a wrong child count
|
|
1616
|
-
// is malformed; assert the tag, don't just count children (fail closed).
|
|
1617
1064
|
if (!ext.children || ext.tagClass !== "universal" || ext.tagNumber !== asn1.TAGS.SEQUENCE ||
|
|
1618
1065
|
ext.children.length < 2 || ext.children.length > 3) {
|
|
1619
1066
|
throw ns.E(ns.prefix + "/bad-extension", "Extension must be a SEQUENCE of {extnID, critical?, extnValue}");
|
|
@@ -1622,8 +1069,6 @@ function extension(ns) {
|
|
|
1622
1069
|
var critical = false, valueNode;
|
|
1623
1070
|
if (ext.children.length === 3) {
|
|
1624
1071
|
critical = asn1.read.boolean(ext.children[1]);
|
|
1625
|
-
// critical is BOOLEAN DEFAULT FALSE -- DER omits the field when false, so an
|
|
1626
|
-
// explicitly-encoded FALSE is a non-canonical form; reject it fail-closed.
|
|
1627
1072
|
if (critical === false) throw ns.E(ns.prefix + "/bad-extension", "an explicit critical FALSE must be omitted (BOOLEAN DEFAULT FALSE)");
|
|
1628
1073
|
valueNode = ext.children[2];
|
|
1629
1074
|
} else {
|
|
@@ -1632,10 +1077,6 @@ function extension(ns) {
|
|
|
1632
1077
|
return { oid: extnID, name: ns.oid.name(extnID) || null, critical: critical, value: asn1.read.octetString(valueNode) };
|
|
1633
1078
|
});
|
|
1634
1079
|
}
|
|
1635
|
-
// opts.implicitTag (optional): read Extensions as a [tag] IMPLICIT SEQUENCE OF
|
|
1636
|
-
// Extension, where the context tag REPLACES the universal SEQUENCE tag, for the CRMF
|
|
1637
|
-
// CertTemplate extensions [9] (RFC 4211 sec. 5). With no opts the shape is a bare
|
|
1638
|
-
// universal SEQUENCE OF, byte-identical to every existing caller.
|
|
1639
1080
|
function extensions(ns, opts) {
|
|
1640
1081
|
opts = opts || {};
|
|
1641
1082
|
var EXT = extension(ns);
|
|
@@ -1648,15 +1089,6 @@ function extensions(ns, opts) {
|
|
|
1648
1089
|
return schema.seqOf(EXT, _assign({ assert: "sequence" }, extOpts));
|
|
1649
1090
|
}
|
|
1650
1091
|
|
|
1651
|
-
// SubjectPublicKeyInfo ::= SEQUENCE { algorithm AlgorithmIdentifier,
|
|
1652
|
-
// subjectPublicKey BIT STRING } (RFC 5280 sec. 4.1.2.7, RFC 2986 sec. 4.1). Asserted as a
|
|
1653
|
-
// universal SEQUENCE, so a context-tagged or SET-tagged constructed node carrying
|
|
1654
|
-
// two well-formed children is not a SubjectPublicKeyInfo. Shared by the
|
|
1655
|
-
// certificate and CSR parsers.
|
|
1656
|
-
// opts.implicitTag (optional): read the SubjectPublicKeyInfo as a [tag] IMPLICIT
|
|
1657
|
-
// SEQUENCE (a context-class constructed node whose children are algorithm +
|
|
1658
|
-
// subjectPublicKey), for the CRMF CertTemplate publicKey [6] (RFC 4211 sec. 5). With no
|
|
1659
|
-
// opts the shape is a universal SEQUENCE, byte-identical to every existing caller.
|
|
1660
1092
|
function spki(ns, opts) {
|
|
1661
1093
|
opts = opts || {};
|
|
1662
1094
|
return schema.seq([
|
|
@@ -1669,22 +1101,12 @@ function spki(ns, opts) {
|
|
|
1669
1101
|
return {
|
|
1670
1102
|
algorithm: m.fields.algorithm.value.result,
|
|
1671
1103
|
publicKey: { unusedBits: m.fields.subjectPublicKey.value.unusedBits, bytes: m.fields.subjectPublicKey.value.bytes },
|
|
1672
|
-
// `bytes` is the importable SubjectPublicKeyInfo DER. In IMPLICIT [tag]
|
|
1673
|
-
// mode the wire node leads with the context tag, so recover the SEQUENCE
|
|
1674
|
-
// encoding a consumer imports / hashes, and not the [tag] wire form.
|
|
1675
1104
|
bytes: opts.implicitTag != null ? asn1.sequenceTlv(m.node) : m.node.bytes,
|
|
1676
1105
|
};
|
|
1677
1106
|
},
|
|
1678
1107
|
});
|
|
1679
1108
|
}
|
|
1680
1109
|
|
|
1681
|
-
// Attribute ::= SEQUENCE { type OBJECT IDENTIFIER, values SET OF AttributeValue }.
|
|
1682
|
-
// AttributeValue is ANY, kept as raw DER (node.bytes), so an unrecognized attribute
|
|
1683
|
-
// type never fails the parse. values is SET SIZE (1..MAX) by default: an empty SET
|
|
1684
|
-
// is rejected; there is NO SET-OF uniqueness. Shared by the CSR (requested
|
|
1685
|
-
// attributes, RFC 2986 sec. 4.1) and PKCS#8 (private-key attributes, RFC 5958 sec. 2).
|
|
1686
|
-
// opts.minValues (default 1) lowers the floor to 0 for the EST CsrAttrs key-type
|
|
1687
|
-
// hints, whose empty values SET means "any key of this type" (RFC 9908 sec. 3.2).
|
|
1688
1110
|
function attribute(ns, opts) {
|
|
1689
1111
|
opts = opts || {};
|
|
1690
1112
|
var minValues = opts.minValues === undefined ? 1 : opts.minValues;
|
|
@@ -1705,14 +1127,6 @@ function attribute(ns, opts) {
|
|
|
1705
1127
|
});
|
|
1706
1128
|
}
|
|
1707
1129
|
|
|
1708
|
-
// Certificate, CertificateList, and CertificationRequest share one outer shape:
|
|
1709
|
-
// SEQUENCE { toBeSigned SEQUENCE, signatureAlgorithm, signatureValue }. This
|
|
1710
|
-
// returns the first element (the to-be-signed info) when `root` is that
|
|
1711
|
-
// SEQUENCE-of-exactly-3 whose first child is itself a constructed universal
|
|
1712
|
-
// SEQUENCE, or null otherwise. Every format's `matches` detector shares this
|
|
1713
|
-
// preamble, so the signed-envelope shape is recognized in one place and the three
|
|
1714
|
-
// detectors cannot drift on it (the CRL detector historically omitted the
|
|
1715
|
-
// tbs-is-universal check this recovers).
|
|
1716
1130
|
function signedEnvelopeTbs(root) {
|
|
1717
1131
|
if (!schema.isUniversal(root, asn1.TAGS.SEQUENCE)) return null;
|
|
1718
1132
|
if (!root.children || root.children.length !== 3) return null;
|
|
@@ -1721,10 +1135,6 @@ function signedEnvelopeTbs(root) {
|
|
|
1721
1135
|
return tbs;
|
|
1722
1136
|
}
|
|
1723
1137
|
|
|
1724
|
-
// rootSequenceChildren(root, minLen, maxLen) -- the shared root-shape guard the
|
|
1725
|
-
// format matches() detectors re-inline: returns root.children when root is a
|
|
1726
|
-
// NON-EMPTY universal SEQUENCE whose child count is within [minLen, maxLen]
|
|
1727
|
-
// (maxLen optional/Infinity), else null. Sibling of signedEnvelopeTbs.
|
|
1728
1138
|
function rootSequenceChildren(root, minLen, maxLen) {
|
|
1729
1139
|
if (!schema.isUniversal(root, asn1.TAGS.SEQUENCE) || !root.children) return null;
|
|
1730
1140
|
var n = root.children.length;
|
|
@@ -1733,49 +1143,15 @@ function rootSequenceChildren(root, minLen, maxLen) {
|
|
|
1733
1143
|
return root.children;
|
|
1734
1144
|
}
|
|
1735
1145
|
|
|
1736
|
-
// Every format's `parse` is the shared runParse bound to that format's identity
|
|
1737
|
-
// (PEM label, error class, error-code prefix, top-level schema). This returns the
|
|
1738
|
-
// bound parser so a format declares its configuration once and never re-writes the
|
|
1739
|
-
// coerce -> decode -> walk wrapper. `opts`: { pemLabel, PemError, ErrorClass,
|
|
1740
|
-
// prefix, what, topSchema, ns }.
|
|
1741
|
-
// The top-level schema's reject code, the verdict for a DER that is not this
|
|
1742
|
-
// structure at all, is `<prefix>/not-a-<structure>` (x509/not-a-certificate,
|
|
1743
|
-
// cms/not-a-content-info, ...). Three codes predate that convention and stay
|
|
1744
|
-
// frozen on the public error-code surface (ocsp/bad-ocsp-response,
|
|
1745
|
-
// tsp/bad-response, crmf/bad-cert-req-messages); a new format follows the
|
|
1746
|
-
// not-a-* form, never those precedents.
|
|
1747
1146
|
function makeParser(opts) {
|
|
1748
1147
|
return function (input) { return runParse(input, opts); };
|
|
1749
1148
|
}
|
|
1750
1149
|
|
|
1751
|
-
// makeRecordingParser(opts, kind) -> a parser that also records the bytes it read.
|
|
1752
|
-
//
|
|
1753
|
-
// The pairing lives here and is not repeated per format, because it is one decision, not four: a
|
|
1754
|
-
// structure whose parse feeds a VERDICT needs its fields bound to the byte string they came from,
|
|
1755
|
-
// since a parsed structure presents the signed range, the signature and the fields that range
|
|
1756
|
-
// encodes as separate properties, and a caller can hand back a genuine range beside substituted
|
|
1757
|
-
// fields. `kind` is the tag the matching door re-derives under, so a certificate cannot be presented
|
|
1758
|
-
// where a CRL is expected. The record lives in guard-parsed, off the object, unreachable through it.
|
|
1759
1150
|
function makeRecordingParser(opts, kind) {
|
|
1760
1151
|
return guard.parsed.recordingParser(kind, makeParser(opts), opts.ErrorClass,
|
|
1761
1152
|
opts.prefix + "/bad-input", "a " + opts.what);
|
|
1762
1153
|
}
|
|
1763
1154
|
|
|
1764
|
-
// The X.509 SIGNED{ToBeSigned} macro (RFC 5280 sec. 4.1.1.3): the outer
|
|
1765
|
-
// SEQUENCE { toBeSigned, signatureAlgorithm AlgorithmIdentifier,
|
|
1766
|
-
// signatureValue BIT STRING } shared by Certificate, CertificateList and
|
|
1767
|
-
// CertificationRequest. `tbsSchema` parses the first element; the SEQUENCE-of-3
|
|
1768
|
-
// shape, the arity, the signature extraction and the raw tbs / outer-signature
|
|
1769
|
-
// bytes (for the cert/CRL outer==inner agreement check) are owned here once, and
|
|
1770
|
-
// each format's `opts.build(envelope, ctx)` shapes its own object from the
|
|
1771
|
-
// envelope. A CSR's build simply omits the agreement check, since its CRI has no inner
|
|
1772
|
-
// signature AlgorithmIdentifier. The omission is structural, not a copy that
|
|
1773
|
-
// forgot a guard.
|
|
1774
|
-
// signatureValue is surfaced raw as { unusedBits, bytes }: for the envelope
|
|
1775
|
-
// family, octet alignment is a verify-layer rule (path-validate rejects
|
|
1776
|
-
// unusedBits != 0 for certification paths; an external verifier must do the
|
|
1777
|
-
// same). A format with no in-tree verify layer (OCSP, CMP protection, CRMF
|
|
1778
|
-
// POP) instead enforces alignment at parse.
|
|
1779
1155
|
function signedEnvelope(ns, tbsSchema, opts) {
|
|
1780
1156
|
return schema.seq([
|
|
1781
1157
|
schema.field("toBeSigned", tbsSchema),
|
|
@@ -1787,8 +1163,8 @@ function signedEnvelope(ns, tbsSchema, opts) {
|
|
|
1787
1163
|
var tbsMatch = m.fields.toBeSigned.value;
|
|
1788
1164
|
var sigBits = m.fields.signatureValue.value;
|
|
1789
1165
|
return opts.build({
|
|
1790
|
-
tbsMatch: tbsMatch,
|
|
1791
|
-
tbsBytes: tbsMatch.node.bytes,
|
|
1166
|
+
tbsMatch: tbsMatch,
|
|
1167
|
+
tbsBytes: tbsMatch.node.bytes,
|
|
1792
1168
|
outerSignatureAlgorithmBytes: m.fields.signatureAlgorithm.node.bytes,
|
|
1793
1169
|
signatureAlgorithm: m.fields.signatureAlgorithm.value.result,
|
|
1794
1170
|
signatureValue: { unusedBits: sigBits.unusedBits, bytes: sigBits.bytes },
|
|
@@ -1797,13 +1173,6 @@ function signedEnvelope(ns, tbsSchema, opts) {
|
|
|
1797
1173
|
});
|
|
1798
1174
|
}
|
|
1799
1175
|
|
|
1800
|
-
// dNSName syntax (RFC 5280 sec. 4.2.1.6 / RFC 1034 preferred name syntax, a representative CABF check): no
|
|
1801
|
-
// whitespace, no leading/trailing dot, no empty label, no underscore, LDH labels (1-63 octets, no leading /
|
|
1802
|
-
// trailing hyphen), <= 253 octets, an optional leftmost "*" wildcard. Returns a reason string, or null when
|
|
1803
|
-
// well-formed. The shared home so the linter AND an identity comparator (case-fold only a valid dNSName) agree.
|
|
1804
|
-
// One LDH label (RFC 1034 preferred syntax), decided character by character: every character an
|
|
1805
|
-
// ASCII letter/digit/hyphen, and a hyphen neither first nor last. The caller has already rejected
|
|
1806
|
-
// any label carrying a dot, whitespace, or underscore, so only the LDH shape remains to check.
|
|
1807
1176
|
function _isLdhLabel(s) {
|
|
1808
1177
|
var n = s.length;
|
|
1809
1178
|
if (n === 0) return false;
|
|
@@ -1841,12 +1210,12 @@ module.exports = {
|
|
|
1841
1210
|
pemDecodeAll: pemDecodeAll,
|
|
1842
1211
|
pemDecodeLenient: pemDecodeLenient,
|
|
1843
1212
|
pemEncode: pemEncode,
|
|
1844
|
-
isJsWhitespace: _isJsWhitespace,
|
|
1845
|
-
stripBase64Whitespace: _stripB64Whitespace,
|
|
1846
|
-
charTable: _charTable,
|
|
1847
|
-
allCharsIn: _allCharsIn,
|
|
1213
|
+
isJsWhitespace: _isJsWhitespace,
|
|
1214
|
+
stripBase64Whitespace: _stripB64Whitespace,
|
|
1215
|
+
charTable: _charTable,
|
|
1216
|
+
allCharsIn: _allCharsIn,
|
|
1848
1217
|
coerceToDer: coerceToDer,
|
|
1849
|
-
isPemArmor: _isPemArmor,
|
|
1218
|
+
isPemArmor: _isPemArmor,
|
|
1850
1219
|
decodeRoot: decodeRoot,
|
|
1851
1220
|
runParse: runParse,
|
|
1852
1221
|
makeNS: makeNS,
|