@blamejs/pki 0.5.7 → 0.5.8
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 +405 -386
- package/MIGRATING.md +43 -0
- package/README.md +12 -12
- package/lib/acme.js +31 -31
- package/lib/asn1-der.js +10 -10
- package/lib/attrcert-sign.js +19 -19
- package/lib/byte-reader.js +6 -6
- package/lib/byte-writer.js +5 -5
- package/lib/cbor-det.js +27 -24
- package/lib/cmc-build.js +97 -92
- package/lib/cmc-verify.js +106 -30
- package/lib/cmp-build.js +25 -25
- package/lib/cmp-session.js +70 -70
- package/lib/cmp-verify.js +71 -57
- package/lib/cms-compress.js +7 -7
- package/lib/cms-decrypt.js +90 -72
- package/lib/cms-encrypt.js +32 -32
- package/lib/cms-sign.js +74 -55
- package/lib/cms-verify.js +97 -75
- package/lib/composite-sig.js +13 -13
- package/lib/constants.js +4 -4
- package/lib/crl-sign.js +22 -22
- package/lib/crl-verify.js +7 -6
- package/lib/crmf-sign.js +14 -14
- package/lib/csr-sign.js +8 -8
- package/lib/ct.js +37 -37
- package/lib/edwards-point.js +7 -7
- package/lib/est.js +98 -55
- package/lib/framework-error.js +5 -5
- package/lib/guard-all.js +3 -3
- package/lib/guard-async.js +4 -4
- package/lib/guard-bytes.js +79 -79
- package/lib/guard-compress.js +17 -17
- package/lib/guard-crypto.js +1 -1
- package/lib/guard-encoding.js +15 -15
- package/lib/guard-header.js +3 -3
- package/lib/guard-identifier.js +16 -16
- package/lib/guard-json.js +15 -15
- package/lib/guard-limits.js +7 -7
- package/lib/guard-name.js +81 -16
- package/lib/guard-parsed.js +80 -80
- package/lib/guard-range.js +19 -19
- package/lib/guard-secret.js +11 -10
- package/lib/guard-text.js +6 -6
- package/lib/guard-time.js +10 -10
- package/lib/hpke.js +18 -17
- package/lib/http-digest.js +35 -35
- package/lib/http-retry-after.js +13 -13
- package/lib/http-transport.js +20 -19
- package/lib/inspect.js +53 -53
- package/lib/ip-utils.js +2 -2
- package/lib/jose.js +13 -13
- package/lib/key.js +16 -16
- package/lib/lint.js +51 -51
- package/lib/merkle.js +51 -36
- package/lib/mime.js +18 -18
- package/lib/ocsp-verify.js +10 -10
- package/lib/ocsp.js +13 -13
- package/lib/oid.js +29 -29
- package/lib/path-validate.js +114 -113
- package/lib/pbes2.js +16 -16
- package/lib/pkcs12-build.js +53 -53
- package/lib/pki-build.js +21 -19
- package/lib/rc2.js +1 -1
- package/lib/rfc3339.js +5 -5
- package/lib/schema-all.js +31 -31
- package/lib/schema-attrcert.js +12 -12
- package/lib/schema-c509.js +144 -142
- package/lib/schema-cmc.js +58 -58
- package/lib/schema-cmp.js +43 -43
- package/lib/schema-cms.js +45 -45
- package/lib/schema-crl.js +7 -7
- package/lib/schema-crmf.js +28 -28
- package/lib/schema-csr.js +12 -12
- package/lib/schema-csrattrs.js +16 -16
- package/lib/schema-engine.js +18 -18
- package/lib/schema-ocsp.js +15 -15
- package/lib/schema-pkcs12.js +20 -20
- package/lib/schema-pkcs8.js +2 -2
- package/lib/schema-pkix.js +131 -126
- package/lib/schema-smime.js +19 -19
- package/lib/schema-tsp.js +12 -12
- package/lib/schema-x509.js +3 -3
- package/lib/shbs.js +18 -18
- package/lib/sign-scheme.js +13 -13
- package/lib/sigstore.js +10 -11
- package/lib/sleep.js +1 -1
- package/lib/smime.js +308 -96
- package/lib/tls-cert-compress.js +18 -18
- package/lib/trust.js +27 -27
- package/lib/tsp-sign.js +17 -17
- package/lib/validator-all.js +1 -1
- package/lib/validator-attcert.js +1 -1
- package/lib/validator-cose.js +43 -44
- package/lib/validator-keydesc.js +3 -3
- package/lib/validator-sig.js +13 -13
- package/lib/validator-tls.js +11 -11
- package/lib/validator-tpm.js +20 -19
- package/lib/webauthn-mds.js +66 -66
- package/lib/webauthn.js +33 -33
- package/lib/webcrypto.js +15 -15
- package/lib/x509-sign.js +13 -13
- package/package.json +3 -2
- package/sbom.cdx.json +6 -6
package/lib/schema-pkix.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// returns an asn1-schema that walks the corresponding ASN.1 structure and emits
|
|
11
11
|
// the caller's own <prefix>/* error codes. x509.js, crl.js, and future CMS/CSR
|
|
12
12
|
// parsers compose these so AlgorithmIdentifier / Name / Extension are defined
|
|
13
|
-
// once, not re-derived per format. This module is internal infrastructure
|
|
13
|
+
// once, not re-derived per format. This module is internal infrastructure: the
|
|
14
14
|
// operator-facing surface is the parsers that consume it.
|
|
15
15
|
|
|
16
16
|
var asn1 = require("./asn1-der");
|
|
@@ -36,7 +36,7 @@ var PEM_RE = /-----BEGIN ([A-Z0-9 ]+)-----([\s\S]*?)-----END \1-----/;
|
|
|
36
36
|
// lenient Buffer.from would otherwise let several distinct PEM texts alias one
|
|
37
37
|
// DER (PEM-layer malleability) and silently drop trailing content bits.
|
|
38
38
|
function pemDecode(text, label, PemError) {
|
|
39
|
-
// guard.text.decode caps the raw byte length
|
|
39
|
+
// guard.text.decode caps the raw byte length before the latin1 string copy:
|
|
40
40
|
// converting first would allocate a full-size string for input the cap is
|
|
41
41
|
// about to reject, and a buffer above Node's max string length would escape as
|
|
42
42
|
// an untyped ERR_STRING_TOO_LONG instead of pem/too-large.
|
|
@@ -51,7 +51,7 @@ function pemDecode(text, label, PemError) {
|
|
|
51
51
|
return guard.encoding.base64(b64, null, function (c, msg) { return new PemError(c, msg); }, "pem/bad-base64", "PEM base64 body");
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
// pemDecodeAll(text, label, PemError): decode
|
|
54
|
+
// pemDecodeAll(text, label, PemError): decode every PEM block under the strict
|
|
55
55
|
// RFC 7468 profile for application/pem-certificate-chain (RFC 8555 sec. 9.1) --
|
|
56
56
|
// each block must carry `label` (default CERTIFICATE), explanatory text MUST NOT
|
|
57
57
|
// appear before/between/after blocks (errata 5983: certchain = stricttextualmsg
|
|
@@ -81,13 +81,13 @@ function pemDecodeAll(text, label, PemError) {
|
|
|
81
81
|
// alphanumeric words separated by single spaces, a subset of the RFC 7468
|
|
82
82
|
// sec. 3 label grammar). Anything else (lowercase, a leading/trailing space,
|
|
83
83
|
// an embedded "-----" or newline) would emit armor pemDecode itself rejects
|
|
84
|
-
// or corrupt the armor grammar
|
|
84
|
+
// or corrupt the armor grammar: a config-time authoring fault, thrown here.
|
|
85
85
|
function pemEncode(der, label, PemError) {
|
|
86
86
|
if (typeof label !== "string" || !/^[A-Z0-9]+( [A-Z0-9]+)*$/.test(label)) {
|
|
87
87
|
throw new PemError("pem/bad-label", "pemEncode requires an uppercase A-Z0-9 label with single spaces");
|
|
88
88
|
}
|
|
89
89
|
// Re-view through the byte guard: a string input would silently utf8-armor into
|
|
90
|
-
// a bogus PEM, and a detached-backed Buffer would armor an empty body
|
|
90
|
+
// a bogus PEM, and a detached-backed Buffer would armor an empty body; both
|
|
91
91
|
// fail closed (a Uint8Array / Buffer of real DER re-views cleanly).
|
|
92
92
|
var buf = guard.bytes.view(der, PemError, "pem/bad-input", "pemEncode DER input");
|
|
93
93
|
var b64 = buf.toString("base64").replace(/(.{64})/g, "$1\n").replace(/\n$/, "");
|
|
@@ -101,7 +101,7 @@ function coerceToDer(input, opts) {
|
|
|
101
101
|
if (typeof input === "string") return pemDecode(input, opts.pemLabel, opts.PemError);
|
|
102
102
|
if (Buffer.isBuffer(input) || input instanceof Uint8Array) {
|
|
103
103
|
// Route the DER Buffer / Uint8Array through the shared byte guard so a
|
|
104
|
-
// detached backing store fails closed here for
|
|
104
|
+
// detached backing store fails closed here for every format that composes
|
|
105
105
|
// this boundary (x509/crl/csr/pkcs8/cms/pkcs12), not per-format.
|
|
106
106
|
var buf = guard.bytes.view(input, opts.ErrorClass, opts.prefix + "/bad-input", "parse");
|
|
107
107
|
return _isPemArmor(buf) ? pemDecode(buf, opts.pemLabel, opts.PemError) : buf;
|
|
@@ -109,11 +109,11 @@ function coerceToDer(input, opts) {
|
|
|
109
109
|
throw new opts.ErrorClass(opts.prefix + "/bad-input", "parse expects a DER Buffer or a PEM string");
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
// Does a Buffer carry PEM armor (a .pem read with fs.readFileSync)
|
|
112
|
+
// Does a Buffer carry PEM armor (a .pem read with fs.readFileSync) instead of
|
|
113
113
|
// raw DER? It does iff "-----BEGIN" appears and everything before it is TEXT
|
|
114
|
-
// (UTF-8 BOM / whitespace / RFC 7468 explanatory preamble). DER is binary
|
|
115
|
-
// leading tag+length bytes are non-printable
|
|
116
|
-
// SET / INTEGER) is
|
|
114
|
+
// (UTF-8 BOM / whitespace / RFC 7468 explanatory preamble). DER is binary and its
|
|
115
|
+
// leading tag+length bytes are non-printable, so a non-SEQUENCE DER (a bare
|
|
116
|
+
// SET / INTEGER) is not misrouted here; it decodes and fails closed structurally.
|
|
117
117
|
function _isPemArmor(buf) {
|
|
118
118
|
var head = buf.slice(0, 4096).toString("latin1");
|
|
119
119
|
var idx = head.indexOf("-----BEGIN");
|
|
@@ -130,9 +130,9 @@ function _isPemArmor(buf) {
|
|
|
130
130
|
// Decode the DER root, wrapping a codec fault in the caller's <prefix>/bad-der.
|
|
131
131
|
function decodeRoot(der, opts) {
|
|
132
132
|
// A format whose wire encoding is normatively BER (RFC 7292 PKCS#12) opts
|
|
133
|
-
// in to the codec's ber mode for the
|
|
134
|
-
// not always a decode-time throw
|
|
135
|
-
// OCTET STRING decodes strictly and only diverges at the typed reader
|
|
133
|
+
// in to the codec's ber mode for the whole decode. BER-vs-DER divergence is
|
|
134
|
+
// not always a decode-time throw: a definite-length constructed IMPLICIT
|
|
135
|
+
// OCTET STRING decodes strictly and only diverges at the typed reader, so
|
|
136
136
|
// a strict-first-retry-on-throw boundary would miss legal BER shapes. A
|
|
137
137
|
// strict-DER input decodes identically under the ber mode, every other
|
|
138
138
|
// strictness verdict included.
|
|
@@ -150,7 +150,7 @@ function runParse(input, opts) {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
// Distinguished-name attribute short labels (RFC 4514 sec. 3 + common use).
|
|
153
|
-
// RFC 4514 short names for the DN string representation
|
|
153
|
+
// RFC 4514 short names for the DN string representation; the single source is
|
|
154
154
|
// pki.C.NAMES.DN_SHORT (shared with the human-readable renderer so the two can't drift).
|
|
155
155
|
var DN_SHORT = constants.NAMES.DN_SHORT;
|
|
156
156
|
|
|
@@ -158,7 +158,7 @@ var DN_SHORT = constants.NAMES.DN_SHORT;
|
|
|
158
158
|
// The error/OID namespace every format module walks its schema under:
|
|
159
159
|
// { prefix, E:(code,message,cause)=>new ErrorClass(...), oid }. Factored so a
|
|
160
160
|
// format declares it in one line instead of repeating the error-constructor
|
|
161
|
-
// closure (a caller that never passes a cause is unaffected
|
|
161
|
+
// closure (a caller that never passes a cause is unaffected, since withCause ignores
|
|
162
162
|
// an undefined third arg).
|
|
163
163
|
function makeNS(prefix, ErrorClass, oidModule) {
|
|
164
164
|
return { prefix: prefix, E: function (code, message, cause) { return new ErrorClass(code, message, cause); }, oid: oidModule };
|
|
@@ -166,9 +166,9 @@ function makeNS(prefix, ErrorClass, oidModule) {
|
|
|
166
166
|
|
|
167
167
|
// A bounded universal-INTEGER version reader. `accept` maps each legal wire value
|
|
168
168
|
// (as a decimal string) to its surfaced version number; any other value is a
|
|
169
|
-
// <prefix>/bad-version fault. The one genuine per-format divergence
|
|
169
|
+
// <prefix>/bad-version fault. The one genuine per-format divergence, the cert
|
|
170
170
|
// rejects 0 and maps 1->2/2->3, a CRL accepts only 1->2, a CSR only 0->1, a PKCS#8
|
|
171
|
-
// 0->1/1->2
|
|
171
|
+
// 0->1/1->2, is expressed purely as the accept map (RFC 5280 sec. 4.1.2.1 / sec. 5.1.2.1,
|
|
172
172
|
// RFC 2986 sec. 4.1, RFC 5958 sec. 2). read.integer is strict, so an ENUMERATED-tagged
|
|
173
173
|
// version is rejected at the leaf (asn1/*).
|
|
174
174
|
function versionReader(ns, accept) {
|
|
@@ -185,11 +185,11 @@ function versionReader(ns, accept) {
|
|
|
185
185
|
// universal SEQUENCE, byte-identical to every existing caller.
|
|
186
186
|
//
|
|
187
187
|
// Parameter-rule tiering: only the params-MUST-BE-ABSENT families (ML-DSA,
|
|
188
|
-
// SLH-DSA, the Edwards/Montgomery curves
|
|
188
|
+
// SLH-DSA, the Edwards/Montgomery curves), new registrations with no legacy
|
|
189
189
|
// corpus) are enforced at parse time, in the build below. The CLASSICAL
|
|
190
190
|
// per-OID rules (RSA parameters MUST be NULL, RFC 3279 sec. 2.2.1/2.3.1;
|
|
191
191
|
// ecdsa-with-SHA2 parameters MUST be omitted, RFC 5758 sec. 3.2) are
|
|
192
|
-
// deliberately
|
|
192
|
+
// deliberately not parse-time rejects: a long tail of deployed certificates
|
|
193
193
|
// omits the RSA NULL, so the parse tier stays interop-lenient for those
|
|
194
194
|
// families and the strict classical check runs on the signature-verification
|
|
195
195
|
// path (path-validate's signature-algorithm registry), where the verdict
|
|
@@ -205,11 +205,11 @@ function algorithmIdentifier(ns, opts) {
|
|
|
205
205
|
arity: { min: 1 }, code: ns.prefix + "/bad-algorithm-identifier", what: "AlgorithmIdentifier",
|
|
206
206
|
build: function (m, ctx) {
|
|
207
207
|
var dotted = m.fields.algorithm.value;
|
|
208
|
-
//
|
|
208
|
+
// The registry marks several algorithm families parameters-absent: the PQC
|
|
209
209
|
// signature families (ML-DSA RFC 9881, SLH-DSA RFC 9909/9814), the
|
|
210
210
|
// Edwards/Montgomery curves (RFC 8410), ML-KEM (RFC 9936), and the HKDF
|
|
211
|
-
// identifiers (RFC 8619)
|
|
212
|
-
// explicit NULL, no bytes. Enforced ONCE here so every consumer of the
|
|
211
|
+
// identifiers (RFC 8619). For all of them the `parameters` field MUST be
|
|
212
|
+
// absent: no explicit NULL, no bytes. Enforced ONCE here so every consumer of the
|
|
213
213
|
// shared AlgorithmIdentifier inherits the rule; the guard-parity bug
|
|
214
214
|
// class is structurally impossible.
|
|
215
215
|
if (m.fields.parameters.present && ctx.oid.paramsMustBeAbsent(dotted)) {
|
|
@@ -221,7 +221,7 @@ function algorithmIdentifier(ns, opts) {
|
|
|
221
221
|
});
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
// pbkdf2Params(ns): PBKDF2-params (RFC 8018 sec. 5.2) constrained to the RFC 9579 PBMAC1 profile
|
|
224
|
+
// pbkdf2Params(ns): PBKDF2-params (RFC 8018 sec. 5.2) constrained to the RFC 9579 PBMAC1 profile, where the
|
|
225
225
|
// salt uses the OCTET STRING choice and keyLength MUST be present (a MacData / PKIProtection consumer
|
|
226
226
|
// cannot infer the derived MAC key size, RFC 9579 sec. 4.b/5). Shared by the PKCS#12 MacData reader and
|
|
227
227
|
// the CMP PBMAC1 protection reader: one ns-parameterized decoder so neither format re-derives the shape.
|
|
@@ -238,26 +238,28 @@ function pbkdf2Params(ns) {
|
|
|
238
238
|
if (!m.fields.keyLength.present) {
|
|
239
239
|
throw ctx.E(ctx.prefix + "/bad-mac-data", "PBMAC1 PBKDF2-params must carry keyLength (RFC 9579 sec. 5)");
|
|
240
240
|
}
|
|
241
|
-
// guard.range.positiveInt31 bounds + narrows each counter atomically
|
|
241
|
+
// guard.range.positiveInt31 bounds + narrows each counter atomically, so a value past the bound
|
|
242
242
|
// would round silently and hand a verifier wrong inputs.
|
|
243
243
|
var iterationCount = guard.range.positiveInt31(m.fields.iterationCount.value, ctx.E, ctx.prefix + "/bad-mac-data", "PBKDF2 iterationCount");
|
|
244
244
|
var keyLength = guard.range.positiveInt31(m.fields.keyLength.value, ctx.E, ctx.prefix + "/bad-mac-data", "PBKDF2 keyLength");
|
|
245
245
|
var prf = m.fields.prf.present ? m.fields.prf.value.result : null;
|
|
246
|
-
// X.690 sec. 11.5
|
|
246
|
+
// X.690 sec. 11.5: the prf DEFAULT is algid-hmacWithSHA1 (hmacWithSHA1 with NULL parameters,
|
|
247
247
|
// i.e. the 2-byte DER 05 00, RFC 8018 sec. 5.2); an explicit prf byte-equal to that default is
|
|
248
248
|
// non-canonical and rejects. hmacWithSHA1 with ABSENT parameters is a different value and decodes.
|
|
249
|
-
// This is a public structural check on the algorithm parameters
|
|
249
|
+
// This is a public structural check on the algorithm parameters, not a secret compare, so a
|
|
250
250
|
// direct byte test on the (fixed 2-octet) NULL encoding, not a timing-safe comparison.
|
|
251
251
|
var pp = prf ? prf.parameters : null;
|
|
252
|
-
//
|
|
253
|
-
//
|
|
252
|
+
// pp is the public algorithm-identifier parameters field, a fixed 2-octet NULL encoding.
|
|
253
|
+
// No MAC, tag or secret is involved, so a timing-safe compare has nothing to protect.
|
|
254
|
+
// nosemgrep: pki-non-constant-time-secret-compare
|
|
254
255
|
if (prf && prf.oid === hmacSha1 && pp !== null && pp.length === 2 && pp[0] === 0x05 && pp[1] === 0x00) {
|
|
255
256
|
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)");
|
|
256
257
|
}
|
|
257
258
|
// The PBKDF2 prf HMAC AlgorithmIdentifier likewise carries NULL (or absent) parameters (RFC 8018 App. B.1):
|
|
258
259
|
// reject a present-but-non-NULL prf parameter (e.g. an INTEGER) instead of discarding it, since the prf
|
|
259
260
|
// hash is dispatched by OID alone. (An absent prf, and hmacWithSHA1 with absent params, remain valid above.)
|
|
260
|
-
//
|
|
261
|
+
// pp is the public algorithm-identifier parameters node.
|
|
262
|
+
// nosemgrep: pki-non-constant-time-secret-compare
|
|
261
263
|
if (prf && pp !== null && !(pp.length === 2 && pp[0] === 0x05 && pp[1] === 0x00)) {
|
|
262
264
|
throw ctx.E(ctx.prefix + "/bad-mac-data", "the PBKDF2 prf parameters must be absent or NULL (RFC 8018 App. B.1)");
|
|
263
265
|
}
|
|
@@ -293,12 +295,13 @@ function pbmac1Params(ns) {
|
|
|
293
295
|
}
|
|
294
296
|
var scheme = m.fields.messageAuthScheme.value.result;
|
|
295
297
|
// RFC 8018 App. B.1: an HMAC messageAuthScheme AlgorithmIdentifier carries NULL (or absent) parameters,
|
|
296
|
-
// matching the builder (_hmacAlgId emits the 2-byte 05 00).
|
|
297
|
-
//
|
|
298
|
+
// matching the builder (_hmacAlgId emits the 2-byte 05 00). Any other parameter encoding is
|
|
299
|
+
// rejected: _verifyMac dispatches by the scheme OID alone, so a mismatched / malformed
|
|
298
300
|
// parameter (e.g. an INTEGER) must not slip through unvalidated. A direct byte test on the fixed 2-octet
|
|
299
301
|
// NULL encoding of the PUBLIC parameters, not a secret compare.
|
|
300
302
|
var sp = scheme.parameters;
|
|
301
|
-
//
|
|
303
|
+
// sp is the public algorithm-identifier parameters node.
|
|
304
|
+
// nosemgrep: pki-non-constant-time-secret-compare
|
|
302
305
|
if (sp !== null && !(sp.length === 2 && sp[0] === 0x05 && sp[1] === 0x00)) {
|
|
303
306
|
throw ctx.E(ctx.prefix + "/bad-mac-data", "the PBMAC1 messageAuthScheme parameters must be absent or NULL (RFC 8018 App. B.1)");
|
|
304
307
|
}
|
|
@@ -314,12 +317,12 @@ function pbmac1Params(ns) {
|
|
|
314
317
|
// attrValueToString(ns): the AttributeValue decode-leaf. A malformed KNOWN
|
|
315
318
|
// string type (invalid UTF-8, a non-IA5 byte, a PrintableString character
|
|
316
319
|
// outside its set, ...) surfaces as an asn1/bad-* content error and must fail
|
|
317
|
-
// closed
|
|
320
|
+
// closed. Do not hex-encode it away, or the decoder's strict string validation
|
|
318
321
|
// is silently bypassed on the DN path. A value that is simply not a decodable
|
|
319
|
-
// primitive string is
|
|
322
|
+
// primitive string is not malformed and stays representable: an ANY-typed
|
|
320
323
|
// non-string tag (asn1/expected-string) or a constructed universal type such as
|
|
321
324
|
// a SEQUENCE (asn1/expected-primitive) renders per RFC 4514 sec. 2.4 as "#" plus the
|
|
322
|
-
// hex of its
|
|
325
|
+
// hex of its complete DER encoding (node.bytes), round-tripping intact.
|
|
323
326
|
// A GENUINE string whose first character is '#' or '\' is surfaced with a
|
|
324
327
|
// leading '\' escape (RFC 4514 sec. 2.4) so it can never collide with the
|
|
325
328
|
// '#'+hex form; the encode direction strips that one leading escape back off.
|
|
@@ -331,8 +334,9 @@ function attrValueToString(ns) {
|
|
|
331
334
|
if (!e || (e.code !== "asn1/expected-string" && e.code !== "asn1/expected-primitive")) {
|
|
332
335
|
throw ns.E(ns.prefix + "/bad-atv", "malformed string in attribute value: " + ((e && e.message) || String(e)));
|
|
333
336
|
}
|
|
334
|
-
//
|
|
335
|
-
// the RFC 5280 sec. 7.1 name-comparison identity class, and NumericString is not a
|
|
337
|
+
// asn1.read.string does not read NumericString, and that is deliberate: a value that returns a plain
|
|
338
|
+
// string joins the RFC 5280 sec. 7.1 name-comparison identity class, and NumericString is not a
|
|
339
|
+
// DirectoryString type.
|
|
336
340
|
// It does have its own strict reader though, so VALIDATE the content here (an out-of-alphabet value is
|
|
337
341
|
// malformed DER and must not be accepted merely because it falls to the opaque form) while still
|
|
338
342
|
// surfacing it in the type-distinct RFC 4514 hex form, which keeps it out of that identity class.
|
|
@@ -347,9 +351,9 @@ function attrValueToString(ns) {
|
|
|
347
351
|
}, function (value) {
|
|
348
352
|
// encode: a leading '\' escapes the next character (the decode's escape of a
|
|
349
353
|
// literal '#'/'\'); a "#hex" form (RFC 4514 sec. 2.4) round-trips its raw DER
|
|
350
|
-
// verbatim
|
|
351
|
-
// DER TLV) and throws
|
|
352
|
-
//
|
|
354
|
+
// verbatim. The hex is validated (even-length hex digits encoding exactly one
|
|
355
|
+
// DER TLV) and throws on anything else, since Buffer.from(str, "hex") stops at
|
|
356
|
+
// the first invalid character and would emit truncated bytes. Any other
|
|
353
357
|
// string encodes as UTF8String (the decode does not preserve the original
|
|
354
358
|
// string type, so this is the canonical re-encoding, not a byte-exact one).
|
|
355
359
|
if (typeof value === "string" && value.charAt(0) === "\\") return asn1.build.utf8(value.slice(1));
|
|
@@ -371,17 +375,17 @@ function attrValueToString(ns) {
|
|
|
371
375
|
|
|
372
376
|
// The always-escape specials of RFC 4514 sec. 2.4, plus its positional rules:
|
|
373
377
|
// NUL escapes as \00, a trailing space as '\ ', and a leading '#' or space as
|
|
374
|
-
// '\#' / '\ '
|
|
378
|
+
// '\#' / '\ '. Without them the rendered dn is ambiguous (a literal '#0500'
|
|
375
379
|
// value would be indistinguishable from the hexstring form) and leading /
|
|
376
380
|
// trailing spaces would be silently significant.
|
|
377
381
|
// RFC 4514 sec. 2.4 attribute-value escaping is the single guard-name primitive
|
|
378
|
-
// (pki.inspect reuses the dn this produces
|
|
382
|
+
// (pki.inspect reuses the dn this produces and does not re-escape the already-
|
|
379
383
|
// escaped value), so the display escaper can't diverge from a re-inline elsewhere.
|
|
380
384
|
var _escapeDnValue = guard.name.escapeDnValue;
|
|
381
385
|
|
|
382
386
|
// The dn RENDERING of a surfaced AttributeValue: the '#'-leading hex form (a
|
|
383
|
-
// non-string value) renders bare
|
|
384
|
-
// hexstring form into an escaped literal
|
|
387
|
+
// non-string value) renders bare, since escaping its '#' would turn the RFC 4514
|
|
388
|
+
// hexstring form into an escaped literal. A genuine string (surfaced
|
|
385
389
|
// with its leading '#'/'\' escape, see attrValueToString) is unescaped back to
|
|
386
390
|
// the semantic string and then rendered with the full escape set.
|
|
387
391
|
function _dnDisplayValue(v) {
|
|
@@ -395,12 +399,12 @@ function _dnDisplayValue(v) {
|
|
|
395
399
|
// sec. 5.1.2.4-5.1.2.6): a date through the year 2049 MUST be UTCTime and
|
|
396
400
|
// GeneralizedTime is reserved for 2050 onward, so a GeneralizedTime carrying a
|
|
397
401
|
// 1950..2049 date is a non-conforming second encoding of a representable value
|
|
398
|
-
// and is rejected (<prefix>/bad-time)
|
|
402
|
+
// and is rejected (<prefix>/bad-time); accepting it would let two wire forms
|
|
399
403
|
// alias one abstract time (a parser-differential channel). The engine's
|
|
400
404
|
// schema.time already applies the cutover on ENCODE; this leaf composes it and
|
|
401
405
|
// enforces the same rule on DECODE. A GeneralizedTime-only field (an RFC 3161
|
|
402
|
-
// genTime, a CRL invalidityDate, an RFC 5755 AttCertValidityPeriod) must
|
|
403
|
-
// take this leaf
|
|
406
|
+
// genTime, a CRL invalidityDate, an RFC 5755 AttCertValidityPeriod) must not
|
|
407
|
+
// take this leaf, whose syntax has no UTCTime alternative to cut over to.
|
|
404
408
|
function time(ns) {
|
|
405
409
|
var base = schema.time(ns);
|
|
406
410
|
return schema.decode(function (n, ctx) {
|
|
@@ -432,10 +436,10 @@ function attributeTypeAndValue(ns) {
|
|
|
432
436
|
});
|
|
433
437
|
}
|
|
434
438
|
function relativeDistinguishedName(ns) {
|
|
435
|
-
// RelativeDistinguishedName ::= SET SIZE (1..MAX)
|
|
439
|
+
// RelativeDistinguishedName ::= SET SIZE (1..MAX), so an empty SET {} is malformed.
|
|
436
440
|
return schema.setOf(attributeTypeAndValue(ns), { assert: "set", min: 1, code: ns.prefix + "/bad-rdn", what: "RelativeDistinguishedName" });
|
|
437
441
|
}
|
|
438
|
-
// opts.implicitTag (optional): read the Name as a [tag] IMPLICIT RDNSequence
|
|
442
|
+
// opts.implicitTag (optional): read the Name as a [tag] IMPLICIT RDNSequence. The
|
|
439
443
|
// context tag REPLACES the universal SEQUENCE tag, so the node is a context-class
|
|
440
444
|
// constructed [tag] whose children ARE the RDN SETs (the CRMF CertTemplate issuer
|
|
441
445
|
// [3] / subject [5] IMPLICIT arm, RFC 4211 sec. 5). With no opts the shape is a bare
|
|
@@ -456,9 +460,9 @@ function name(ns, opts) {
|
|
|
456
460
|
parts.push(atvParts.join("+"));
|
|
457
461
|
});
|
|
458
462
|
// `dn` is a DISPLAY string, and its spelling is `openssl x509 -subject`: the RDNs in the
|
|
459
|
-
// certificate's own order, joined by ", ". That is deliberately
|
|
463
|
+
// certificate's own order, joined by ", ". That is deliberately not the RFC 4514 / LDAP
|
|
460
464
|
// distinguishedName, which reverses the RDNs and joins them with a bare comma (`openssl -nameopt
|
|
461
|
-
// RFC2253`)
|
|
465
|
+
// RFC2253`), so this string must never be handed to an LDAP client. What IS taken from RFC 4514
|
|
462
466
|
// is sec. 2.4 value escaping, so an attribute value holding a comma cannot read as two RDNs. The
|
|
463
467
|
// interop gate pins this against openssl's own output. Identity never flows through this string:
|
|
464
468
|
// guard.name.dnEqual compares the DER under RFC 5280 sec. 7.1, and `bytes` below carries the raw
|
|
@@ -475,28 +479,29 @@ function name(ns, opts) {
|
|
|
475
479
|
}
|
|
476
480
|
|
|
477
481
|
// GeneralName ::= CHOICE (RFC 5280 sec. 4.2.1.6). A validate-and-surface-raw leaf for a
|
|
478
|
-
// caller that keeps the value
|
|
482
|
+
// caller that keeps the value raw but needs it to be a well-formed GeneralName: it
|
|
479
483
|
// checks the chosen alternative's tag, form (constructed vs primitive per X.690
|
|
480
|
-
// sec. 10.2), and content
|
|
484
|
+
// sec. 10.2), and content, then surfaces the value raw ({ bytes, tagClass, tagNumber }).
|
|
485
|
+
// The content rule per alternative: otherName [0] is a SEQUENCE { type-id OID, value [0] EXPLICIT
|
|
481
486
|
// }; x400Address [3] / ediPartyName [5] are non-empty constructed; directoryName [4]
|
|
482
487
|
// EXPLICIT wraps a valid Name; rfc822Name [1] / dNSName [2] / uniformResourceIdentifier
|
|
483
488
|
// [6] are primitive non-empty IA5String (7-bit); iPAddress [7] is a 4- or 16-octet
|
|
484
|
-
// OCTET STRING; registeredID [8] is a primitive OBJECT IDENTIFIER
|
|
485
|
-
//
|
|
489
|
+
// OCTET STRING; registeredID [8] is a primitive OBJECT IDENTIFIER.
|
|
490
|
+
// `opts.code` is the caller's error code
|
|
486
491
|
// (e.g. ocsp/bad-requestor-name, tsp/bad-tsa). Shared so the two parsers cannot drift.
|
|
487
492
|
// opts.decodeValue (default false): in addition to the raw { bytes, tagClass,
|
|
488
|
-
// tagNumber }, surface the DECODED `value` per arm
|
|
493
|
+
// tagNumber }, surface the DECODED `value` per arm: IA5 text (string), an
|
|
489
494
|
// iPAddress Buffer, a directoryName { rdns, dn }, a registeredID OID string,
|
|
490
495
|
// or an otherName { typeId, valueBytes }. The path validator's name-constraint
|
|
491
496
|
// matcher needs the decoded value; the tsp/ocsp/attrcert consumers pass no
|
|
492
497
|
// flag and get the byte-identical raw-only shape.
|
|
493
498
|
// opts.subtreeBase (default false): the GeneralSubtree.base form (RFC 5280
|
|
494
|
-
// sec. 4.2.1.10)
|
|
495
|
-
//
|
|
499
|
+
// sec. 4.2.1.10). An iPAddress base is an address+mask (8 octets IPv4 / 32 IPv6),
|
|
500
|
+
// and not the 4/16-octet SAN address form. Only the iPAddress size rule changes.
|
|
496
501
|
// A GeneralizedTime-only leaf. Several formats fix their times to
|
|
497
502
|
// GeneralizedTime and reject UTCTime (OCSP RFC 6960, TSP RFC 3161 sec. 2.4.2,
|
|
498
503
|
// CMP RFC 9810 sec. 5.1.1, attribute-certificate validity RFC 5755 sec. 4.2.6);
|
|
499
|
-
//
|
|
504
|
+
// a single ns-parameterized factory owns the tag assert, so the per-format code and
|
|
500
505
|
// message differ but the check cannot drift. opts.allowFractional enables the
|
|
501
506
|
// X.690 sec. 11.7 fractional-seconds profile (the RFC 3161 genTime).
|
|
502
507
|
function generalizedTime(ns, opts) {
|
|
@@ -529,8 +534,8 @@ function utf8Text(ns, opts) {
|
|
|
529
534
|
// A raw NON-EMPTY universal SEQUENCE leaf -- surfaces the exact TLV bytes of an
|
|
530
535
|
// embedded element (a Certificate, a CertificateList, a PKIPublicationInfo)
|
|
531
536
|
// after asserting the SEQUENCE tag and at least one child. An empty SEQUENCE
|
|
532
|
-
// (30 00) has the right tag but is none of those structures, so it rejects
|
|
533
|
-
//
|
|
537
|
+
// (30 00) has the right tag but is none of those structures, so it rejects and
|
|
538
|
+
// no degenerate bytes reach certificate processing.
|
|
534
539
|
function rawNonEmptySequence(ns, opts) {
|
|
535
540
|
opts = opts || {};
|
|
536
541
|
var code = opts.code || (ns.prefix + "/bad-sequence");
|
|
@@ -543,8 +548,8 @@ function rawNonEmptySequence(ns, opts) {
|
|
|
543
548
|
});
|
|
544
549
|
}
|
|
545
550
|
|
|
546
|
-
// CRLReason ::= ENUMERATED (RFC 5280 sec. 5.3.1)
|
|
547
|
-
// The
|
|
551
|
+
// CRLReason ::= ENUMERATED (RFC 5280 sec. 5.3.1). Value 7 is unused/reserved.
|
|
552
|
+
// The single canonical value->name table every consumer validates against: the
|
|
548
553
|
// CRL reasonCode decoder surfaces the numeric code, the OCSP RevokedInfo
|
|
549
554
|
// decoder the name; both draw the legal set from here so they cannot drift.
|
|
550
555
|
// CRLReason ENUMERATED value names -- the single source is pki.C.NAMES.CRL_REASON
|
|
@@ -580,7 +585,7 @@ function generalName(ns, opts) {
|
|
|
580
585
|
}
|
|
581
586
|
if (decodeValue) value = { typeId: typeId, valueBytes: ov.children[0].bytes };
|
|
582
587
|
} else if (t === 4) {
|
|
583
|
-
// directoryName [4] EXPLICIT Name
|
|
588
|
+
// directoryName [4] EXPLICIT Name: validate the wrapped RDNSequence.
|
|
584
589
|
if (n.children.length !== 1) throw ctx.E(code, "GeneralName directoryName [4] must wrap exactly one Name");
|
|
585
590
|
var dnMatch = schema.walk(NAME, n.children[0], ctx);
|
|
586
591
|
if (decodeValue) value = dnMatch.result;
|
|
@@ -589,7 +594,7 @@ function generalName(ns, opts) {
|
|
|
589
594
|
if (constructed) throw ctx.E(code, "GeneralName [" + t + "] must be primitive (X.690 sec. 10.2)");
|
|
590
595
|
if (GN_IA5[t]) {
|
|
591
596
|
if (n.content.length === 0) throw ctx.E(code, "GeneralName [" + t + "] must be a non-empty IA5String");
|
|
592
|
-
// 7-bit IA5, no C0/DEL control byte
|
|
597
|
+
// 7-bit IA5, no C0/DEL control byte. An embedded NUL/control in a
|
|
593
598
|
// dNSName/rfc822Name/URI enables a name-truncation bypass (CVE-2009-2408).
|
|
594
599
|
guard.name.assertPrintableIa5(n.content, ctx.E, code, "GeneralName [" + t + "]");
|
|
595
600
|
if (decodeValue) value = n.content.toString("latin1");
|
|
@@ -619,10 +624,10 @@ function generalName(ns, opts) {
|
|
|
619
624
|
// element is validated by generalName (its CHOICE alternative's form + content) and
|
|
620
625
|
// surfaced raw, so a caller carrying a GeneralNames field cannot accept a malformed
|
|
621
626
|
// element by treating the whole sequence as opaque bytes. Returns { names, bytes }
|
|
622
|
-
// where each `names[i]` is the generalName leaf's return
|
|
623
|
-
// tagNumber }
|
|
627
|
+
// where each `names[i]` is the generalName leaf's return: { bytes, tagClass,
|
|
628
|
+
// tagNumber } plus, when opts.decodeValue is set, the decoded `value`. `bytes`
|
|
624
629
|
// is the raw outer DER.
|
|
625
|
-
// `opts.implicitTag` handles a [tag] IMPLICIT GeneralNames
|
|
630
|
+
// `opts.implicitTag` handles a [tag] IMPLICIT GeneralNames, where the context tag REPLACES
|
|
626
631
|
// the universal SEQUENCE tag (RFC 5755 Holder.entityName [1]); otherwise it is a bare
|
|
627
632
|
// universal SEQUENCE OF. `opts.code` is the caller's error code. Shared so the x509 /
|
|
628
633
|
// attribute-certificate / (future) CRMF parsers validate a GeneralNames identically.
|
|
@@ -641,11 +646,11 @@ function generalNames(ns, opts) {
|
|
|
641
646
|
|
|
642
647
|
// DistributionPointName ::= CHOICE { fullName [0] IMPLICIT GeneralNames,
|
|
643
648
|
// nameRelativeToCRLIssuer [1] IMPLICIT RelativeDistinguishedName } (RFC 5280
|
|
644
|
-
// sec. 4.2.1.13). The caller hands the CHOICE node itself
|
|
645
|
-
//
|
|
649
|
+
// sec. 4.2.1.13). The caller hands the CHOICE node itself: the alternative
|
|
650
|
+
// inside the [0]-tagged distributionPoint field wrapper (a context tag on a
|
|
646
651
|
// CHOICE-typed field is always EXPLICIT, so the wire nests [0]{ [0]|[1] ... }).
|
|
647
|
-
// fullName surfaces each element's
|
|
648
|
-
// names: [Buffer, ...] }
|
|
652
|
+
// fullName surfaces each element's raw GeneralName DER as { kind: "fullName",
|
|
653
|
+
// names: [Buffer, ...] }, the byte-exact comparison key the sec. 5.2.5
|
|
649
654
|
// "identical encoding MUST be used" correspondence rule requires;
|
|
650
655
|
// nameRelativeToCRLIssuer surfaces its full [1]-tagged TLV as { kind: "rdn",
|
|
651
656
|
// bytes: Buffer } (byte-identical [1] TLVs are byte-identical RDN fragments).
|
|
@@ -660,7 +665,7 @@ function distributionPointName(ns, node, code) {
|
|
|
660
665
|
return { kind: "fullName", names: gns.names.map(function (n) { return n.bytes; }) };
|
|
661
666
|
}
|
|
662
667
|
if (node && node.tagClass === "context" && node.tagNumber === 1) {
|
|
663
|
-
// [1] IMPLICIT RelativeDistinguishedName
|
|
668
|
+
// In [1] IMPLICIT RelativeDistinguishedName the context tag replaces the
|
|
664
669
|
// SET tag, so the node's direct children are the AttributeTypeAndValues.
|
|
665
670
|
schema.walk(schema.implicitSetOf(1, attributeTypeAndValue(ns), {
|
|
666
671
|
min: 1, code: code, what: "DistributionPointName nameRelativeToCRLIssuer" }), node, ns);
|
|
@@ -670,15 +675,15 @@ function distributionPointName(ns, node, code) {
|
|
|
670
675
|
}
|
|
671
676
|
|
|
672
677
|
// A certificate's keyUsage as the named booleans the shared sec. 4.2.1 decoder produces, or null
|
|
673
|
-
// when the certificate carries no keyUsage extension
|
|
674
|
-
// a distinct answer from "carries one that permits nothing".
|
|
678
|
+
// when the certificate carries no keyUsage extension. An absent extension places no restriction
|
|
679
|
+
// (sec. 4.2.1.3), a distinct answer from "carries one that permits nothing".
|
|
675
680
|
//
|
|
676
681
|
// Declared here because "may this certificate do X" is asked at five boundaries and the answer has
|
|
677
682
|
// to be the same at all of them. keyUsage is a NamedBitList: DER drops its trailing zero bits
|
|
678
683
|
// (X.690 sec. 11.2.2) and sec. 4.2.1.3 requires at least one bit set. A boundary that reads the
|
|
679
684
|
// bits with a plain BIT STRING read applies neither, so the same certificate is authorized there
|
|
680
|
-
// and rejected as malformed by the issuing side and the path validator
|
|
681
|
-
// readings, and the permissive one
|
|
685
|
+
// and rejected as malformed by the issuing side and the path validator. One extension gets two
|
|
686
|
+
// readings, and the permissive one decides.
|
|
682
687
|
//
|
|
683
688
|
// `E(code, message, cause)` is the caller's typed error factory, so each boundary keeps its own
|
|
684
689
|
// domain while the RULE stays single-homed.
|
|
@@ -701,8 +706,8 @@ function keyUsageOf(ns, cert, E, code, label) {
|
|
|
701
706
|
// onlySomeReasons [3] ReasonFlags OPTIONAL, indirectCRL [4] DEFAULT FALSE,
|
|
702
707
|
// onlyContainsAttributeCerts [5] DEFAULT FALSE } (RFC 5280 sec. 5.2.5).
|
|
703
708
|
//
|
|
704
|
-
//
|
|
705
|
-
// question
|
|
709
|
+
// It is declared here and not in a consumer because the extension answers the
|
|
710
|
+
// question "which certificates does this CRL speak for", which both the path
|
|
706
711
|
// validator and the standalone CRL verbs act on, and a scope flag read by a hand
|
|
707
712
|
// walk over the raw children is read by the reader's own rules instead of the
|
|
708
713
|
// encoding's. Each flag is an IMPLICIT BOOLEAN, so `implicitBoolean` is what
|
|
@@ -714,7 +719,7 @@ function keyUsageOf(ns, cert, E, code, label) {
|
|
|
714
719
|
//
|
|
715
720
|
// A present DEFAULT-FALSE flag encoding FALSE is well-formed at the leaf but is
|
|
716
721
|
// still an encoding DER forbids (X.690 sec. 11.5 omits the default), so the
|
|
717
|
-
// caller decides what that means for it
|
|
722
|
+
// caller decides what that means for it. It is reported as a value of `false`
|
|
718
723
|
// against a `present` of true, never silently normalized away.
|
|
719
724
|
function issuingDistributionPoint(code) {
|
|
720
725
|
return schema.seq([
|
|
@@ -744,7 +749,7 @@ var _T = asn1.TAGS;
|
|
|
744
749
|
|
|
745
750
|
// assertPolicyQualifiers(qNode, fail) -- RFC 5280 sec. 4.2.1.4: a PolicyInformation's
|
|
746
751
|
// policyQualifiers is a non-empty SEQUENCE OF PolicyQualifierInfo, each a SEQUENCE
|
|
747
|
-
// { policyQualifierId OID, qualifier } of
|
|
752
|
+
// { policyQualifierId OID, qualifier } of exactly two members led by an OID. The
|
|
748
753
|
// structure is validated fail-closed; the qualifier body stays opaque (surfaced
|
|
749
754
|
// raw by the caller). `fail(msg)` throws the caller's typed code. Shared by the
|
|
750
755
|
// certificatePolicies extension decoder and the RFC 5035 ESS SigningCertificate
|
|
@@ -764,8 +769,8 @@ function assertPolicyQualifiers(qNode, fail) {
|
|
|
764
769
|
}
|
|
765
770
|
|
|
766
771
|
// DisplayText ::= CHOICE { ia5String, visibleString, bmpString, utf8String } each SIZE (1..200)
|
|
767
|
-
// -- RFC 5280 sec. 4.2.1.4. The bound is deliberately
|
|
768
|
-
// requirement
|
|
772
|
+
// -- RFC 5280 sec. 4.2.1.4. The bound is deliberately not enforced here. That is a normative
|
|
773
|
+
// requirement and not a gap: the same section closes with "While the explicitText has a maximum
|
|
769
774
|
// size of 200 characters, some non-conforming CAs exceed this limit. Therefore, certificate users
|
|
770
775
|
// SHOULD gracefully handle explicitText with more than 200 characters." A decoder IS the certificate
|
|
771
776
|
// user that note addresses, so refusing an over-long notice would violate a SHOULD and reject
|
|
@@ -804,11 +809,11 @@ function _isDisplayTextNode(n) {
|
|
|
804
809
|
// Reading the content bytes directly would repair the value instead: `toString("utf8")` substitutes
|
|
805
810
|
// U+FFFD for invalid sequences and a hand-rolled UCS-2 loop drops a trailing odd octet, so a caller
|
|
806
811
|
// would render or measure text the certificate does not contain. Returns null when the value does
|
|
807
|
-
// not decode,
|
|
812
|
+
// not decode, leaving the fallback to the caller.
|
|
808
813
|
// An undecodable value yields an entry whose `text` and `chars` are null but whose `field` and
|
|
809
814
|
// `tagNumber` are still present. The two are separable facts: which ASN.1 string type was used is
|
|
810
815
|
// established by the tag alone, so a rule about the ENCODING stays answerable even when the contents
|
|
811
|
-
// do not decode, while every rule that reads the TEXT must skip the entry
|
|
816
|
+
// do not decode, while every rule that reads the TEXT must skip the entry and not analyze a
|
|
812
817
|
// repair. Collapsing both into "not analyzable" would let a prohibited encoding escape its finding
|
|
813
818
|
// by also being malformed inside.
|
|
814
819
|
function _dtEntry(field, node) {
|
|
@@ -817,29 +822,29 @@ function _dtEntry(field, node) {
|
|
|
817
822
|
try { text = asn1.read.string(node); } catch (_e) { text = null; }
|
|
818
823
|
return { field: field, tagNumber: node.tagNumber, text: text, chars: text === null ? null : displayTextChars(text) };
|
|
819
824
|
}
|
|
820
|
-
// A NoticeReference names its notice by ORGANIZATION **and** NUMBER
|
|
825
|
+
// A NoticeReference names its notice by ORGANIZATION **and** NUMBER. The organization alone does not
|
|
821
826
|
// identify which notice is meant, so a consumer that surfaced only the text would lose the lookup key.
|
|
822
827
|
// Returns null when the numbers are absent, wrongly shaped, or contain a member that is not a readable
|
|
823
|
-
// INTEGER: an incompletely decoded reference must
|
|
824
|
-
// failed would render a partial reference indistinguishable from a whole one
|
|
828
|
+
// INTEGER: an incompletely decoded reference must not be presentable, because dropping the members that
|
|
829
|
+
// failed would render a partial reference indistinguishable from a whole one. That is the same defect as
|
|
825
830
|
// omitting the numbers entirely, in a form that is harder to notice. Numbers are decimal strings.
|
|
826
831
|
function _noticeNumbers(node) {
|
|
827
832
|
if (!node || node.tagClass !== "universal" || node.tagNumber !== _T.SEQUENCE || !node.children) return null;
|
|
828
833
|
var nums = [], ok = true;
|
|
829
834
|
node.children.forEach(function (n) {
|
|
830
|
-
// allow:swallow-unverified a non-INTEGER member makes the whole reference undecodable
|
|
835
|
+
// allow:swallow-unverified a non-INTEGER member makes the whole reference undecodable, and the caller falls back
|
|
831
836
|
try { nums.push(String(asn1.read.integer(n))); } catch (_e) { ok = false; }
|
|
832
837
|
});
|
|
833
838
|
return ok ? nums : null;
|
|
834
839
|
}
|
|
835
|
-
// Every DisplayText member is returned, including one whose contents did not decode
|
|
840
|
+
// Every DisplayText member is returned, including one whose contents did not decode; that entry
|
|
836
841
|
// carries its tag with a null `text`. A caller that RENDERS the notice must check that every entry
|
|
837
842
|
// decoded before showing any of it (a partial notice is indistinguishable from a complete one); a
|
|
838
843
|
// caller that only classifies the encoding can read the tags regardless.
|
|
839
844
|
// The LAYOUT is validated before any member is read: UserNotice fixes the order (noticeRef first) and
|
|
840
845
|
// the cardinality (each member at most once, nothing else present). Collecting every recognized member
|
|
841
|
-
// and ignoring the rest would accept a duplicated explicitText, a reversed order, or an extra member
|
|
842
|
-
//
|
|
846
|
+
// and ignoring the rest would accept a duplicated explicitText, a reversed order, or an extra member.
|
|
847
|
+
// The caller can only see whether the entries it got look complete, so it would then present
|
|
843
848
|
// a structurally invalid notice as a whole one. A malformed layout yields NO entries, so a renderer
|
|
844
849
|
// falls back to hex and a reporter measures nothing.
|
|
845
850
|
function userNoticeTexts(qualifier) {
|
|
@@ -914,7 +919,7 @@ function certExtensionDecoders(ns) {
|
|
|
914
919
|
if (kids[i] && kids[i].tagClass === "universal" && kids[i].tagNumber === _T.INTEGER) {
|
|
915
920
|
if (!cA) throw ns.E(C, "BasicConstraints pathLenConstraint is only permitted when cA is TRUE (RFC 5280 sec. 4.2.1.9)");
|
|
916
921
|
var pl = readInt(kids[i], C, "pathLenConstraint");
|
|
917
|
-
// guard.range.uint31 bounds the decoded BigInt and narrows atomically
|
|
922
|
+
// guard.range.uint31 bounds the decoded BigInt and narrows atomically. A
|
|
918
923
|
// value past the safe-integer range would otherwise round silently and be
|
|
919
924
|
// compared as a path-length ceiling.
|
|
920
925
|
pathLen = guard.range.uint31(pl, ns.E, C, "BasicConstraints pathLenConstraint (RFC 5280 sec. 4.2.1.9)"); i++;
|
|
@@ -932,7 +937,7 @@ function certExtensionDecoders(ns) {
|
|
|
932
937
|
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)");
|
|
933
938
|
var bs;
|
|
934
939
|
try { bs = asn1.read.bitString(n); } catch (e) { throw ns.E(C, "KeyUsage must be a well-formed BIT STRING", e); }
|
|
935
|
-
// At least one bit MUST be set (RFC 5280 sec. 4.2.1.3)
|
|
940
|
+
// At least one bit MUST be set (RFC 5280 sec. 4.2.1.3), so an all-zero value (a
|
|
936
941
|
// non-empty byte run of only zero bits, e.g. 03 02 07 00) is malformed too.
|
|
937
942
|
var anyBit = false;
|
|
938
943
|
for (var z = 0; z < bs.bytes.length; z++) { if (bs.bytes[z] !== 0) { anyBit = true; break; } }
|
|
@@ -946,7 +951,7 @@ function certExtensionDecoders(ns) {
|
|
|
946
951
|
});
|
|
947
952
|
// Any bit beyond the 9 named positions is a reserved/unknown usage. RFC 5280 tolerates it,
|
|
948
953
|
// but a "MUST be the ONLY bit" rule (e.g. RFC 9935 sec. 5 ML-KEM keyEncipherment-only) needs
|
|
949
|
-
// to see it
|
|
954
|
+
// to see it, so bits >= 9 are surfaced on `reservedBitsSet`.
|
|
950
955
|
var reserved = false;
|
|
951
956
|
for (var bit2 = KU_BITS.length; bit2 < bs.bytes.length * 8 && !reserved; bit2++) {
|
|
952
957
|
if ((bs.bytes[bit2 >> 3] & (0x80 >> (bit2 & 7))) !== 0) reserved = true;
|
|
@@ -999,7 +1004,7 @@ function certExtensionDecoders(ns) {
|
|
|
999
1004
|
var seen = {};
|
|
1000
1005
|
return kids.map(function (pi) {
|
|
1001
1006
|
// PolicyInformation ::= SEQUENCE { policyIdentifier, policyQualifiers
|
|
1002
|
-
// SEQUENCE SIZE(1..MAX) OPTIONAL }
|
|
1007
|
+
// SEQUENCE SIZE(1..MAX) OPTIONAL }. Exactly one or two fields; a second
|
|
1003
1008
|
// field, if present, MUST be a SEQUENCE. Extra/mis-typed fields are malformed.
|
|
1004
1009
|
if (pi.tagClass !== "universal" || pi.tagNumber !== _T.SEQUENCE || !pi.children || pi.children.length < 1 || pi.children.length > 2) {
|
|
1005
1010
|
throw ns.E(C, "PolicyInformation must be a SEQUENCE { policyIdentifier, policyQualifiers? }");
|
|
@@ -1069,7 +1074,7 @@ function certExtensionDecoders(ns) {
|
|
|
1069
1074
|
return guard.range.uint31(v, ns.E, C, "InhibitAnyPolicy skip count (RFC 5280 sec. 4.2.1.14)");
|
|
1070
1075
|
}
|
|
1071
1076
|
|
|
1072
|
-
// subjectAltName / issuerAltName ::= GeneralNames
|
|
1077
|
+
// subjectAltName / issuerAltName ::= GeneralNames, with decoded values surfaced.
|
|
1073
1078
|
function altName(buf) {
|
|
1074
1079
|
var C = ns.prefix + "/bad-extension-value";
|
|
1075
1080
|
var n = decodeTop(buf, C, "GeneralNames");
|
|
@@ -1080,7 +1085,7 @@ function certExtensionDecoders(ns) {
|
|
|
1080
1085
|
// (RFC 5280 sec. 4.2.2.1); AccessDescription ::= SEQUENCE { accessMethod OBJECT IDENTIFIER, accessLocation
|
|
1081
1086
|
// GeneralName }. Surfaces [{ accessMethod: <dotted OID>, accessLocation: { tag, value } }] in wire order --
|
|
1082
1087
|
// accessLocation is the shared generalName leaf (its context tag number + decoded value, so a control-byte
|
|
1083
|
-
// URI is rejected by the CVE-2009-2408 guard).
|
|
1088
|
+
// URI is rejected by the CVE-2009-2408 guard). Both accessMethods surface (id-ad-caIssuers AND id-ad-ocsp):
|
|
1084
1089
|
// a consumer filters by accessMethod (caIssuers for issuer fetching, ocsp for responder discovery). An empty
|
|
1085
1090
|
// SEQUENCE violates SIZE(1..MAX) and is malformed. A composed decoder + registry row, not a hand-roll.
|
|
1086
1091
|
function authorityInfoAccess(buf) {
|
|
@@ -1149,7 +1154,7 @@ function certExtensionDecoders(ns) {
|
|
|
1149
1154
|
// which owns the inner peel + the bounded TLS decode; structure is decoded, the
|
|
1150
1155
|
// signature stays raw and unverified. The registry contract is that a decoder
|
|
1151
1156
|
// throws a `<prefix>/bad-*` code, so the CT module's own `ct/*` fault is wrapped
|
|
1152
|
-
// and carried as `.cause
|
|
1157
|
+
// and carried as `.cause`, so a foreign namespace never leaks out here.
|
|
1153
1158
|
function sctList(buf) {
|
|
1154
1159
|
var C = ns.prefix + "/bad-extension-value";
|
|
1155
1160
|
try { return ct.parseSctList(buf); }
|
|
@@ -1172,13 +1177,13 @@ function certExtensionDecoders(ns) {
|
|
|
1172
1177
|
// distributionPoint [0] DistributionPointName OPTIONAL, reasons [1]
|
|
1173
1178
|
// ReasonFlags OPTIONAL, cRLIssuer [2] GeneralNames OPTIONAL }. "a
|
|
1174
1179
|
// DistributionPoint MUST NOT consist of only the reasons field; either
|
|
1175
|
-
// distributionPoint or cRLIssuer MUST be present"
|
|
1180
|
+
// distributionPoint or cRLIssuer MUST be present". A reasons-only (or
|
|
1176
1181
|
// empty) DistributionPoint is malformed. The DistributionPointName is
|
|
1177
|
-
// surfaced through the shared helper (raw name encodings
|
|
1182
|
+
// surfaced through the shared helper (raw name encodings, the sec. 5.2.5
|
|
1178
1183
|
// correspondence key); `reasons` stays a raw BIT STRING ({ unusedBits,
|
|
1179
1184
|
// bytes }); cRLIssuer is a validated GeneralNames with decoded values.
|
|
1180
|
-
// FreshestCRL ::= CRLDistributionPoints (sec. 4.2.1.15), so the
|
|
1181
|
-
// serves both OIDs
|
|
1185
|
+
// FreshestCRL ::= CRLDistributionPoints (sec. 4.2.1.15), so the same decoder
|
|
1186
|
+
// serves both OIDs: one codec, both registry rows.
|
|
1182
1187
|
function crlDistributionPoints(buf) {
|
|
1183
1188
|
var C = ns.prefix + "/bad-crl-distribution-points";
|
|
1184
1189
|
var kids = seqChildren(buf, C, "CRLDistributionPoints");
|
|
@@ -1202,7 +1207,7 @@ function certExtensionDecoders(ns) {
|
|
|
1202
1207
|
try { bs = asn1.read.bitStringImplicit(f, 1); }
|
|
1203
1208
|
catch (e) { throw ns.E(C, "DistributionPoint reasons [1] must be an IMPLICIT ReasonFlags BIT STRING", e); }
|
|
1204
1209
|
// ReasonFlags is a NamedBitList, so DER requires trailing zero bits be
|
|
1205
|
-
// dropped (X.690 sec. 11.2.2)
|
|
1210
|
+
// dropped (X.690 sec. 11.2.2), the same rule the keyUsage decoder
|
|
1206
1211
|
// enforces above. Without it two different encodings of one reason set
|
|
1207
1212
|
// both parse, and RFC 5280 sec. 6.3.3(d)(1) would intersect the CRL's
|
|
1208
1213
|
// onlySomeReasons against a value the encoding rules forbid.
|
|
@@ -1247,9 +1252,9 @@ function certExtensionDecoders(ns) {
|
|
|
1247
1252
|
}
|
|
1248
1253
|
var nras = [];
|
|
1249
1254
|
if (node.children[i]) {
|
|
1250
|
-
// nameRegistrationAuthorities ::= SEQUENCE SIZE (1..MAX) OF GeneralName
|
|
1255
|
+
// nameRegistrationAuthorities ::= SEQUENCE SIZE (1..MAX) OF GeneralName. Decode and fully validate
|
|
1251
1256
|
// each element through the shared GeneralName decoder (context tag [0]..[8] + per-alternative
|
|
1252
|
-
// structure)
|
|
1257
|
+
// structure); arbitrary bytes never surface as a name-registration authority.
|
|
1253
1258
|
nras = schema.walk(generalNames(ns, { decodeValue: true, code: C }), node.children[i], ns).result.names;
|
|
1254
1259
|
i++;
|
|
1255
1260
|
}
|
|
@@ -1283,7 +1288,7 @@ function certExtensionDecoders(ns) {
|
|
|
1283
1288
|
return {
|
|
1284
1289
|
currency: currency,
|
|
1285
1290
|
// MonetaryValue amount and exponent are unconstrained INTEGERs (the amount non-negative in practice);
|
|
1286
|
-
// bound each only to the safe-integer range so it narrows to a Number losslessly
|
|
1291
|
+
// bound each only to the safe-integer range so it narrows to a Number losslessly. An artificial
|
|
1287
1292
|
// tighter bound (uint31, or a small exponent window) would false-reject a valid MonetaryValue, and a
|
|
1288
1293
|
// wider one would round silently and mis-state the limit.
|
|
1289
1294
|
amount: guard.range.int(readInt(node.children[1], C, "QcLimitValue amount"), 0n, 9007199254740991n, ns.E, C, "QcLimitValue amount"),
|
|
@@ -1304,7 +1309,7 @@ function certExtensionDecoders(ns) {
|
|
|
1304
1309
|
}) };
|
|
1305
1310
|
};
|
|
1306
1311
|
// ETSI EN 319 412-5 V2.5.1 additions: QcIdentMethod (SEQUENCE OF OID, like QcType) and
|
|
1307
|
-
// QcQSCDlegislation (SEQUENCE OF CountryName, like QcCClegislation)
|
|
1312
|
+
// QcQSCDlegislation (SEQUENCE OF CountryName, like QcCClegislation). Both are registered ids, decoded.
|
|
1308
1313
|
qcInfoByOid[O("qcIdentMethod")] = function (node, C) {
|
|
1309
1314
|
var oids = _qcOidSeq(node, C, "QcIdentMethod");
|
|
1310
1315
|
return { methods: oids, methodNames: oids.map(function (d) { return ns.oid.name(d) || null; }) };
|
|
@@ -1333,9 +1338,9 @@ function certExtensionDecoders(ns) {
|
|
|
1333
1338
|
return out;
|
|
1334
1339
|
}
|
|
1335
1340
|
|
|
1336
|
-
// [MS-WCCE] szOID-CERTIFICATE_TEMPLATE
|
|
1341
|
+
// [MS-WCCE] szOID-CERTIFICATE_TEMPLATE. CertificateTemplateOID ::= SEQUENCE {
|
|
1337
1342
|
// templateID OID, templateMajorVersion INTEGER OPTIONAL, templateMinorVersion INTEGER OPTIONAL }.
|
|
1338
|
-
// The two versions are DWORDs (0..2^32-1)
|
|
1343
|
+
// The two versions are DWORDs (0..2^32-1) and not uint31: a CA key/template revision with bit 31
|
|
1339
1344
|
// set is a legal DWORD, so bound to the whole safe-integer range, never guard.range.uint31.
|
|
1340
1345
|
function msCertificateTemplate(buf) {
|
|
1341
1346
|
var C = ns.prefix + "/bad-ms-certificate-template";
|
|
@@ -1352,7 +1357,7 @@ function certExtensionDecoders(ns) {
|
|
|
1352
1357
|
return { templateID: id, name: ns.oid.name(id) || null, templateMajorVersion: ver(1, "templateMajorVersion"), templateMinorVersion: ver(2, "templateMinorVersion") };
|
|
1353
1358
|
}
|
|
1354
1359
|
|
|
1355
|
-
// [MS-CRTD] szOID-ENROLL_CERTTYPE_EXTENSION
|
|
1360
|
+
// [MS-CRTD] szOID-ENROLL_CERTTYPE_EXTENSION carries the legacy v1 template name as a BARE BMPString.
|
|
1356
1361
|
// asn1.read.string accepts any universal string tag, so the explicit BMP_STRING assert is the guard.
|
|
1357
1362
|
function msEnrollCertType(buf) {
|
|
1358
1363
|
var C = ns.prefix + "/bad-ms-enroll-cert-type";
|
|
@@ -1361,7 +1366,7 @@ function certExtensionDecoders(ns) {
|
|
|
1361
1366
|
try { return asn1.read.string(n); } catch (e) { throw ns.E(C, "the enroll cert-type name must be a well-formed BMPString", e); }
|
|
1362
1367
|
}
|
|
1363
1368
|
|
|
1364
|
-
// [MS-WCCE] szOID-CERTSRV_CA_VERSION
|
|
1369
|
+
// [MS-WCCE] szOID-CERTSRV_CA_VERSION is an INTEGER (0..2^32-1) = (caKeyIndex << 16) | certIndex.
|
|
1365
1370
|
function msCaVersion(buf) {
|
|
1366
1371
|
var C = ns.prefix + "/bad-ms-ca-version";
|
|
1367
1372
|
var n = decodeTop(buf, C, "CACertVersion");
|
|
@@ -1370,7 +1375,7 @@ function certExtensionDecoders(ns) {
|
|
|
1370
1375
|
return { caVersion: v, caKeyIndex: v >>> 16, certIndex: v & 0xffff };
|
|
1371
1376
|
}
|
|
1372
1377
|
|
|
1373
|
-
// [MS-WCCE] szOID-CERTSRV_PREVIOUS_CERT_HASH
|
|
1378
|
+
// [MS-WCCE] szOID-CERTSRV_PREVIOUS_CERT_HASH holds the SHA-1 thumbprint of the prior CA certificate.
|
|
1374
1379
|
// AD CS stamps the Windows certificate THUMBPRINT here, which Windows computes with SHA-1 regardless
|
|
1375
1380
|
// of the certificate's signature algorithm, so the OCTET STRING is exactly 20 octets. An empty or
|
|
1376
1381
|
// other-length value is malformed renewal metadata and fails closed.
|
|
@@ -1418,7 +1423,7 @@ function certExtensionDecoders(ns) {
|
|
|
1418
1423
|
// and the RFC 5280 sec. 4.2 per-OID uniqueness.
|
|
1419
1424
|
function extension(ns) {
|
|
1420
1425
|
return schema.decode(function (ext) {
|
|
1421
|
-
// Extension ::= SEQUENCE { extnID, critical DEFAULT FALSE, extnValue }
|
|
1426
|
+
// Extension ::= SEQUENCE { extnID, critical DEFAULT FALSE, extnValue } is a
|
|
1422
1427
|
// UNIVERSAL SEQUENCE of exactly 2 (critical omitted) or 3 children. A
|
|
1423
1428
|
// context-tagged item (e.g. [5]{OID, OCTET STRING}) or a wrong child count
|
|
1424
1429
|
// is malformed; assert the tag, don't just count children (fail closed).
|
|
@@ -1441,7 +1446,7 @@ function extension(ns) {
|
|
|
1441
1446
|
});
|
|
1442
1447
|
}
|
|
1443
1448
|
// opts.implicitTag (optional): read Extensions as a [tag] IMPLICIT SEQUENCE OF
|
|
1444
|
-
// Extension
|
|
1449
|
+
// Extension, where the context tag REPLACES the universal SEQUENCE tag, for the CRMF
|
|
1445
1450
|
// CertTemplate extensions [9] (RFC 4211 sec. 5). With no opts the shape is a bare
|
|
1446
1451
|
// universal SEQUENCE OF, byte-identical to every existing caller.
|
|
1447
1452
|
function extensions(ns, opts) {
|
|
@@ -1458,8 +1463,8 @@ function extensions(ns, opts) {
|
|
|
1458
1463
|
|
|
1459
1464
|
// SubjectPublicKeyInfo ::= SEQUENCE { algorithm AlgorithmIdentifier,
|
|
1460
1465
|
// subjectPublicKey BIT STRING } (RFC 5280 sec. 4.1.2.7, RFC 2986 sec. 4.1). Asserted as a
|
|
1461
|
-
// universal SEQUENCE
|
|
1462
|
-
// two well-formed children is
|
|
1466
|
+
// universal SEQUENCE, so a context-tagged or SET-tagged constructed node carrying
|
|
1467
|
+
// two well-formed children is not a SubjectPublicKeyInfo. Shared by the
|
|
1463
1468
|
// certificate and CSR parsers.
|
|
1464
1469
|
// opts.implicitTag (optional): read the SubjectPublicKeyInfo as a [tag] IMPLICIT
|
|
1465
1470
|
// SEQUENCE (a context-class constructed node whose children are algorithm +
|
|
@@ -1479,7 +1484,7 @@ function spki(ns, opts) {
|
|
|
1479
1484
|
publicKey: { unusedBits: m.fields.subjectPublicKey.value.unusedBits, bytes: m.fields.subjectPublicKey.value.bytes },
|
|
1480
1485
|
// `bytes` is the importable SubjectPublicKeyInfo DER. In IMPLICIT [tag]
|
|
1481
1486
|
// mode the wire node leads with the context tag, so recover the SEQUENCE
|
|
1482
|
-
// encoding a consumer imports / hashes
|
|
1487
|
+
// encoding a consumer imports / hashes, and not the [tag] wire form.
|
|
1483
1488
|
bytes: opts.implicitTag != null ? asn1.sequenceTlv(m.node) : m.node.bytes,
|
|
1484
1489
|
};
|
|
1485
1490
|
},
|
|
@@ -1487,7 +1492,7 @@ function spki(ns, opts) {
|
|
|
1487
1492
|
}
|
|
1488
1493
|
|
|
1489
1494
|
// Attribute ::= SEQUENCE { type OBJECT IDENTIFIER, values SET OF AttributeValue }.
|
|
1490
|
-
// AttributeValue is ANY, kept as raw DER (node.bytes)
|
|
1495
|
+
// AttributeValue is ANY, kept as raw DER (node.bytes), so an unrecognized attribute
|
|
1491
1496
|
// type never fails the parse. values is SET SIZE (1..MAX) by default: an empty SET
|
|
1492
1497
|
// is rejected; there is NO SET-OF uniqueness. Shared by the CSR (requested
|
|
1493
1498
|
// attributes, RFC 2986 sec. 4.1) and PKCS#8 (private-key attributes, RFC 5958 sec. 2).
|
|
@@ -1546,8 +1551,8 @@ function rootSequenceChildren(root, minLen, maxLen) {
|
|
|
1546
1551
|
// bound parser so a format declares its configuration once and never re-writes the
|
|
1547
1552
|
// coerce -> decode -> walk wrapper. `opts`: { pemLabel, PemError, ErrorClass,
|
|
1548
1553
|
// prefix, what, topSchema, ns }.
|
|
1549
|
-
// The top-level schema's reject code
|
|
1550
|
-
// structure at all
|
|
1554
|
+
// The top-level schema's reject code, the verdict for a DER that is not this
|
|
1555
|
+
// structure at all, is `<prefix>/not-a-<structure>` (x509/not-a-certificate,
|
|
1551
1556
|
// cms/not-a-content-info, ...). Three codes predate that convention and stay
|
|
1552
1557
|
// frozen on the public error-code surface (ocsp/bad-ocsp-response,
|
|
1553
1558
|
// tsp/bad-response, crmf/bad-cert-req-messages); a new format follows the
|
|
@@ -1558,10 +1563,10 @@ function makeParser(opts) {
|
|
|
1558
1563
|
|
|
1559
1564
|
// makeRecordingParser(opts, kind) -> a parser that also records the bytes it read.
|
|
1560
1565
|
//
|
|
1561
|
-
// The pairing
|
|
1566
|
+
// The pairing lives here and is not repeated per format, because it is one decision, not four: a
|
|
1562
1567
|
// structure whose parse feeds a VERDICT needs its fields bound to the byte string they came from,
|
|
1563
1568
|
// since a parsed structure presents the signed range, the signature and the fields that range
|
|
1564
|
-
// encodes as separate properties
|
|
1569
|
+
// encodes as separate properties, and a caller can hand back a genuine range beside substituted
|
|
1565
1570
|
// fields. `kind` is the tag the matching door re-derives under, so a certificate cannot be presented
|
|
1566
1571
|
// where a CRL is expected. The record lives in guard-parsed, off the object, unreachable through it.
|
|
1567
1572
|
function makeRecordingParser(opts, kind) {
|
|
@@ -1576,10 +1581,10 @@ function makeRecordingParser(opts, kind) {
|
|
|
1576
1581
|
// shape, the arity, the signature extraction and the raw tbs / outer-signature
|
|
1577
1582
|
// bytes (for the cert/CRL outer==inner agreement check) are owned here once, and
|
|
1578
1583
|
// each format's `opts.build(envelope, ctx)` shapes its own object from the
|
|
1579
|
-
// envelope. A CSR's build simply omits the agreement check
|
|
1580
|
-
// signature AlgorithmIdentifier
|
|
1584
|
+
// envelope. A CSR's build simply omits the agreement check, since its CRI has no inner
|
|
1585
|
+
// signature AlgorithmIdentifier. The omission is structural, not a copy that
|
|
1581
1586
|
// forgot a guard.
|
|
1582
|
-
// signatureValue is surfaced
|
|
1587
|
+
// signatureValue is surfaced raw as { unusedBits, bytes }: for the envelope
|
|
1583
1588
|
// family, octet alignment is a verify-layer rule (path-validate rejects
|
|
1584
1589
|
// unusedBits != 0 for certification paths; an external verifier must do the
|
|
1585
1590
|
// same). A format with no in-tree verify layer (OCSP, CMP protection, CRMF
|