@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-c509.js
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
* @title C509
|
|
8
8
|
* @intro C509 CBOR-encoded certificates (draft-ietf-cose-cbor-encoded-cert). A compact CBOR
|
|
9
9
|
* re-encoding of an X.509 v3 certificate: a deterministic-CBOR array of exactly 11 elements
|
|
10
|
-
* (10 TBS fields + the issuer signature). Two modes
|
|
11
|
-
* C509, 3
|
|
10
|
+
* (10 TBS fields + the issuer signature). Two modes: c509CertificateType 2 is a natively-signed
|
|
11
|
+
* C509, and 3 is a CBOR re-encoding of a DER X.509 certificate that inverts byte-for-byte to the
|
|
12
12
|
* original DER (so the original signature still verifies). It decodes CBOR, not DER, so it is
|
|
13
|
-
* reached by an explicit pki.schema.c509.parse call and is
|
|
13
|
+
* reached by an explicit pki.schema.c509.parse call and is never auto-routed by pki.schema.parse.
|
|
14
14
|
* @card Composes the shipped pki.cbor codec (core-deterministic, fail-closed) + the X.509 model.
|
|
15
15
|
*/
|
|
16
16
|
|
|
@@ -36,9 +36,9 @@ function _err(code, message, cause) { return new C509Error(code, message, cause)
|
|
|
36
36
|
var MAX_EPOCH_SECONDS = 8640000000000n;
|
|
37
37
|
|
|
38
38
|
// The C509 integer registries (draft-20 sec. 8.6/sec. 8.8/sec. 8.14/sec. 8.15): a C509 int is a compact ALIAS of an
|
|
39
|
-
// OID, resolved to the
|
|
40
|
-
// OID NAME (never a dotted-decimal literal
|
|
41
|
-
// yields the dotted string. A row whose target name is not registered fails closed at module load.
|
|
39
|
+
// OID, resolved to the same name oid.byName returns for the DER form. Declared as int -> registered
|
|
40
|
+
// OID NAME (never a dotted-decimal literal, which the oid-dotted-decimal-literal gate enforces);
|
|
41
|
+
// oid.byName then yields the dotted string. A row whose target name is not registered fails closed at module load.
|
|
42
42
|
function _name(n) { var d = oid.byName(n); if (!d) { throw new Error("schema-c509: unregistered OID name " + JSON.stringify(n)); } return n; }
|
|
43
43
|
|
|
44
44
|
// sec. 8.14 issuerSignatureAlgorithm / signature (the subset v1 covers; negative = legacy SHA-1 values).
|
|
@@ -58,8 +58,8 @@ var PK_ALG_BY_INT = {
|
|
|
58
58
|
var EC_FIELD_BYTES = { "prime256v1": 32, "secp384r1": 48, "secp521r1": 66 };
|
|
59
59
|
|
|
60
60
|
// sec. 8.6 RDN attribute types (abs(int) -> name; the sign selects the X.509 string type: positive utf8String,
|
|
61
|
-
// negative printableString). An attribute whose value is
|
|
62
|
-
// represented using a non-negative int" (draft sec. 3.1.4)
|
|
61
|
+
// negative printableString). An attribute whose value is always an IA5String is instead "unambiguously
|
|
62
|
+
// represented using a non-negative int" (draft sec. 3.1.4). emailAddress is int 0, so its value reconstructs
|
|
63
63
|
// as an IA5String and its sign carries no string-type meaning.
|
|
64
64
|
var ATTR_BY_INT = {
|
|
65
65
|
0: _name("emailAddress"),
|
|
@@ -98,7 +98,7 @@ var EXT_BY_INT = {
|
|
|
98
98
|
30: _name("inhibitAnyPolicy"),
|
|
99
99
|
31: _name("subjectInfoAccess"),
|
|
100
100
|
// RFC 3779 resource-delegation extensions, and their RFC 8360 "v2" twins, which the draft
|
|
101
|
-
// encodes "exactly like" the originals
|
|
101
|
+
// encodes "exactly like" the originals: same two codecs, four registry rows (sec. 8.8).
|
|
102
102
|
32: _name("ipAddrBlocks"),
|
|
103
103
|
33: _name("autonomousSysIds"),
|
|
104
104
|
34: _name("ipAddrBlocksV2"),
|
|
@@ -107,7 +107,7 @@ var EXT_BY_INT = {
|
|
|
107
107
|
38: _name("tlsFeature"),
|
|
108
108
|
};
|
|
109
109
|
// The extensions whose compact CBOR value form (sec. 3.3) is invertible to/from the DER extnValue.
|
|
110
|
-
// A registered extension
|
|
110
|
+
// A registered extension outside this set carries its value as the ~oid + byte-string form (sec. 3.7).
|
|
111
111
|
var EXT_COMPACT = {
|
|
112
112
|
subjectKeyIdentifier: 1, keyUsage: 1, basicConstraints: 1, authorityKeyIdentifier: 1,
|
|
113
113
|
extKeyUsage: 1, inhibitAnyPolicy: 1, ocspNoCheck: 1, tlsFeature: 1,
|
|
@@ -130,7 +130,7 @@ Object.keys(EKU_BY_INT).forEach(function (k) { EKU_TO_INT[oid.byName(EKU_BY_INT[
|
|
|
130
130
|
|
|
131
131
|
// The shared pkix namespace + GeneralName leaves the general-name value codec composes for the DER ->
|
|
132
132
|
// CBOR direction (the CVE-2009-2408 IA5 control-byte guard, the otherName/directoryName shape, the
|
|
133
|
-
// iPAddress length and RFC 9549 subtree-base length are all enforced there
|
|
133
|
+
// iPAddress length and RFC 9549 subtree-base length are all enforced there, never re-validated by hand).
|
|
134
134
|
var NS = pkix.makeNS("c509", C509Error, oid);
|
|
135
135
|
var GN_LEAF = pkix.generalName(NS, { decodeValue: true }); // SAN-form GeneralName leaf
|
|
136
136
|
var GN_LEAF_SUBTREE = pkix.generalName(NS, { decodeValue: true, subtreeBase: true }); // name-constraints subtree base
|
|
@@ -138,7 +138,7 @@ var GN_LEAF_SUBTREE = pkix.generalName(NS, { decodeValue: true, subtreeBase: tru
|
|
|
138
138
|
// sec. 8.13 C509 General Names registry: for the standard forms the C509 int == the RFC 5280 sec. 4.2.1.6
|
|
139
139
|
// GeneralName CHOICE context tag (1 rfc822Name, 2 dNSName, 4 directoryName, 6 URI, 7 iPAddress, 8
|
|
140
140
|
// registeredID); the _generalNameToDer / _generalNameFromDer switches carry that mapping directly.
|
|
141
|
-
// x400Address [3] and ediPartyName [5] have no registry row
|
|
141
|
+
// x400Address [3] and ediPartyName [5] have no registry row, so a GeneralNames carrying either is not
|
|
142
142
|
// compact-encodable (the enclosing extension falls back to ~oid + byte-string).
|
|
143
143
|
// The id-on otherName specials get their own negative C509 ints (sec. 8.13 / sec. 3.3): int -> the id-on
|
|
144
144
|
// type-id OID name; the derived inverse keys on the resolved OID so a decode selects the negative int.
|
|
@@ -180,8 +180,8 @@ Object.keys(PQ_BY_INT).forEach(function (k) { PQ_TO_INT[oid.byName(PQ_BY_INT[k])
|
|
|
180
180
|
// ---- field readers (the unwrapped ~biguint / ~time / ~oid contracts; draft-20 sec. 3.1) ----
|
|
181
181
|
|
|
182
182
|
// ~biguint (sec. 3.1.2): a BARE byte string (major type 2), big-endian magnitude, the non-negative
|
|
183
|
-
// leading 0x00 OMITTED.
|
|
184
|
-
// <= 8-byte content
|
|
183
|
+
// leading 0x00 OMITTED. This is not the shipped read.biguint, which requires the tag-2 wrapper and
|
|
184
|
+
// rejects <= 8-byte content. A leading 0x00 is non-minimal; content over the cap is rejected.
|
|
185
185
|
function _biguint(node, code, label) {
|
|
186
186
|
if (!node || node.majorType !== 2) throw _err(code, label + " must be an unwrapped CBOR byte string (~biguint)");
|
|
187
187
|
var b = node.content;
|
|
@@ -223,11 +223,11 @@ function _algorithm(node, byInt, code, label) {
|
|
|
223
223
|
if (node.majorType === 4 && node.children && node.children.length === 2) {
|
|
224
224
|
var a = _oidName(node.children[0], code, label);
|
|
225
225
|
// The [~oid, params] form carries the DER parameters as a CBOR byte string; a non-byte-string here
|
|
226
|
-
// is malformed and cannot be reconstructed (b.raw would append garbage)
|
|
226
|
+
// is malformed and cannot be reconstructed (b.raw would append garbage), so it fails closed.
|
|
227
227
|
if (node.children[1].majorType !== 2) throw _err(code, label + " algorithm parameters must be a CBOR byte string");
|
|
228
|
-
// The parameters carry
|
|
228
|
+
// The parameters carry one complete DER element spliced into the reconstruction. An empty byte
|
|
229
229
|
// string is no element at all: it rebuilds the same AlgorithmIdentifier as the bare ~oid form,
|
|
230
|
-
// which is how the CDDL already spells "no parameters"
|
|
230
|
+
// which is how the CDDL already spells "no parameters", so accepting it would give one
|
|
231
231
|
// algorithm two encodings, and one X.509 signature would cover both.
|
|
232
232
|
if (node.children[1].content.length === 0) throw _err(code, label + " algorithm parameters must not be an empty byte string; omit them with the bare ~oid form (draft sec. 3.1.3)");
|
|
233
233
|
return { name: a.name, oid: a.oid, parameters: node.children[1].content };
|
|
@@ -253,13 +253,13 @@ function _specialText(node) {
|
|
|
253
253
|
throw _err("c509/bad-name", "an attribute value is not a C509 SpecialText (text / bytes / tag-48)");
|
|
254
254
|
}
|
|
255
255
|
|
|
256
|
-
// draft sec. 3.1.4 fixes
|
|
256
|
+
// draft sec. 3.1.4 fixes which of the three SpecialText spellings a value takes; the choice belongs to
|
|
257
257
|
// the specification, not the sender. A text string of an even length >= 2 drawn only from '0'-'9'/'a'-'f'
|
|
258
258
|
// is encoded as a byte string; a text string of the form "HH-HH-HH-HH-HH-HH-HH-HH" is encoded as a tag-48
|
|
259
259
|
// MAC address, 48-bit when it matches "HH-HH-HH-FF-FE-HH-HH-HH" and 64-bit otherwise; anything else is a
|
|
260
260
|
// text string. Accepting a second spelling of one value would give the reconstructed certificate more than
|
|
261
261
|
// one C509 encoding, so a single X.509 signature would cover them all and two distinct byte strings would
|
|
262
|
-
// name one certificate.
|
|
262
|
+
// name one certificate. A natively signed certificate is exempt: sec. 3.1.4 says bytes and tag 48
|
|
263
263
|
// there "do not correspond to any predefined text string encoding and may also be used for other attribute
|
|
264
264
|
// types", so no canonical text spelling exists to hold one to.
|
|
265
265
|
var _HEX_OPTIMIZED = /^(?:[0-9a-f]{2})+$/;
|
|
@@ -291,10 +291,10 @@ function _assertCanonicalSpecialText(node, isNative, E) {
|
|
|
291
291
|
// commonName | an array of RDNAttributes. Surfaces { dn, rdns, eui64? } shape-compatible with x509.
|
|
292
292
|
// An rdn's (attribute type, sign) pair DECLARES an X.509 string type, so its text must be valid for that type --
|
|
293
293
|
// a printableString sign over a non-PrintableString character, a non-ASCII emailAddress (IA5String-only), or a
|
|
294
|
-
// value outside the type's SIZE. Assert it by running the
|
|
294
|
+
// value outside the type's SIZE. Assert it by running the same builder the reconstruction uses, so a Name this
|
|
295
295
|
// parse accepts is always one it can rebuild, and a natively-signed certificate (which never reconstructs) is
|
|
296
|
-
// held to the identical rule.
|
|
297
|
-
// The builder's asn1/* fault is re-thrown in this module's domain
|
|
296
|
+
// held to the identical rule. Both Name forms get it: the array form and the bare single-commonName form.
|
|
297
|
+
// The builder's asn1/* fault is re-thrown in this module's domain so it cannot leak onto the parse surface.
|
|
298
298
|
function _assertAttrValue(rdn) {
|
|
299
299
|
try { _reconAttrValue(rdn); }
|
|
300
300
|
catch (e) {
|
|
@@ -309,11 +309,11 @@ function _name509(node, isSubject, isNative) {
|
|
|
309
309
|
if (node.majorType === 3 || node.majorType === 2 || node.majorType === 6) {
|
|
310
310
|
var sv = _specialText(node);
|
|
311
311
|
_assertCanonicalSpecialText(node, isNative, "c509/bad-name");
|
|
312
|
-
// The bare form is a single commonName
|
|
312
|
+
// The bare form is a single commonName. Its value is held to the same rules the array form applies, so a
|
|
313
313
|
// natively-signed certificate (which never reconstructs) cannot carry a value the reconstruction would refuse.
|
|
314
314
|
// A tag-48 MAC renders to a fixed 17-character EUI-64 string that satisfies every value rule by
|
|
315
|
-
// construction, and _reconAttrValue short-circuits on eui64 before any of them
|
|
316
|
-
//
|
|
315
|
+
// construction, and _reconAttrValue short-circuits on eui64 before any of them, which makes an assertion
|
|
316
|
+
// here a no-op. The rules bind on the TEXT forms below.
|
|
317
317
|
if (sv.eui64) return { rdns: [{ type: "commonName", eui64: sv.eui64 }], eui64: sv.eui64, dn: "CN=" + _macToEui64String(sv.eui64) };
|
|
318
318
|
var val = sv.text !== undefined ? sv.text : sv.hex;
|
|
319
319
|
var bareRdn = { type: "commonName", value: val };
|
|
@@ -325,23 +325,23 @@ function _name509(node, isSubject, isNative) {
|
|
|
325
325
|
var parts = [];
|
|
326
326
|
var kids = node.children || [];
|
|
327
327
|
// Each RDN attribute is an (attributeType, attributeValue) pair; an odd-length array is a dangling
|
|
328
|
-
// attribute type with no value
|
|
328
|
+
// attribute type with no value, so the whole Name is rejected.
|
|
329
329
|
if (kids.length % 2 !== 0) throw _err("c509/bad-name", "a C509 Name array must be attribute-type/value pairs (dangling attribute type)");
|
|
330
330
|
// RFC 5280 sec. 4.1.2.4 -- the issuer MUST be a non-empty distinguished name (only the SUBJECT may be empty,
|
|
331
|
-
// for the subjectAltName case). An empty issuer array would reconstruct a certificate this toolkit's
|
|
331
|
+
// for the subjectAltName case). An empty issuer array would reconstruct a certificate this toolkit's own
|
|
332
332
|
// x509.parse refuses to load (x509/bad-issuer), so the codec must not produce one.
|
|
333
333
|
if (kids.length === 0 && !isSubject) throw _err("c509/bad-name", "the issuer Name must not be empty (RFC 5280 sec. 4.1.2.4)");
|
|
334
334
|
for (var i = 0; i + 1 < kids.length; i += 2) {
|
|
335
|
-
// The attributeType slot must be a CBOR integer (major type 0/1). Guard the major type
|
|
335
|
+
// The attributeType slot must be a CBOR integer (major type 0/1). Guard the major type before the read
|
|
336
336
|
// so a non-integer type slot fails in this module's own domain (c509/bad-name), matching the value slot,
|
|
337
|
-
//
|
|
337
|
+
// and cbor.read.int's cbor/unexpected-major fault never leaks onto the parse surface. Reached with
|
|
338
338
|
// attacker-controlled bytes via the sec. 8.13 directoryName general name (subjectAltName, authorityKey-
|
|
339
339
|
// Identifier issuer, cRLDistributionPoints cRLIssuer, nameConstraints subtree base) and the top-level Name.
|
|
340
340
|
if (kids[i].majorType !== 0 && kids[i].majorType !== 1) throw _err("c509/bad-name", "a C509 Name attribute type must be a CBOR integer");
|
|
341
341
|
var ti = Number(cbor.read.int(kids[i]));
|
|
342
342
|
// draft sec. 3.1.4: "in natively signed C509 certificates all CBOR ints SHALL be non-negative." The sign
|
|
343
|
-
// exists
|
|
344
|
-
// have
|
|
343
|
+
// exists only to reproduce the string type of an original X.509 DER, which a native certificate does not
|
|
344
|
+
// have. A negative attributeType there is an encoding no conformant producer emits, and accepting it
|
|
345
345
|
// would let this parser read a natively-signed name a conformant peer must reject.
|
|
346
346
|
if (isNative && ti < 0) throw _err("c509/bad-name", "a natively signed C509 Name attribute type integer must be non-negative (draft sec. 3.1.4), got " + ti);
|
|
347
347
|
var tname = ATTR_BY_INT[Math.abs(ti)];
|
|
@@ -349,7 +349,7 @@ function _name509(node, isSubject, isNative) {
|
|
|
349
349
|
var v = _specialText(kids[i + 1]);
|
|
350
350
|
_assertCanonicalSpecialText(kids[i + 1], isNative, "c509/bad-name");
|
|
351
351
|
var vv = v.text !== undefined ? v.text : (v.hex !== undefined ? v.hex : _macToEui64String(v.eui64));
|
|
352
|
-
// The
|
|
352
|
+
// The rdn carries the RENDERED text, because the EUI shortcut
|
|
353
353
|
// in _reconAttrValue is unconditional and would take precedence over the attribute's declared string type,
|
|
354
354
|
// rebuilding a tag-48 emailAddress or countryName as a UTF8String. Only the bare-SpecialText form above is
|
|
355
355
|
// always a commonName and may carry it; here the value is held to whatever type the attribute integer declares.
|
|
@@ -361,21 +361,21 @@ function _name509(node, isSubject, isNative) {
|
|
|
361
361
|
// sec. 3.1.4: "If Name contains a single 'common name' attribute with attributeType = +1, it is for
|
|
362
362
|
// compactness encoded as just the SpecialText containing the single attribute value." The array
|
|
363
363
|
// spelling of that one case is therefore a second encoding of a name that already has a canonical
|
|
364
|
-
// one. Only +1 has a bare form
|
|
364
|
+
// one. Only +1 has a bare form; a negative (printableString) commonName keeps the array.
|
|
365
365
|
if (!isNative && rdns.length === 1 && rdns[0].type === "commonName" && !rdns[0].printable) {
|
|
366
366
|
throw _err("c509/bad-name", "a Name holding a single +1 commonName must be encoded as a bare SpecialText, not an array (draft sec. 3.1.4)");
|
|
367
367
|
}
|
|
368
368
|
return { rdns: rdns, dn: parts.join(", ") };
|
|
369
369
|
}
|
|
370
|
-
// The rendered dn uses the
|
|
371
|
-
// certificate order joined by ", ", which is what `openssl x509 -subject` prints
|
|
370
|
+
// The rendered dn uses the same spelling as every other name renderer here (schema-pkix): the RDNs in
|
|
371
|
+
// certificate order joined by ", ", which is what `openssl x509 -subject` prints. One certificate therefore
|
|
372
372
|
// has one dn string whichever parser read it, and the interop gate can pin both against openssl. It is
|
|
373
373
|
// deliberately not the RFC 4514 / LDAP distinguishedName (which reverses the RDNs and drops the space).
|
|
374
374
|
// What IS taken from RFC 4514 is sec. 2.4 value escaping, through the shared guard: raw concatenation
|
|
375
375
|
// would let a single attribute whose value contains a comma render identically to a genuine multi-RDN
|
|
376
|
-
// name (a spoofable identity string, and the
|
|
376
|
+
// name (a spoofable identity string, and the only name surface a natively signed certificate has, since
|
|
377
377
|
// it never reconstructs DER), and would carry control bytes into logs.
|
|
378
|
-
// The
|
|
378
|
+
// The label table is the one schema-pkix renders with (pki.C.NAMES.DN_SHORT), not a local list: a hand-rolled
|
|
379
379
|
// one covers whichever attributes it was written for and silently renders the rest by their long name,
|
|
380
380
|
// so the same certificate would still read differently depending on which parser produced the string.
|
|
381
381
|
function _shortName(n) { return (n && constants.NAMES.DN_SHORT[n]) || n; }
|
|
@@ -387,7 +387,7 @@ function _shortName(n) { return (n && constants.NAMES.DN_SHORT[n]) || n; }
|
|
|
387
387
|
// the compact form only when it round-trips byte-for-byte (else the ~oid + byte-string fallback).
|
|
388
388
|
|
|
389
389
|
// A CBOR unsigned integer node -> its BigInt value (major type 0 guarantees non-negative). Arbitrary
|
|
390
|
-
// precision: a DER INTEGER holds any width, so no cap
|
|
390
|
+
// precision: a DER INTEGER holds any width, so there is no cap; the round-trip guard verifies the reconstruction.
|
|
391
391
|
function _cborUint(node, label) {
|
|
392
392
|
if (node.majorType !== 0) throw _err("c509/bad-extensions", "a " + label + " value must be a CBOR unsigned integer");
|
|
393
393
|
return cbor.read.int(node);
|
|
@@ -398,10 +398,10 @@ function _cborIntVal(node, label) {
|
|
|
398
398
|
return cbor.read.int(node);
|
|
399
399
|
}
|
|
400
400
|
// A non-negative reconstruct-side count (a policyConstraints / inhibitAnyPolicy SkipCerts, a basicConstraints
|
|
401
|
-
// pathLenConstraint) narrowed to [0, 2^31-1]
|
|
402
|
-
// (schema-pkix)
|
|
403
|
-
//
|
|
404
|
-
// produce nothing that decoder rejects. Returns a Number; b.integer narrows it to the minimal DER INTEGER.
|
|
401
|
+
// pathLenConstraint) narrowed to [0, 2^31-1] through guard.range.uint31, the same bound the toolkit's own DER
|
|
402
|
+
// decoders (schema-pkix) put on these INTEGER (0..MAX) counts. A native CBOR value past 2^31-1 fails closed
|
|
403
|
+
// here; it never reconstructs a DER the toolkit's own decoder would then reject, because the reconstruct path
|
|
404
|
+
// must produce nothing that decoder rejects. Returns a Number; b.integer narrows it to the minimal DER INTEGER.
|
|
405
405
|
function _boundCount(value, label) {
|
|
406
406
|
return guard.range.uint31(value, _err, "c509/bad-extensions", label + " (0..2^31-1)");
|
|
407
407
|
}
|
|
@@ -414,7 +414,7 @@ function _ekuPurposeOid(node) {
|
|
|
414
414
|
return oid.byName(nm);
|
|
415
415
|
}
|
|
416
416
|
// A ~oid KeyPurposeId routes through _oidName, which validates the BER OID content and remaps an oid/*
|
|
417
|
-
// fault to c509/bad-extensions (fault preserved as .cause)
|
|
417
|
+
// fault to c509/bad-extensions (fault preserved as .cause). A non-byte-string node fails closed there too.
|
|
418
418
|
return _oidName(node, "c509/bad-extensions", "an extKeyUsage KeyPurposeId").oid;
|
|
419
419
|
}
|
|
420
420
|
|
|
@@ -424,8 +424,8 @@ function _ekuPurposeOid(node) {
|
|
|
424
424
|
// authorityInfoAccess, ...), so a tag/int mapping bug cannot diverge across them. The DER -> CBOR side
|
|
425
425
|
// composes the shared pkix.generalName leaf; the CBOR -> DER side builds directly with asn1.build.*, the
|
|
426
426
|
// exact inverse. Every encode stays behind the extension-level byte-exact round-trip guard
|
|
427
|
-
// (_tryCompactExtValue), so a member the codec cannot invert byte-for-byte falls the
|
|
428
|
-
// to the conformant ~oid + byte-string form (sec. 3.7)
|
|
427
|
+
// (_tryCompactExtValue), so a member the codec cannot invert byte-for-byte falls the whole extension back
|
|
428
|
+
// to the conformant ~oid + byte-string form (sec. 3.7). A partial GeneralNames is never emitted.
|
|
429
429
|
|
|
430
430
|
// The unwrapped ~oid CBOR form (a bare byte string of the BER OID content octets; RFC 9090).
|
|
431
431
|
function _oidCbor(dotted) { return cbor.build.byteString(asn1.encodeOidContent(dotted)); }
|
|
@@ -434,13 +434,13 @@ function _isCborNull(node) { return node.majorType === 7 && node.ai === 22; }
|
|
|
434
434
|
|
|
435
435
|
// A CBOR text node -> the IA5String content octets of an IMPLICIT [n] GeneralName (rfc822Name/dNSName/URI).
|
|
436
436
|
// IA5String is 7-bit ASCII, so a code point > 0x7f cannot be one; a control byte (C0/DEL) is an injection
|
|
437
|
-
// vector (CVE-2009-2408) the shared name guard rejects
|
|
437
|
+
// vector (CVE-2009-2408) the shared name guard rejects. That matches the pkix.generalName decode side, so a
|
|
438
438
|
// value it rejects also fails the reconstruction direction (no round-trip guard runs on decode).
|
|
439
439
|
function _ia5Bytes(node, tag) {
|
|
440
440
|
if (node.majorType !== 3) throw _err("c509/bad-extensions", "a GeneralName [" + tag + "] value must be a CBOR text string (IA5String)");
|
|
441
441
|
var s = cbor.read.textString(node);
|
|
442
442
|
// An empty IA5 GeneralName is rejected by the shared pkix.generalName leaf (a non-empty rfc822Name /
|
|
443
|
-
// dNSName / URI), so
|
|
443
|
+
// dNSName / URI), so the reconstruction side rejects it too.
|
|
444
444
|
if (s.length === 0) throw _err("c509/bad-extensions", "a GeneralName [" + tag + "] IA5String must be non-empty");
|
|
445
445
|
for (var i = 0; i < s.length; i++) { if (s.charCodeAt(i) > 0x7f) throw _err("c509/bad-extensions", "a GeneralName [" + tag + "] IA5String has a non-ASCII code point"); }
|
|
446
446
|
var buf = Buffer.from(s, "latin1");
|
|
@@ -503,7 +503,7 @@ function _generalNamesToDer(node, isNative) {
|
|
|
503
503
|
return out;
|
|
504
504
|
}
|
|
505
505
|
// The inverse: a list of DER GeneralName nodes -> the flat CBOR items, or null if ANY member is not
|
|
506
|
-
// compact-representable (the whole enclosing extension then falls back
|
|
506
|
+
// compact-representable (the whole enclosing extension then falls back, and no partial GeneralNames is produced).
|
|
507
507
|
function _generalNamesFromDer(gnNodes) {
|
|
508
508
|
var out = [];
|
|
509
509
|
for (var i = 0; i < gnNodes.length; i++) {
|
|
@@ -514,7 +514,7 @@ function _generalNamesFromDer(gnNodes) {
|
|
|
514
514
|
return out;
|
|
515
515
|
}
|
|
516
516
|
|
|
517
|
-
// otherName [0] IMPLICIT SEQUENCE { type-id OBJECT IDENTIFIER, value [0] EXPLICIT ANY }
|
|
517
|
+
// otherName [0] IMPLICIT SEQUENCE { type-id OBJECT IDENTIFIER, value [0] EXPLICIT ANY }. The context tag
|
|
518
518
|
// REPLACES the SEQUENCE tag (RFC 5280 IMPLICIT tagging), so the [0] node holds type-id then value directly
|
|
519
519
|
// (no inner universal SEQUENCE). The generic form (C509 int 0) carries [ ~oid(type-id), bytes(the [0]
|
|
520
520
|
// EXPLICIT inner value's raw TLV) ]; the id-on specials (-1/-2/-3) carry the RFC 4108 / RFC 9598 /
|
|
@@ -525,7 +525,7 @@ function _otherNameToDer(node, intVal) {
|
|
|
525
525
|
if (node.majorType !== 4 || !node.children || node.children.length !== 2) throw _err("c509/bad-extensions", "a generic otherName value must be a CBOR [ ~oid, bytes ] pair");
|
|
526
526
|
typeId = _oidName(node.children[0], "c509/bad-extensions", "an otherName type-id").oid;
|
|
527
527
|
if (node.children[1].majorType !== 2) throw _err("c509/bad-extensions", "an otherName value must be a CBOR byte string (the [0] EXPLICIT inner TLV)");
|
|
528
|
-
// The [0] EXPLICIT inner TLV is an ANY spliced verbatim
|
|
528
|
+
// The [0] EXPLICIT inner TLV is an ANY spliced verbatim: strict-validate it, not just its framing.
|
|
529
529
|
inner = _requireStrictDerTlv(node.children[1].content, "c509/bad-extensions", "an otherName value");
|
|
530
530
|
} else if (intVal === -1) { // id-on-hardwareModuleName: [ ~oid(hwType), bytes(hwSerialNum) ] -> HardwareModuleName
|
|
531
531
|
if (node.majorType !== 4 || !node.children || node.children.length !== 2) throw _err("c509/bad-extensions", "an id-on-hardwareModuleName value must be a CBOR [ ~oid, bytes ] pair");
|
|
@@ -566,8 +566,9 @@ function _otherNameFromDer(v) {
|
|
|
566
566
|
}
|
|
567
567
|
if (inner.tagClass !== "universal" || inner.tagNumber !== asn1.TAGS.OCTET_STRING) return null; // onInt === -3: MACAddress ::= OCTET STRING
|
|
568
568
|
var mac; try { mac = asn1.read.octetString(inner); } catch (_e4) { return null; }
|
|
569
|
-
//
|
|
570
|
-
//
|
|
569
|
+
// mac is a PUBLIC hardware MAC address (an EUI-48/64 identifier, RFC 9542 / lamps-macaddress), not a
|
|
570
|
+
// MAC / HMAC / auth tag / secret; this is a length check.
|
|
571
|
+
// nosemgrep: pki-non-constant-time-secret-compare
|
|
571
572
|
if (mac.length !== 6 && mac.length !== 8) return null;
|
|
572
573
|
return [cbor.build.int(-3n), cbor.build.byteString(mac)];
|
|
573
574
|
}
|
|
@@ -621,23 +622,23 @@ function _ncIpFromDer(buf) {
|
|
|
621
622
|
//
|
|
622
623
|
// An RFC 3779 IPAddress is a BIT STRING whose unused-bit count carries the prefix length, so the
|
|
623
624
|
// draft maps it to the byte sequence `unusedBits || value`, which "preserves the exact information
|
|
624
|
-
// contained in the ASN.1 BIT STRING"
|
|
625
|
-
// IPAddressFamily the draft then picks
|
|
625
|
+
// contained in the ASN.1 BIT STRING", which stays lossless even for a prefix ending in zero bits. Per
|
|
626
|
+
// IPAddressFamily the draft then picks one of two forms and makes the choice a SHALL: if any of the
|
|
626
627
|
// family's byte sequences exceeds 8 octets the whole family uses the bytes form, otherwise the int
|
|
627
628
|
// form. Accepting the wrong one would give one DER two CBOR encodings, so decode enforces it.
|
|
628
629
|
//
|
|
629
|
-
// The int form is the big-endian integer of `(unusedBits + 1) || value
|
|
630
|
-
// non-zero leading octet, which is what makes the minimal big-endian representation unambiguous
|
|
630
|
+
// The int form is the big-endian integer of `(unusedBits + 1) || value`. The +1 guarantees a
|
|
631
|
+
// non-zero leading octet, which is what makes the minimal big-endian representation unambiguous,
|
|
631
632
|
// and every IPAddress after the first is stored as the DIFFERENCE from its predecessor. The chain
|
|
632
633
|
// runs flat over each address in order (a range contributes min then max) and RESETS at each
|
|
633
634
|
// family, so a family always opens with an absolute value.
|
|
634
635
|
//
|
|
635
636
|
// These integers reach 2^64-1: an IPv6 /48 already exceeds 2^53, and the draft's own A.5 vector
|
|
636
637
|
// lands there. Everything below is BigInt end to end and bounds through guard.range.uint64 (the
|
|
637
|
-
// BigInt-preserving guard)
|
|
638
|
+
// BigInt-preserving guard). The uint31 counter bound is never used here; it would reject a valid prefix.
|
|
638
639
|
|
|
639
640
|
// An asn1 node exposes tagClass + tagNumber (never a combined `.tag`), so a universal-type test
|
|
640
|
-
// has to check both
|
|
641
|
+
// has to check both: a context-tagged [16] must not read as a SEQUENCE.
|
|
641
642
|
function _isUniversal(n, tagNumber) { return !!n && n.tagClass === "universal" && n.tagNumber === tagNumber; }
|
|
642
643
|
|
|
643
644
|
// `unusedBits || value` -> the (unusedBits+1)||value integer. Returns null if the sequence cannot
|
|
@@ -650,7 +651,7 @@ function _ipSeqToInt(seq) {
|
|
|
650
651
|
return BigInt("0x" + Buffer.concat([head, seq.subarray(1)]).toString("hex"));
|
|
651
652
|
}
|
|
652
653
|
// The inverse: the minimal big-endian octets of `n`, with the leading octet decremented back to the
|
|
653
|
-
// unused-bit count. Returns null when `n` cannot be an encoded IPAddress
|
|
654
|
+
// unused-bit count. Returns null when `n` cannot be an encoded IPAddress: a leading octet outside
|
|
654
655
|
// 1..8 is not a DER unused-bit count, and past 8 octets the family was required to use the bytes form.
|
|
655
656
|
function _ipIntToSeq(n) {
|
|
656
657
|
if (n < 1n) return null;
|
|
@@ -662,7 +663,7 @@ function _ipIntToSeq(n) {
|
|
|
662
663
|
}
|
|
663
664
|
// The address width in octets of an address family, from its AFI (RFC 3779 sec. 2.2.3.3). Only a
|
|
664
665
|
// family whose width is known can have its canonical form checked, so an unrecognized AFI yields
|
|
665
|
-
// null and the caller declines to compact
|
|
666
|
+
// null and the caller declines to compact; it will not compact something it cannot verify.
|
|
666
667
|
var _IP_WIDTH = { 1: 4, 2: 16 };
|
|
667
668
|
|
|
668
669
|
// The lowest address an `unusedBits || value` sequence denotes, as `width` big-endian octets. DER
|
|
@@ -680,7 +681,7 @@ function _ipHigh(seq, width) {
|
|
|
680
681
|
for (var i = bits; i < width * 8; i++) v[i >> 3] |= 0x80 >> (i & 7);
|
|
681
682
|
return v;
|
|
682
683
|
}
|
|
683
|
-
// Big-endian octet-string compare, and "is `b` the immediate successor of `a`"
|
|
684
|
+
// Big-endian octet-string compare, and "is `b` the immediate successor of `a`": the test that
|
|
684
685
|
// distinguishes a legal gap from a contiguous pair the RFC requires be merged.
|
|
685
686
|
function _ipOctCmp(a, b) {
|
|
686
687
|
for (var i = 0; i < a.length; i++) { if (a[i] !== b[i]) return a[i] - b[i]; }
|
|
@@ -694,7 +695,7 @@ function _ipIsSuccessor(a, b) {
|
|
|
694
695
|
}
|
|
695
696
|
|
|
696
697
|
// RFC 3779 sec. 2.2.3.7: "any range of addresses that can be encoded as a prefix MUST be encoded
|
|
697
|
-
// using an IPAddress element", with the choice fixed by the spec's own pseudocode
|
|
698
|
+
// using an IPAddress element", with the choice fixed by the spec's own pseudocode. Let N be the
|
|
698
699
|
// count of matching leading bits of the low and high addresses; if every remaining bit of the low
|
|
699
700
|
// is zero AND every remaining bit of the high is one, the span IS the N-bit prefix and the range
|
|
700
701
|
// form is forbidden. Two encodings of one address span would otherwise both be legal.
|
|
@@ -727,9 +728,9 @@ function _famOctCmp(a, b) {
|
|
|
727
728
|
|
|
728
729
|
// Is `unusedBits || value` a sequence DER would accept as a BIT STRING? The declared unused low
|
|
729
730
|
// bits MUST be zero (RFC 3779 sec. 2.2.3.8 restates the DER rule), and a value with no octets can
|
|
730
|
-
// only declare zero unused bits.
|
|
731
|
-
//
|
|
732
|
-
//
|
|
731
|
+
// only declare zero unused bits. The check lives here, not in the BIT STRING builder: the builder's
|
|
732
|
+
// fault is an asn1/* error surfacing out of a CBOR-layer decode, and a caller handed a malformed
|
|
733
|
+
// compact value should see this module's own verdict, not the ASN.1 layer's.
|
|
733
734
|
function _ipSeqBitsClear(seq) {
|
|
734
735
|
var unused = seq[0], value = seq.subarray(1);
|
|
735
736
|
if (!value.length) return unused === 0;
|
|
@@ -739,7 +740,7 @@ function _ipSeqBitsClear(seq) {
|
|
|
739
740
|
|
|
740
741
|
// RFC 3779 sec. 2.2.3.6 fixes the canonical form of an address list: entries sorted on
|
|
741
742
|
// `<lowest address> | <prefix length>` (which is neither the DER byte order nor the compact integer
|
|
742
|
-
// order
|
|
743
|
+
// order; the RFC warns about the first and the second sorts the same wrong way), no pair
|
|
743
744
|
// overlapping, and any contiguous pair combined into one entry. All three bind together: a list
|
|
744
745
|
// violating any of them is not the one canonical encoding of its address set, so this codec
|
|
745
746
|
// declines to compact it and the extension keeps its original bytes.
|
|
@@ -759,20 +760,20 @@ function _ipRangesCanonical(bounds) {
|
|
|
759
760
|
// One family's IntIPAddressChoice / IPAddressChoice -> the DER IPAddressOrRange SEQUENCE list.
|
|
760
761
|
// The int form stores each address after the first as a DIFFERENCE from its predecessor, flat over
|
|
761
762
|
// every address in order (a range contributes min then max); the chain resets at each family, which
|
|
762
|
-
// is why `prev` starts null here
|
|
763
|
-
// ABSOLUTE is what gets bounded
|
|
763
|
+
// is why `prev` starts null here and is never threaded across families. The reconstructed
|
|
764
|
+
// ABSOLUTE is what gets bounded: the delta itself is an arbitrary CBOR int, and bounding it would
|
|
764
765
|
// miss a chain that walks out of range in steps.
|
|
765
766
|
function _ipChoiceToDer(items, afi) {
|
|
766
767
|
var out = [], prev = null, sawBytes = false, sawInt = false, seqs = [];
|
|
767
768
|
// An address may not be wider than its family: RFC 3779 sec. 2.2.3.8 sizes an IPAddress by the
|
|
768
769
|
// family, so a 5-octet address under AFI 1 is not an IPv4 address at all. Without this, a native
|
|
769
|
-
// C509 would reconstruct into a DER carrying an over-wide address
|
|
770
|
-
//
|
|
771
|
-
//
|
|
770
|
+
// C509 this codec had accepted would reconstruct into a DER carrying an over-wide address that the
|
|
771
|
+
// RFC forbids and an independent validator refuses. The mirror check lives on the encode side;
|
|
772
|
+
// both directions have to hold or the pair is not a bijection.
|
|
772
773
|
// Without the family's address width none of the RFC 3779 rules below can be evaluated: the
|
|
773
774
|
// width bound has nothing to compare against, and the low/high bounds that drive the order,
|
|
774
775
|
// overlap, adjacency and endpoint checks cannot be computed at all. Accepting such a family
|
|
775
|
-
// would therefore wave every one of those checks through
|
|
776
|
+
// would therefore wave every one of those checks through. A family this codec cannot
|
|
776
777
|
// measure is refused outright, matching the encode side, which declines to compact it. An
|
|
777
778
|
// `inherit` family is unaffected: it carries no addresses and never reaches here.
|
|
778
779
|
var width = _IP_WIDTH[afi];
|
|
@@ -833,9 +834,9 @@ function _ipChoiceToDer(items, afi) {
|
|
|
833
834
|
if (sawBytes && !seqs.some(function (s) { return s.length > 8; })) {
|
|
834
835
|
throw _err("c509/bad-extensions", "an IPAddressFamily whose addresses all fit 8 octets must use the integer form (sec. 3.3)");
|
|
835
836
|
}
|
|
836
|
-
// The
|
|
837
|
+
// The same RFC 3779 sec. 2.2.3.6 canonical form the encode side requires, enforced here too.
|
|
837
838
|
// draft sec. 3.3 says "The limitations specified in [RFC3779] apply here as well", so a compact
|
|
838
|
-
// list that is unsorted, overlapping, or unmerged is malformed C509
|
|
839
|
+
// list that is unsorted, overlapping, or unmerged is malformed C509, and reconstructing it
|
|
839
840
|
// would emit a certificate an independent validator refuses, from CBOR this codec had accepted.
|
|
840
841
|
// Both directions must hold or the pair is not a bijection.
|
|
841
842
|
for (var r = 0; r < bounds.length; r++) {
|
|
@@ -848,7 +849,7 @@ function _ipChoiceToDer(items, afi) {
|
|
|
848
849
|
}
|
|
849
850
|
|
|
850
851
|
// DER IPAddressChoice (a SEQUENCE of BIT STRING / SEQUENCE-of-two-BIT-STRING) -> the compact CBOR
|
|
851
|
-
// array. The form is chosen per family and is a SHALL: the bytes form applies to the
|
|
852
|
+
// array. The form is chosen per family and is a SHALL: the bytes form applies to the whole family
|
|
852
853
|
// as soon as any one sequence exceeds 8 octets, otherwise every member takes the delta-coded int
|
|
853
854
|
// form. Returns null on any shape the compact form cannot carry exactly.
|
|
854
855
|
function _ipChoiceFromDer(ch, afi) {
|
|
@@ -869,12 +870,12 @@ function _ipChoiceFromDer(ch, afi) {
|
|
|
869
870
|
groups.push(pair);
|
|
870
871
|
for (var k = 0; k < pair.length; k++) flat.push(pair[k]);
|
|
871
872
|
}
|
|
872
|
-
// The list must be in RFC 3779 sec. 2.2.3.6 canonical form
|
|
873
|
+
// The list must be in RFC 3779 sec. 2.2.3.6 canonical form: sorted, non-overlapping, and with
|
|
873
874
|
// every contiguous pair already merged. A list that is not is not the one canonical encoding of
|
|
874
|
-
// its address set, so it is
|
|
875
|
+
// its address set, so it is never re-encoded into a conforming one. This returns null and the
|
|
875
876
|
// extension rides the byte-string form, keeping its exact bytes and leaving the defect visible to
|
|
876
877
|
// a validator. Checking overlap and adjacency needs the family's address width, so a family whose
|
|
877
|
-
// AFI this codec does not know declines too,
|
|
878
|
+
// AFI this codec does not know declines too, since there is no way to verify what it would compact.
|
|
878
879
|
// Defense-in-depth, and deliberately explicit: without the width the bound arithmetic below would
|
|
879
880
|
// fault and the caller's catch would produce the same fallback, so this line changes no observable
|
|
880
881
|
// verdict and no vector can isolate it. It stays because a fallback that depends on an exception
|
|
@@ -890,7 +891,7 @@ function _ipChoiceFromDer(ch, afi) {
|
|
|
890
891
|
// No vector can isolate this line today, because the encode path's round-trip self-verify
|
|
891
892
|
// re-parses what it produced and the decode rule rejects it there, yielding the same fallback.
|
|
892
893
|
// It stays explicit because that makes encode's correctness depend on decode continuing to
|
|
893
|
-
// throw
|
|
894
|
+
// throw: soften the decode rule and this path would silently start compacting again.
|
|
894
895
|
if (bg.length === 2 && _ipRangeIsPrefix(blo, bhi)) return null;
|
|
895
896
|
bounds.push({ lo: blo, hi: bhi });
|
|
896
897
|
}
|
|
@@ -981,8 +982,8 @@ function _reasonsBitsToDer(value) {
|
|
|
981
982
|
// ReasonFlags bit 0 is reserved "unused" (RFC 5280 sec. 4.2.1.13; the reason bits are 1..8), so a value
|
|
982
983
|
// that sets it is not a valid ReasonFlags even though the shared 9-bit range check would accept it. Bit 0
|
|
983
984
|
// is a legitimate keyUsage bit (digitalSignature), so this reason-specific reject lives here, not in the
|
|
984
|
-
// shared named-bit helper
|
|
985
|
-
// a semantically invalid X.509 ReasonFlags.
|
|
985
|
+
// shared named-bit helper. A malformed native C509 reasons value fails closed; it never reconstructs
|
|
986
|
+
// into a semantically invalid X.509 ReasonFlags.
|
|
986
987
|
if ((value & 1) !== 0) throw _err("c509/bad-extensions", "a cRLDistributionPoints reasons value must not set the reserved unused ReasonFlags bit 0 (RFC 5280 sec. 4.2.1.13)");
|
|
987
988
|
var c = _namedBitsToContent(value);
|
|
988
989
|
if (c == null) throw _err("c509/bad-extensions", "a cRLDistributionPoints reasons value must be within the 9 defined ReasonFlags bits");
|
|
@@ -999,7 +1000,7 @@ function _serialIntContent(node) {
|
|
|
999
1000
|
// ---- certificatePolicies (draft-20 sec. 3.3 / sec. 8.9 / sec. 8.10; RFC 5280 sec. 4.2.1.4) ----
|
|
1000
1001
|
// A CBOR text -> a UNIVERSAL IA5String TLV, running the same 7-bit + CVE-2009-2408 control-byte guard the
|
|
1001
1002
|
// general-name IA5 arms use (never the bare b.ia5, which permits control bytes). Non-empty (the general-name
|
|
1002
|
-
// IA5 arms reject empty too; a CPSuri parity tightening
|
|
1003
|
+
// IA5 arms reject empty too; this is a CPSuri parity tightening, since RFC 5280 sets no SIZE floor on CPSuri).
|
|
1003
1004
|
function _ia5Universal(text, label) {
|
|
1004
1005
|
if (text.length === 0) throw _err("c509/bad-extensions", label + " must be non-empty");
|
|
1005
1006
|
for (var i = 0; i < text.length; i++) { if (text.charCodeAt(i) > 0x7f) throw _err("c509/bad-extensions", label + " must be a 7-bit IA5String"); }
|
|
@@ -1021,7 +1022,7 @@ function _policyIdFromDer(dotted) {
|
|
|
1021
1022
|
return i !== undefined ? cbor.build.int(BigInt(i)) : _oidCbor(dotted);
|
|
1022
1023
|
}
|
|
1023
1024
|
// (qualifierId CBOR, qualifier-text CBOR) -> one DER PolicyQualifierInfo SEQUENCE { policyQualifierId OID,
|
|
1024
|
-
// qualifier ANY DEFINED BY }. The qualifierId resolves to a sec. 8.10 int
|
|
1025
|
+
// qualifier ANY DEFINED BY }. The qualifierId resolves to a sec. 8.10 int and nothing else: a ~oid / unregistered
|
|
1025
1026
|
// qualifierId has no defined qualifier:text semantics, so it is not compact-representable (fail closed, never
|
|
1026
1027
|
// guess the ANY type). id-qt-cps -> CPSuri IA5String; id-qt-unotice -> UserNotice { explicitText utf8String }.
|
|
1027
1028
|
function _qualifierToDer(qidNode, qtextNode) {
|
|
@@ -1031,11 +1032,11 @@ function _qualifierToDer(qidNode, qtextNode) {
|
|
|
1031
1032
|
if (qtextNode.majorType !== 3) throw _err("c509/bad-extensions", "a policyQualifier value must be a CBOR text string");
|
|
1032
1033
|
var text = cbor.read.textString(qtextNode);
|
|
1033
1034
|
if (qi === 1) return b.sequence([b.oid(oid.byName("cps")), _ia5Universal(text, "a CPSuri")]); // CPSuri ::= IA5String
|
|
1034
|
-
// id-qt-unotice: UserNotice ::= SEQUENCE { explicitText utf8String }
|
|
1035
|
+
// id-qt-unotice: UserNotice ::= SEQUENCE { explicitText utf8String }, noticeRef omitted. Only the SIZE
|
|
1035
1036
|
// (1..200) FLOOR is enforced: an empty explicitText is a degenerate value no encoder can have produced,
|
|
1036
1037
|
// while RFC 5280 sec. 4.2.1.4 directs certificate users to gracefully handle a notice ABOVE 200
|
|
1037
|
-
// characters, and draft-20 sec. 3.3's compact predicate is SIZE-silent
|
|
1038
|
-
// transcodes
|
|
1038
|
+
// characters, and draft-20 sec. 3.3's compact predicate is SIZE-silent. An over-long notice
|
|
1039
|
+
// therefore transcodes and is not refused.
|
|
1039
1040
|
if (text.length === 0) throw _err("c509/bad-extensions", "a UserNotice explicitText must be non-empty (DisplayText SIZE 1..200)");
|
|
1040
1041
|
return b.sequence([b.oid(oid.byName("unotice")), b.sequence([b.utf8(text)])]);
|
|
1041
1042
|
}
|
|
@@ -1052,7 +1053,7 @@ function _qualifierFromDer(pq) {
|
|
|
1052
1053
|
var uri; try { uri = asn1.read.string(q); } catch (_e) { return null; }
|
|
1053
1054
|
return [cbor.build.int(1n), cbor.build.textString(uri)];
|
|
1054
1055
|
}
|
|
1055
|
-
// id-qt-unotice: SEQUENCE { explicitText utf8String }
|
|
1056
|
+
// id-qt-unotice: SEQUENCE { explicitText utf8String } and nothing more (noticeRef absent).
|
|
1056
1057
|
if (q.tagClass !== "universal" || q.tagNumber !== asn1.TAGS.SEQUENCE || !q.children || q.children.length !== 1) return null;
|
|
1057
1058
|
var dt = q.children[0];
|
|
1058
1059
|
if (dt.tagClass !== "universal" || dt.tagNumber !== asn1.TAGS.UTF8_STRING) return null;
|
|
@@ -1062,7 +1063,7 @@ function _qualifierFromDer(pq) {
|
|
|
1062
1063
|
|
|
1063
1064
|
// One CBOR DistributionPointName [ fullName, reasons, cRLIssuer ] -> one DER DistributionPoint SEQUENCE.
|
|
1064
1065
|
// distributionPoint [0] is EXPLICIT (it wraps the DistributionPointName CHOICE); fullName [0], reasons [1],
|
|
1065
|
-
// cRLIssuer [2] are IMPLICIT (RFC 5280 sec. 4.2.1.13)
|
|
1066
|
+
// cRLIssuer [2] are IMPLICIT (RFC 5280 sec. 4.2.1.13); mixing these is the classic byte-exactness trap.
|
|
1066
1067
|
function _dpToDer(dpNode, isNative) {
|
|
1067
1068
|
if (dpNode.majorType !== 4 || !dpNode.children || dpNode.children.length !== 3) throw _err("c509/bad-extensions", "a DistributionPoint must be a CBOR [ fullName, reasons, cRLIssuer ] array");
|
|
1068
1069
|
var fullName = dpNode.children[0], reasons = dpNode.children[1], crlIssuer = dpNode.children[2];
|
|
@@ -1200,7 +1201,7 @@ function _extValueToDer(name, node, isNative) {
|
|
|
1200
1201
|
if (node.majorType !== 4 || !node.children || node.children.length < 1) throw _err("c509/bad-extensions", "a " + name + " value must be a CBOR array of DistributionPoints or a bare URI text (sec. 3.3)");
|
|
1201
1202
|
return b.sequence(node.children.map(function (dp) { return _dpToDer(dp, isNative); }));
|
|
1202
1203
|
// IPAddrBlocks (and its RFC 8360 v2 twin): a FLAT array of (AFI, SAFI, choice) triples --
|
|
1203
|
-
// IPAddressFamily is a parenthesized CDDL group, so it splices
|
|
1204
|
+
// IPAddressFamily is a parenthesized CDDL group, so it splices and does not nest.
|
|
1204
1205
|
case "ipAddrBlocks":
|
|
1205
1206
|
case "ipAddrBlocksV2": {
|
|
1206
1207
|
if (node.majorType !== 4 || !node.children) throw _err("c509/bad-extensions", "an IPAddrBlocks value must be a CBOR array");
|
|
@@ -1248,7 +1249,7 @@ function _extValueToDer(name, node, isNative) {
|
|
|
1248
1249
|
}
|
|
1249
1250
|
// The deltas are `uint` in the draft's CDDL precisely because RFC 3779 sec. 3.2.3.4 sorts AS
|
|
1250
1251
|
// ids by increasing value: a negative delta would walk the chain backwards. That section also
|
|
1251
|
-
// forbids a pair overlapping and requires a contiguous series to be one range
|
|
1252
|
+
// forbids a pair overlapping and requires a contiguous series to be one range: the same
|
|
1252
1253
|
// three rules the encode side applies, tracked here across members through `asPrevHigh`.
|
|
1253
1254
|
// A negative delta needs no separate test: on the first entry it drives the absolute below
|
|
1254
1255
|
// zero and the range guard refuses it, and on any later entry it lands at or below the
|
|
@@ -1284,8 +1285,8 @@ function _extValueToDer(name, node, isNative) {
|
|
|
1284
1285
|
var quals = cpKids[cpi + 1];
|
|
1285
1286
|
if (quals.majorType !== 4 || !quals.children) throw _err("c509/bad-extensions", "a certificatePolicies qualifiers slot must be a CBOR array");
|
|
1286
1287
|
// A policy OID MUST NOT appear more than once (RFC 5280 sec. 4.2.1.4); the toolkit's own DER decoder
|
|
1287
|
-
// rejects a duplicate (schema-pkix certExtensionDecoders), so reconstruct fails closed
|
|
1288
|
-
//
|
|
1288
|
+
// rejects a duplicate (schema-pkix certExtensionDecoders), so reconstruct fails closed here and
|
|
1289
|
+
// never produces a certificatePolicies extension that decoder would reject.
|
|
1289
1290
|
var policyOid = _policyIdToDerOid(cpKids[cpi]);
|
|
1290
1291
|
if (seenPolicy[policyOid]) throw _err("c509/bad-extensions", "a certificatePolicies policy OID must not appear more than once (RFC 5280 sec. 4.2.1.4)");
|
|
1291
1292
|
seenPolicy[policyOid] = true;
|
|
@@ -1474,7 +1475,7 @@ function _extValueFromDer(name, der) {
|
|
|
1474
1475
|
// RFC 3779 sec. 3.2.3.4 fixes the canonical form the same way sec. 2.2.3.6 does for addresses:
|
|
1475
1476
|
// sorted by increasing value, no pair overlapping, and any contiguous series already merged
|
|
1476
1477
|
// into one range. `asPrevHigh` carries the previous entry's upper bound so all three hold
|
|
1477
|
-
// ACROSS members
|
|
1478
|
+
// ACROSS members. Checking only within a range would let a descending or adjacent pair
|
|
1478
1479
|
// through. A list that is not canonical is left uncompacted with its bytes intact.
|
|
1479
1480
|
var asOut = [], asPrevOut = null, asPrevHigh = null;
|
|
1480
1481
|
for (var aoi = 0; aoi < inner.children.length; aoi++) {
|
|
@@ -1565,39 +1566,40 @@ function _extValueFromDer(name, der) {
|
|
|
1565
1566
|
// ---- subjectDirectoryAttributes compact value codec (draft-20 sec. 3.3 / 8.6 / 8.8, RFC 5280 sec. 4.2.1.8) ----
|
|
1566
1567
|
// SubjectDirectoryAttributes ::= SEQUENCE SIZE(1..MAX) OF Attribute ::= SEQUENCE { type, values SET OF value }.
|
|
1567
1568
|
// The compact form is a flat CBOR array [ type1, values1, ... ] where each values slot is ITSELF a non-empty
|
|
1568
|
-
// CBOR array
|
|
1569
|
+
// CBOR array, since an Attribute's SET may hold more than one value where a Name's RDN holds one. type is a
|
|
1569
1570
|
// sec. 8.6 registry int (the sign selects the string type) with text values, or an unwrapped ~oid with raw DER
|
|
1570
1571
|
// value bytes (an unregistered type stays compact via its own ~oid form, never dropping the whole extension).
|
|
1571
1572
|
// The extension-level _tryCompactExtValue round-trip guard is the byte-exactness net.
|
|
1572
1573
|
|
|
1573
|
-
// A ~oid-form attributeValue must be exactly
|
|
1574
|
+
// A ~oid-form attributeValue must be exactly one non-empty, well-formed DER AttributeValue TLV. An empty byte
|
|
1574
1575
|
// string would build a SET OF SIZE 0 (RFC 5280 sec. 4.2.1.8 requires at least one value); a byte string carrying
|
|
1575
1576
|
// more than one TLV would fan one declared value into several SET members (draft-20 sec. 3.3: one `bytes` is one
|
|
1576
1577
|
// AttributeValue). There is no downstream DER decoder to catch either (SDA ships c509-only), so the reconstruct
|
|
1577
|
-
// arm self-enforces the single-TLV shape
|
|
1578
|
+
// arm self-enforces the single-TLV shape, so a degenerate or invalid Attribute is never spliced.
|
|
1578
1579
|
// ---- the shared strict-DER gate for a raw-spliced ANY value --------------------------------------
|
|
1579
1580
|
// Several reconstruct sites splice CALLER/ATTACKER-supplied bytes into the rebuilt certificate verbatim (an
|
|
1580
1581
|
// AlgorithmIdentifier's `parameters`, a generic otherName's `value [0] EXPLICIT ANY`, a subjectDirectoryAttributes
|
|
1581
|
-
// `~oid`-form AttributeValue). asn1.decode validates TLV
|
|
1582
|
-
// non-minimal lengths, and enforces the DER primitive/constructed rules, but it does
|
|
1582
|
+
// `~oid`-form AttributeValue). asn1.decode validates TLV framing alone. It rejects truncation, trailing bytes and
|
|
1583
|
+
// non-minimal lengths, and enforces the DER primitive/constructed rules, but it does not check per-type content:
|
|
1583
1584
|
// it frames an empty INTEGER, the reserved end-of-contents tag 0, and an out-of-alphabet string quite happily.
|
|
1584
|
-
// Splicing those produces a certificate an independent decoder refuses to load, and one this toolkit's
|
|
1585
|
-
// reject
|
|
1585
|
+
// Splicing those produces a certificate an independent decoder refuses to load, and one this toolkit's own readers
|
|
1586
|
+
// reject. Every such site therefore routes through the one gate below instead of a bare asn1.decode.
|
|
1586
1587
|
|
|
1587
1588
|
// Every universal PRIMITIVE tag whose content this toolkit can strictly validate, mapped to its reader. A
|
|
1588
|
-
// universal primitive
|
|
1589
|
-
// spliced unchecked. The documented residual: a value whose type this toolkit cannot yet strictly
|
|
1590
|
-
// not compact-representable
|
|
1591
|
-
// content reader in this toolkit), and a
|
|
1589
|
+
// universal primitive outside this map has no strict content validator here, so the gate REJECTS it and
|
|
1590
|
+
// nothing is spliced unchecked. The documented residual: a value whose type this toolkit cannot yet strictly
|
|
1591
|
+
// validate is not compact-representable. The ASN.1 types in that residual are `VideotexString` /
|
|
1592
|
+
// `GraphicString` / `GeneralString` / `ObjectDescriptor` / `REAL` (no content reader in this toolkit), and a
|
|
1593
|
+
// fractional-seconds GeneralizedTime (the X.690 sec. 11.7 relaxation is deliberately scoped to the codec
|
|
1592
1594
|
// and RFC 3161 timestamping, and must not creep into a third consumer). On the ENCODE path such a value simply
|
|
1593
1595
|
// degrades to the byte-exact ~oid + byte-string form, losing nothing; on the DECODE path it is a fail-closed
|
|
1594
|
-
// verdict
|
|
1596
|
+
// verdict.
|
|
1595
1597
|
var _ANY_VALUE_READERS = (function () {
|
|
1596
1598
|
var m = {}, R = asn1.read, T = asn1.TAGS;
|
|
1597
1599
|
m[T.BOOLEAN] = R.boolean; m[T.INTEGER] = R.integer; m[T.ENUMERATED] = R.enumerated;
|
|
1598
1600
|
m[T.BIT_STRING] = R.bitString; m[T.OCTET_STRING] = R.octetString; m[T.NULL] = R.nullValue;
|
|
1599
1601
|
m[T.OBJECT_IDENTIFIER] = R.oid; m[T.UTC_TIME] = R.time; m[T.GENERALIZED_TIME] = R.time;
|
|
1600
|
-
// NumericString reads through its
|
|
1602
|
+
// NumericString reads through its own reader: it is not a DirectoryString type, and routing it through
|
|
1601
1603
|
// read.string would fold it into the RFC 5280 sec. 7.1 name-comparison identity class (see asn1-der.js).
|
|
1602
1604
|
m[T.NUMERIC_STRING] = R.numericString;
|
|
1603
1605
|
[T.UTF8_STRING, T.PRINTABLE_STRING, T.IA5_STRING, T.TELETEX_STRING, T.VISIBLE_STRING, T.BMP_STRING, T.UNIVERSAL_STRING].forEach(function (t) { m[t] = R.string; });
|
|
@@ -1605,11 +1607,11 @@ var _ANY_VALUE_READERS = (function () {
|
|
|
1605
1607
|
})();
|
|
1606
1608
|
|
|
1607
1609
|
// A universal SET's required member order depends on a type the ANY does not carry: X.690 sec. 11.6 orders a
|
|
1608
|
-
// SET OF by the members' full encodings, while a structured SET is ordered by TAG (X.680 sec. 8.6)
|
|
1609
|
-
// differ whenever the constructed bit does (a SEQUENCE member, tag 16, sorts
|
|
1610
|
-
// tag but
|
|
1610
|
+
// SET OF by the members' full encodings, while a structured SET is ordered by TAG (X.680 sec. 8.6), and the two
|
|
1611
|
+
// differ whenever the constructed bit does (a SEQUENCE member, tag 16, sorts before a PrintableString, tag 19, by
|
|
1612
|
+
// tag but after it by octets). A structured SET cannot repeat a tag, so a repeated tag proves SET OF and the
|
|
1611
1613
|
// octet rule binds; with all-distinct tags either reading is possible, so accept a value that satisfies EITHER
|
|
1612
|
-
// (rejecting only what is non-canonical under
|
|
1614
|
+
// (rejecting only what is non-canonical under both readings: sound in both directions, never a guess).
|
|
1613
1615
|
var _TAG_CLASS_RANK = { universal: 0, application: 1, context: 2, private: 3 };
|
|
1614
1616
|
function _setOrderOk(kids) {
|
|
1615
1617
|
var i, dup = false, seen = {};
|
|
@@ -1622,7 +1624,7 @@ function _setOrderOk(kids) {
|
|
|
1622
1624
|
for (i = 1; i < kids.length; i++) {
|
|
1623
1625
|
if (Buffer.compare(kids[i - 1].bytes, kids[i].bytes) > 0) octetAsc = false;
|
|
1624
1626
|
// Tag order ranks by CLASS first (universal < application < context < private, X.680 sec. 8.6), then by tag
|
|
1625
|
-
// number
|
|
1627
|
+
// number. Compare the class's NUMBER: the names do not sort in class order.
|
|
1626
1628
|
var pc = _TAG_CLASS_RANK[kids[i - 1].tagClass], cc = _TAG_CLASS_RANK[kids[i].tagClass];
|
|
1627
1629
|
if (pc !== cc ? pc > cc : kids[i - 1].tagNumber > kids[i].tagNumber) tagAsc = false;
|
|
1628
1630
|
}
|
|
@@ -1632,7 +1634,7 @@ function _setOrderOk(kids) {
|
|
|
1632
1634
|
// Strict-validate a decoded DER element at ANY depth, in the caller's error domain. Rejects the reserved EOC tag
|
|
1633
1635
|
// 0; runs a universal primitive through its strict content reader (or rejects a type with none); recurses into a
|
|
1634
1636
|
// constructed element's children; and holds a universal SET to a canonical order. Only SEQUENCE and SET are
|
|
1635
|
-
// accepted as universal CONSTRUCTED types
|
|
1637
|
+
// accepted as universal CONSTRUCTED types. An EXTERNAL / EMBEDDED PDV / CHARACTER STRING has mandatory
|
|
1636
1638
|
// components this gate cannot verify (the degenerate empty form is not a valid encoding of any of them), so it is
|
|
1637
1639
|
// refused for the same reason an unvalidatable primitive is. A NON-universal element (a legitimately context- or
|
|
1638
1640
|
// application-tagged ANY) passes on its framing, but its constructed children are still walked.
|
|
@@ -1650,14 +1652,14 @@ function _strictDerElement(node, code, label) {
|
|
|
1650
1652
|
return;
|
|
1651
1653
|
}
|
|
1652
1654
|
if (node.tagClass === "universal") {
|
|
1653
|
-
// Validate-or-reject: a universal primitive with no strict content reader is
|
|
1655
|
+
// Validate-or-reject: a universal primitive with no strict content reader is never accepted on framing alone
|
|
1654
1656
|
// (asn1.decode frames a malformed NumericString "12 01 40" happily), so the map is exhaustive by refusal.
|
|
1655
1657
|
var reader = _ANY_VALUE_READERS[node.tagNumber];
|
|
1656
1658
|
if (!reader) throw _err(code, label + " of universal type " + node.tagNumber + " has no strict DER content validator here");
|
|
1657
1659
|
try { reader(node); } catch (e) { throw _err(code, label + " is not a valid DER element for its type", e); }
|
|
1658
1660
|
}
|
|
1659
1661
|
}
|
|
1660
|
-
// Raw ANY bytes about to be spliced verbatim must be exactly
|
|
1662
|
+
// Raw ANY bytes about to be spliced verbatim must be exactly one non-empty, well-formed AND strictly-valid DER
|
|
1661
1663
|
// element: framing + no-trailing-data via asn1.decode, then content / structure / SET order via
|
|
1662
1664
|
// _strictDerElement, both reported in the CALLER's error domain. Returns the bytes so a call site can wrap a
|
|
1663
1665
|
// splice inline. Used by every reconstruct site that emits caller-supplied ANY bytes verbatim.
|
|
@@ -1669,8 +1671,8 @@ function _requireStrictDerTlv(content, code, label) {
|
|
|
1669
1671
|
return content;
|
|
1670
1672
|
}
|
|
1671
1673
|
|
|
1672
|
-
// asn1.build.set SORTS its members, so handing it a non-canonically-ordered values list would
|
|
1673
|
-
// the declared order
|
|
1674
|
+
// asn1.build.set SORTS its members, so handing it a non-canonically-ordered values list would silently rewrite
|
|
1675
|
+
// the declared order, and many distinct C509 encodings would reconstruct one and the same certificate. Require the
|
|
1674
1676
|
// declared order to already BE canonical, so the compact form maps one-to-one onto the DER it rebuilds.
|
|
1675
1677
|
function _derSetInDeclaredOrder(vals) {
|
|
1676
1678
|
for (var i = 1; i < vals.length; i++) {
|
|
@@ -1692,12 +1694,12 @@ function _sdaToDer(node, isNative) {
|
|
|
1692
1694
|
var vals = [];
|
|
1693
1695
|
if (typeNode.majorType === 0 || typeNode.majorType === 1) { // int form: a sec. 8.6 registry alias, text values
|
|
1694
1696
|
var ti = Number(cbor.read.int(typeNode));
|
|
1695
|
-
// draft sec. 3.1.4 applies to
|
|
1697
|
+
// draft sec. 3.1.4 applies to every int in a natively signed certificate, not just the top-level Name's.
|
|
1696
1698
|
if (isNative && ti < 0) throw _err("c509/bad-extensions", "a natively signed C509 subjectDirectoryAttributes attribute type integer must be non-negative (draft sec. 3.1.4), got " + ti);
|
|
1697
1699
|
var tname = ATTR_BY_INT[Math.abs(ti)];
|
|
1698
1700
|
if (tname === undefined) throw _err("c509/bad-extensions", "a subjectDirectoryAttributes attribute type int " + ti + " has no C509 sec. 8.6 registry row");
|
|
1699
1701
|
// countryName / serialNumber carry a CHARACTER restriction (draft sec. 3.1.4 "SHALL contain only
|
|
1700
|
-
// characters from the 74-character ASCII subset permitted by PrintableString"),
|
|
1702
|
+
// characters from the 74-character ASCII subset permitted by PrintableString"), and not a sign override --
|
|
1701
1703
|
// _reconAttrValue asserts the charset and honours the declared string type. Requiring the negative sign
|
|
1702
1704
|
// here would also make these attributes unrepresentable in a NATIVE certificate, whose ints SHALL all be
|
|
1703
1705
|
// non-negative (same sec.), so the rule is the charset, not the sign.
|
|
@@ -1728,12 +1730,12 @@ function _sdaToDer(node, isNative) {
|
|
|
1728
1730
|
|
|
1729
1731
|
// One DER Attribute's SET members -> the int-form { sign, values:[CBOR text] }, or null when the int form is
|
|
1730
1732
|
// not confidently byte-exact (an unregistered type, a non-utf8/printable value, or a SET mixing utf8String and
|
|
1731
|
-
// printableString
|
|
1733
|
+
// printableString, which one sign cannot express). A null routes that one attribute to the always-byte-exact
|
|
1732
1734
|
// ~oid + raw-bytes form (a still-compact sec. 3.3 SDA form); the extension round-trip guard is the ultimate net.
|
|
1733
1735
|
function _sdaTryIntForm(tint, name, valueNodes) {
|
|
1734
1736
|
if (tint === undefined) return null;
|
|
1735
1737
|
var i;
|
|
1736
|
-
// An IA5String-only attribute (emailAddress) rides its non-negative int with IA5String values
|
|
1738
|
+
// An IA5String-only attribute (emailAddress) rides its non-negative int with IA5String values. Its type,
|
|
1737
1739
|
// not a sign, fixes the string type, so it is matched separately from the utf8/printable sign convention.
|
|
1738
1740
|
if (name === "emailAddress") {
|
|
1739
1741
|
var mails = [];
|
|
@@ -1783,10 +1785,10 @@ function _sdaFromDer(node) {
|
|
|
1783
1785
|
|
|
1784
1786
|
// Encode-side guard: emit the compact value only when it decodes back to the EXACT DER extnValue, so a
|
|
1785
1787
|
// non-canonical or unrepresentable value can never produce a lossy compact form (it falls back to ~oid).
|
|
1786
|
-
//
|
|
1787
|
-
// builder rejects (an AKI authorityCertIssuer [1] with no members), or any future codec asymmetry
|
|
1788
|
-
//
|
|
1789
|
-
// mismatch
|
|
1788
|
+
// Some values _extValueFromDer produced cannot be reconstructed by _extValueToDer: an empty GeneralNames the
|
|
1789
|
+
// DER builder rejects (an AKI authorityCertIssuer [1] with no members), or any future codec asymmetry. None of
|
|
1790
|
+
// them is compact-representable, and the round-trip reconstruction throwing is treated identically to a byte
|
|
1791
|
+
// mismatch. encode() falls back to the ~oid form and does not raise on a certificate x509.parse
|
|
1790
1792
|
// accepts. The final encode() self-verify still gates byte-exactness end to end.
|
|
1791
1793
|
function _tryCompactExtValue(name, der) {
|
|
1792
1794
|
var compact = _extValueFromDer(name, der);
|
|
@@ -1812,7 +1814,7 @@ function _extensions(node, isNative) {
|
|
|
1812
1814
|
var out = [];
|
|
1813
1815
|
var kids = node.children || [];
|
|
1814
1816
|
// Each extension is an (extensionID, extensionValue) pair; an odd-length array is a dangling
|
|
1815
|
-
// extension identifier with no value
|
|
1817
|
+
// extension identifier with no value, so the whole extensions array is rejected.
|
|
1816
1818
|
if (kids.length % 2 !== 0) throw _err("c509/bad-extensions", "a C509 extensions array must be id/value pairs (dangling extension identifier)");
|
|
1817
1819
|
// sec. 3.1.10: "If the CBOR array contains exactly two ints and the absolute value of the first int is
|
|
1818
1820
|
// 2 (corresponding to keyUsage), the CBOR array is omitted and the 'extensions' field is encoded as a
|
|
@@ -1831,10 +1833,10 @@ function _extensions(node, isNative) {
|
|
|
1831
1833
|
if (name === undefined) throw _err("c509/bad-extensions", "extension type integer " + ei + " has no C509 registry row");
|
|
1832
1834
|
extOid = oid.byName(name); critical = ei < 0;
|
|
1833
1835
|
// A registered extension with a compact value form (sec. 3.3) decodes its specific CBOR value to the
|
|
1834
|
-
// DER extnValue inner content. A registered extension
|
|
1836
|
+
// DER extnValue inner content. A registered extension without one (subjectAltName, until the general-
|
|
1835
1837
|
// name value codec lands) can only reconstruct a byte-string value (the raw DER extnValue); a
|
|
1836
|
-
// non-byte-string value there is an unsupported compact form and MUST fail closed
|
|
1837
|
-
// is
|
|
1838
|
+
// non-byte-string value there is an unsupported compact form and MUST fail closed. A text/array value
|
|
1839
|
+
// is not raw DER, and copying its bytes would reconstruct a structurally invalid extension.
|
|
1838
1840
|
if (EXT_COMPACT[name]) {
|
|
1839
1841
|
valContent = _extValueToDer(name, valNode, isNative);
|
|
1840
1842
|
} else if (valNode.majorType === 2) {
|
|
@@ -1845,7 +1847,7 @@ function _extensions(node, isNative) {
|
|
|
1845
1847
|
} else {
|
|
1846
1848
|
var r = _oidName(idNode, "c509/bad-extensions", "an extension id");
|
|
1847
1849
|
name = r.name; extOid = r.oid;
|
|
1848
|
-
// ~oid extension (sec. 3.1.10): the extnValue is a byte string
|
|
1850
|
+
// ~oid extension (sec. 3.1.10): the extnValue is a byte string, either BARE (non-critical) or wrapped in a
|
|
1849
1851
|
// single-element array (critical). Validate the shape so a malformed value fails closed at decode.
|
|
1850
1852
|
critical = valNode.majorType === 4;
|
|
1851
1853
|
if (critical) {
|
|
@@ -1866,7 +1868,7 @@ function _extensions(node, isNative) {
|
|
|
1866
1868
|
// ---- type-3 DER reconstruction (byte-exact inversion; draft-20 sec. 3 / sec. 5) ----
|
|
1867
1869
|
// Type 3 is an INVERTIBLE transform: the reconstruction reproduces the ORIGINAL DER Certificate
|
|
1868
1870
|
// byte-for-byte (so the original signature verifies). A field outside the covered set fails closed
|
|
1869
|
-
// (c509/non-invertible)
|
|
1871
|
+
// (c509/non-invertible); it never emits a partial or best-effort DER.
|
|
1870
1872
|
|
|
1871
1873
|
// A C509 tag-48 MAC (RFC 9542) reconstructs to the commonName EUI-64 string: a 6-byte value is a 48-bit
|
|
1872
1874
|
// MAC expanded to the EUI-64 HH-HH-HH-FF-FE-HH-HH-HH by inserting FF-FE; an 8-byte value is the EUI-64.
|
|
@@ -1894,7 +1896,7 @@ function _reconAttrValue(rdn) {
|
|
|
1894
1896
|
if (rdn.type === "emailAddress") return b.ia5(s);
|
|
1895
1897
|
// serialNumber / countryName carry a CHARACTER restriction, not a string-type override: draft sec. 3.1.4
|
|
1896
1898
|
// "SHALL contain only characters from the 74-character ASCII subset permitted by PrintableString". Enforce
|
|
1897
|
-
// that on the CHARACTERS and still honour the sign for the string type
|
|
1899
|
+
// that on the CHARACTERS and still honour the sign for the string type. Coercing them to PrintableString
|
|
1898
1900
|
// regardless of sign would make the +N and -N encodings of one value reconstruct IDENTICAL DER, so a single
|
|
1899
1901
|
// X.509 signature would cover two distinct C509 encodings (a malleability window in the type-3 transform).
|
|
1900
1902
|
// b.printable IS the PrintableString charset authority -- run it for the assert even when the sign selects
|
|
@@ -1925,8 +1927,8 @@ function _reconSpki(spkAlg, keyBytes, rsaKey) {
|
|
|
1925
1927
|
if (!fieldSize) throw _err("c509/non-invertible", "unsupported EC curve " + spkAlg.curve);
|
|
1926
1928
|
if (!keyBytes || keyBytes.length === 0) throw _err("c509/non-invertible", "the EC subjectPublicKey byte string is empty");
|
|
1927
1929
|
var head = keyBytes[0], point;
|
|
1928
|
-
// The point length must match the curve field size for its encoding
|
|
1929
|
-
// 1 + 2*fieldSize, a compressed 0x02/0x03/0xFE/0xFD point is 1 + fieldSize
|
|
1930
|
+
// The point length must match the curve field size for its encoding: an uncompressed 0x04 point is
|
|
1931
|
+
// 1 + 2*fieldSize, and a compressed 0x02/0x03/0xFE/0xFD point is 1 + fieldSize. A truncated / padded
|
|
1930
1932
|
// point cannot be re-emitted as a valid (or byte-exact) SubjectPublicKeyInfo.
|
|
1931
1933
|
if (head === 0x04) {
|
|
1932
1934
|
if (keyBytes.length !== 1 + 2 * fieldSize) throw _err("c509/non-invertible", "uncompressed EC point length " + keyBytes.length + " does not match " + spkAlg.curve);
|
|
@@ -1984,7 +1986,7 @@ function _reconExtensions(exts) {
|
|
|
1984
1986
|
function _reconAlgId(alg) {
|
|
1985
1987
|
var fields = [b.oid(alg.oid)];
|
|
1986
1988
|
if (alg.parameters && alg.parameters.length) {
|
|
1987
|
-
// AlgorithmIdentifier.parameters is ANY, spliced verbatim
|
|
1989
|
+
// AlgorithmIdentifier.parameters is ANY, spliced verbatim, so it gets the same strict gate as every other
|
|
1988
1990
|
// raw-ANY splice, not just a framing check. Framing alone admits an empty INTEGER / reserved tag 0 / a
|
|
1989
1991
|
// non-minimal INTEGER, each of which reconstructs an AlgorithmIdentifier that an independent X.509 decoder
|
|
1990
1992
|
// refuses to load and that this toolkit's own readers reject.
|
|
@@ -2014,18 +2016,18 @@ function _reconstructDer(r, sigNode) {
|
|
|
2014
2016
|
_reconSpki(r.subjectPublicKeyAlgorithm, r.subjectPublicKey, r.rsaPublicKey),
|
|
2015
2017
|
];
|
|
2016
2018
|
// RFC 5280 sec. 4.1: the [3] extensions field is OPTIONAL and, when present, SHALL contain at least one
|
|
2017
|
-
// extension
|
|
2019
|
+
// extension, so an empty C509 extensions array reconstructs to an OMITTED field, not an empty SEQUENCE.
|
|
2018
2020
|
if (r.extensions.length) tbsFields.push(_reconExtensions(r.extensions));
|
|
2019
2021
|
var tbs = b.sequence(tbsFields);
|
|
2020
2022
|
// The signature is re-wrapped as a DER ECDSA-Sig-Value from the fixed-width r||s, so only an ECDSA
|
|
2021
2023
|
// signature algorithm is in the type-3 reconstruction covered set (an RSA/EdDSA signature is raw bytes,
|
|
2022
|
-
// not r||s
|
|
2024
|
+
// not r||s, so it is rejected). A wrong-length r||s surfaces the caller's typed code.
|
|
2023
2025
|
if (!/^ecdsa/i.test(r.signatureAlgorithm.name || "")) {
|
|
2024
2026
|
throw _err("c509/non-invertible", "type-3 signature reconstruction covers only ECDSA; got " + r.signatureAlgorithm.name);
|
|
2025
2027
|
}
|
|
2026
|
-
// The fixed-width r||s must split at a real curve field width
|
|
2028
|
+
// The fixed-width r||s must split at a real curve field width: P-256/384/521 = 64/96/132 bytes
|
|
2027
2029
|
// (RFC 9053 sec. 2.1). A width that is not 2x a supported field size is not a valid ECDSA signature and
|
|
2028
|
-
// cannot be re-wrapped byte-exact
|
|
2030
|
+
// cannot be re-wrapped byte-exact, so it surfaces the caller's typed code.
|
|
2029
2031
|
var coordLen = r.signatureValue.length / 2;
|
|
2030
2032
|
if (coordLen !== 32 && coordLen !== 48 && coordLen !== 66) {
|
|
2031
2033
|
throw _err("c509/bad-signature", "the type-3 ECDSA signature width " + r.signatureValue.length + " is not a valid fixed-width r||s (expected 64/96/132 for P-256/384/521)");
|
|
@@ -2047,7 +2049,7 @@ function _reconstructDer(r, sigNode) {
|
|
|
2047
2049
|
* Returns the decoded fields (c509CertificateType 2 native or 3 re-encoded); a malformed shape throws a
|
|
2048
2050
|
* typed C509Error carrying the inner cbor/asn1 fault as .cause. It decodes CBOR, not DER, so it is
|
|
2049
2051
|
* reached by an explicit call and is not auto-routed by pki.schema.parse. The type-2 signedData and the
|
|
2050
|
-
* raw signature are surfaced
|
|
2052
|
+
* raw signature are surfaced as-is (a native verifier hashes them without re-serialization).
|
|
2051
2053
|
*
|
|
2052
2054
|
* @example
|
|
2053
2055
|
* // the RFC 7925 profiled certificate from draft-ietf-cose-cbor-encoded-cert Appendix A.1 (type 3)
|
|
@@ -2304,13 +2306,13 @@ function _encodeC509Array(r) {
|
|
|
2304
2306
|
* @spec draft-ietf-cose-cbor-encoded-cert, RFC 8949, RFC 9090
|
|
2305
2307
|
* @related pki.schema.c509.parse
|
|
2306
2308
|
*
|
|
2307
|
-
* Encode a C509 certificate to its deterministic-CBOR bytes
|
|
2309
|
+
* Encode a C509 certificate to its deterministic-CBOR bytes. This is the producing-side inverse of
|
|
2308
2310
|
* `pki.schema.c509.parse`. `input` is either a DER X.509 v3 certificate (a Buffer or PEM string), which is
|
|
2309
2311
|
* forward-transformed to a **type-3** C509 (a compact CBOR re-encoding whose signature is copied from the
|
|
2310
2312
|
* source and re-expressed as a fixed-width r||s, so `parse(encode(der)).reconstructedDer` reproduces the
|
|
2311
2313
|
* original DER byte for byte and the original signature still verifies), or a `pki.schema.c509.parse`
|
|
2312
2314
|
* result object, which is re-emitted to its native deterministic-CBOR array. The emission is canonical
|
|
2313
|
-
* deterministic CBOR (RFC 8949 sec. 4.2)
|
|
2315
|
+
* deterministic CBOR (RFC 8949 sec. 4.2): shortest-form heads, definite lengths, sorted map keys, and the
|
|
2314
2316
|
* registry integer shorthand for every registered algorithm / attribute / extension. It is signing-free (a
|
|
2315
2317
|
* byte transform, like `pki.ct.encodeSctList`); a shape outside the covered set throws a typed `C509Error`.
|
|
2316
2318
|
*
|
|
@@ -2318,7 +2320,7 @@ function _encodeC509Array(r) {
|
|
|
2318
2320
|
* carry. It is resolved authoritatively (never a magnitude guess, and matching issuer/subject Names are not
|
|
2319
2321
|
* taken as proof of self-signing): from `opts.issuerCurve`, or from the RFC 5480 standard digest<->curve
|
|
2320
2322
|
* pairing the signature algorithm implies. A certificate signed with a non-standard digest/curve pairing
|
|
2321
|
-
* (its r/s wider than the digest's standard curve) fails closed
|
|
2323
|
+
* (its r/s wider than the digest's standard curve) fails closed; supply the issuer curve via
|
|
2322
2324
|
* `opts.issuerCurve`.
|
|
2323
2325
|
*
|
|
2324
2326
|
* @opts
|