@blamejs/pki 0.6.13 → 0.6.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -1
- package/README.md +1 -1
- package/index.js +1 -1
- package/lib/acme.js +59 -815
- package/lib/asn1-der.js +7 -245
- package/lib/attrcert-sign.js +12 -260
- package/lib/attrcert-verify.js +1 -7
- package/lib/byte-reader.js +0 -8
- package/lib/byte-writer.js +0 -11
- package/lib/cbor-det.js +0 -54
- package/lib/cmc-build.js +15 -357
- package/lib/cmc-verify.js +6 -239
- package/lib/cmp-build.js +41 -317
- package/lib/cmp-session.js +73 -760
- package/lib/cmp-verify.js +32 -488
- package/lib/cms-compress.js +3 -32
- package/lib/cms-decrypt.js +29 -294
- package/lib/cms-encrypt.js +14 -234
- package/lib/cms-sign.js +29 -324
- package/lib/cms-verify.js +9 -500
- package/lib/composite-kem.js +2 -102
- package/lib/composite-sig.js +1 -77
- package/lib/constants.js +0 -249
- package/lib/crl-sign.js +9 -237
- package/lib/crl-verify.js +1 -10
- package/lib/crmf-sign.js +19 -198
- package/lib/crmf-verify.js +1 -12
- package/lib/csr-sign.js +3 -34
- package/lib/csr-verify.js +1 -12
- package/lib/ct.js +28 -253
- package/lib/edwards-point.js +10 -44
- package/lib/est.js +28 -764
- package/lib/framework-error.js +4 -182
- package/lib/guard-all.js +1 -58
- package/lib/guard-async.js +1 -36
- package/lib/guard-bytes.js +6 -650
- package/lib/guard-compress.js +5 -102
- package/lib/guard-crypto.js +1 -40
- package/lib/guard-der.js +2 -93
- package/lib/guard-encoding.js +2 -60
- package/lib/guard-header.js +1 -36
- package/lib/guard-identifier.js +7 -531
- package/lib/guard-intrinsic.js +1 -150
- package/lib/guard-json.js +10 -73
- package/lib/guard-limits.js +1 -77
- package/lib/guard-list.js +1 -49
- package/lib/guard-name.js +3 -209
- package/lib/guard-parsed.js +7 -335
- package/lib/guard-range.js +1 -71
- package/lib/guard-secret.js +1 -102
- package/lib/guard-text.js +1 -64
- package/lib/guard-time.js +1 -97
- package/lib/hpke.js +3 -105
- package/lib/http-digest.js +14 -142
- package/lib/http-retry-after.js +1 -47
- package/lib/http-transport.js +21 -131
- package/lib/inspect.js +30 -284
- package/lib/ip-utils.js +6 -47
- package/lib/jose.js +7 -142
- package/lib/key.js +2 -67
- package/lib/lint.js +9 -194
- package/lib/merkle.js +1 -27
- package/lib/mime.js +9 -82
- package/lib/ocsp-request-verify.js +1 -14
- package/lib/ocsp-verify.js +1 -82
- package/lib/ocsp.js +11 -169
- package/lib/oid.js +5 -337
- package/lib/path-validate.js +103 -1492
- package/lib/pbes2.js +2 -89
- package/lib/pkcs12-build.js +31 -379
- package/lib/pki-build.js +64 -290
- package/lib/rc2.js +2 -18
- package/lib/rfc3339.js +4 -27
- package/lib/scep.js +59 -233
- package/lib/schema-all.js +0 -122
- package/lib/schema-attrcert.js +4 -146
- package/lib/schema-c509.js +153 -802
- package/lib/schema-cmc.js +5 -220
- package/lib/schema-cmp.js +2 -223
- package/lib/schema-cms.js +13 -427
- package/lib/schema-crl.js +5 -68
- package/lib/schema-crmf.js +6 -167
- package/lib/schema-csr.js +1 -54
- package/lib/schema-csrattrs.js +2 -87
- package/lib/schema-engine.js +0 -152
- package/lib/schema-ocsp.js +0 -117
- package/lib/schema-pkcs12.js +2 -93
- package/lib/schema-pkcs8.js +3 -35
- package/lib/schema-pkix.js +24 -655
- package/lib/schema-smime.js +0 -54
- package/lib/schema-tsp.js +3 -99
- package/lib/schema-x509.js +4 -63
- package/lib/shbs.js +11 -95
- package/lib/sign-scheme.js +2 -97
- package/lib/sigstore.js +9 -169
- package/lib/sleep.js +2 -8
- package/lib/smime.js +15 -408
- package/lib/tls-cert-compress.js +0 -62
- package/lib/trust.js +19 -185
- package/lib/tsp-sign.js +16 -270
- package/lib/validator-all.js +1 -30
- package/lib/validator-attcert.js +2 -46
- package/lib/validator-cose.js +2 -153
- package/lib/validator-keydesc.js +1 -37
- package/lib/validator-sig.js +1 -46
- package/lib/validator-tls.js +1 -88
- package/lib/validator-tpm.js +19 -122
- package/lib/verify-seam.js +1 -24
- package/lib/webauthn-mds.js +3 -400
- package/lib/webauthn.js +12 -833
- package/lib/webcrypto.js +5 -485
- package/lib/x509-sign.js +4 -108
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/schema-c509.js
CHANGED
|
@@ -32,36 +32,23 @@ var b = asn1.build;
|
|
|
32
32
|
var C = constants;
|
|
33
33
|
var C509Error = frameworkError.C509Error;
|
|
34
34
|
function _err(code, message, cause) { return new C509Error(code, message, cause); }
|
|
35
|
-
// The ECMAScript Date window is +/- 8.64e15 ms = +/- 8.64e12 seconds; a C509 ~time is an unsigned
|
|
36
|
-
// epoch-seconds value, so only the upper bound can be exceeded (mirrors cbor-det read.time).
|
|
37
35
|
var MAX_EPOCH_SECONDS = 8640000000000n;
|
|
38
36
|
|
|
39
|
-
// 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
|
|
40
|
-
// OID, resolved to the same name oid.byName returns for the DER form. Declared as int -> registered
|
|
41
|
-
// OID NAME (never a dotted-decimal literal, which the oid-dotted-decimal-literal gate enforces);
|
|
42
|
-
// oid.byName then yields the dotted string. A row whose target name is not registered fails closed at module load.
|
|
43
37
|
function _name(n) { var d = oid.byName(n); if (!d) { throw new Error("schema-c509: unregistered OID name " + JSON.stringify(n)); } return n; }
|
|
44
38
|
|
|
45
|
-
// sec. 8.14 issuerSignatureAlgorithm / signature (the subset v1 covers; negative = legacy SHA-1 values).
|
|
46
39
|
var SIG_ALG_BY_INT = {
|
|
47
40
|
0: _name("ecdsaWithSHA256"),
|
|
48
41
|
1: _name("ecdsaWithSHA384"),
|
|
49
42
|
2: _name("ecdsaWithSHA512"),
|
|
50
43
|
};
|
|
51
|
-
// sec. 8.15 subjectPublicKeyAlgorithm (int -> {alg, curve?} so the reconstruction can rebuild the SPKI).
|
|
52
44
|
var PK_ALG_BY_INT = {
|
|
53
45
|
0: { alg: _name("rsaEncryption") },
|
|
54
46
|
1: { alg: _name("ecPublicKey"), curve: _name("prime256v1") },
|
|
55
47
|
2: { alg: _name("ecPublicKey"), curve: _name("secp384r1") },
|
|
56
48
|
3: { alg: _name("ecPublicKey"), curve: _name("secp521r1") },
|
|
57
49
|
};
|
|
58
|
-
// EC curve -> field size in bytes (the SEC1 coordinate width), for point-length validation.
|
|
59
50
|
var EC_FIELD_BYTES = { "prime256v1": 32, "secp384r1": 48, "secp521r1": 66 };
|
|
60
51
|
|
|
61
|
-
// sec. 8.6 RDN attribute types (abs(int) -> name; the sign selects the X.509 string type: positive utf8String,
|
|
62
|
-
// negative printableString). An attribute whose value is always an IA5String is instead "unambiguously
|
|
63
|
-
// represented using a non-negative int" (draft sec. 3.1.4). emailAddress is int 0, so its value reconstructs
|
|
64
|
-
// as an IA5String and its sign carries no string-type meaning.
|
|
65
52
|
var ATTR_BY_INT = {
|
|
66
53
|
0: _name("emailAddress"),
|
|
67
54
|
1: _name("commonName"),
|
|
@@ -75,11 +62,6 @@ var ATTR_BY_INT = {
|
|
|
75
62
|
9: _name("organizationalUnitName"),
|
|
76
63
|
10: _name("title"),
|
|
77
64
|
};
|
|
78
|
-
// sec. 8.8 extension types (abs(int) -> name; the sign selects criticality). The general-name-bearing
|
|
79
|
-
// extensions (subjectAltName / issuerAltName / nameConstraints / cRLDistributionPoints / freshestCRL /
|
|
80
|
-
// authorityInfoAccess / subjectInfoAccess) ride the shared sec. 8.13 GeneralNames value codec below; each
|
|
81
|
-
// carries a specific CBOR value form and falls back to ~oid + byte-string only when a member is outside
|
|
82
|
-
// the sec. 8.13 registry (an x400Address / ediPartyName general name, a non-URI access location, ...).
|
|
83
65
|
var EXT_BY_INT = {
|
|
84
66
|
1: _name("subjectKeyIdentifier"),
|
|
85
67
|
2: _name("keyUsage"),
|
|
@@ -98,8 +80,6 @@ var EXT_BY_INT = {
|
|
|
98
80
|
29: _name("freshestCRL"),
|
|
99
81
|
30: _name("inhibitAnyPolicy"),
|
|
100
82
|
31: _name("subjectInfoAccess"),
|
|
101
|
-
// RFC 3779 resource-delegation extensions, and their RFC 8360 "v2" twins, which the draft
|
|
102
|
-
// encodes "exactly like" the originals: same two codecs, four registry rows (sec. 8.8).
|
|
103
83
|
32: _name("ipAddrBlocks"),
|
|
104
84
|
33: _name("autonomousSysIds"),
|
|
105
85
|
34: _name("ipAddrBlocksV2"),
|
|
@@ -107,8 +87,6 @@ var EXT_BY_INT = {
|
|
|
107
87
|
36: _name("ocspNoCheck"),
|
|
108
88
|
38: _name("tlsFeature"),
|
|
109
89
|
};
|
|
110
|
-
// The extensions whose compact CBOR value form (sec. 3.3) is invertible to/from the DER extnValue.
|
|
111
|
-
// A registered extension outside this set carries its value as the ~oid + byte-string form (sec. 3.7).
|
|
112
90
|
var EXT_COMPACT = {
|
|
113
91
|
subjectKeyIdentifier: 1, keyUsage: 1, basicConstraints: 1, authorityKeyIdentifier: 1,
|
|
114
92
|
extKeyUsage: 1, inhibitAnyPolicy: 1, ocspNoCheck: 1, tlsFeature: 1,
|
|
@@ -117,8 +95,6 @@ var EXT_COMPACT = {
|
|
|
117
95
|
policyMappings: 1, policyConstraints: 1, subjectDirectoryAttributes: 1,
|
|
118
96
|
ipAddrBlocks: 1, autonomousSysIds: 1, ipAddrBlocksV2: 1, autonomousSysIdsV2: 1,
|
|
119
97
|
};
|
|
120
|
-
// sec. 8.12 Extended Key Usages registry (C509 int -> registered id-kp purpose name). A KeyPurposeId
|
|
121
|
-
// outside this set encodes as an unwrapped ~oid; a C509 int outside it fails closed on decode.
|
|
122
98
|
var EKU_BY_INT = {
|
|
123
99
|
0: _name("anyExtendedKeyUsage"), 1: _name("serverAuth"), 2: _name("clientAuth"),
|
|
124
100
|
3: _name("codeSigning"), 4: _name("emailProtection"), 8: _name("timeStamping"), 9: _name("ocspSigning"),
|
|
@@ -126,29 +102,16 @@ var EKU_BY_INT = {
|
|
|
126
102
|
14: _name("bundleSecurity"), 15: _name("cmcCA"), 16: _name("cmcRA"), 17: _name("cmcArchive"), 18: _name("cmKGA"),
|
|
127
103
|
20: _name("fanDevice"),
|
|
128
104
|
};
|
|
129
|
-
var EKU_TO_INT = {};
|
|
105
|
+
var EKU_TO_INT = {};
|
|
130
106
|
Object.keys(EKU_BY_INT).forEach(function (k) { EKU_TO_INT[oid.byName(EKU_BY_INT[k])] = Number(k); });
|
|
131
107
|
|
|
132
|
-
// The shared pkix namespace + GeneralName leaves the general-name value codec composes for the DER ->
|
|
133
|
-
// CBOR direction (the CVE-2009-2408 IA5 control-byte guard, the otherName/directoryName shape, the
|
|
134
|
-
// iPAddress length and RFC 9549 subtree-base length are all enforced there, never re-validated by hand).
|
|
135
108
|
var NS = pkix.makeNS("c509", C509Error, oid);
|
|
136
|
-
var GN_LEAF = pkix.generalName(NS, { decodeValue: true });
|
|
137
|
-
var GN_LEAF_SUBTREE = pkix.generalName(NS, { decodeValue: true, subtreeBase: true });
|
|
138
|
-
|
|
139
|
-
// sec. 8.13 C509 General Names registry: for the standard forms the C509 int == the RFC 5280 sec. 4.2.1.6
|
|
140
|
-
// GeneralName CHOICE context tag (1 rfc822Name, 2 dNSName, 4 directoryName, 6 URI, 7 iPAddress, 8
|
|
141
|
-
// registeredID); the _generalNameToDer / _generalNameFromDer switches carry that mapping directly.
|
|
142
|
-
// x400Address [3] and ediPartyName [5] have no registry row, so a GeneralNames carrying either is not
|
|
143
|
-
// compact-encodable (the enclosing extension falls back to ~oid + byte-string).
|
|
144
|
-
// The id-on otherName specials get their own negative C509 ints (sec. 8.13 / sec. 3.3): int -> the id-on
|
|
145
|
-
// type-id OID name; the derived inverse keys on the resolved OID so a decode selects the negative int.
|
|
109
|
+
var GN_LEAF = pkix.generalName(NS, { decodeValue: true });
|
|
110
|
+
var GN_LEAF_SUBTREE = pkix.generalName(NS, { decodeValue: true, subtreeBase: true });
|
|
111
|
+
|
|
146
112
|
var ON_NAME_BY_INT = { "-1": _name("hardwareModuleName"), "-2": _name("smtpUtf8Mailbox"), "-3": _name("macAddress") };
|
|
147
113
|
var ON_INT_BY_OID = {};
|
|
148
114
|
Object.keys(ON_NAME_BY_INT).forEach(function (k) { ON_INT_BY_OID[oid.byName(ON_NAME_BY_INT[k])] = Number(k); });
|
|
149
|
-
// sec. 8.11 C509 Information Access registry (int -> registered access-method OID name; the id-ad methods
|
|
150
|
-
// keep the id-ad- prefix, see oid.js). A method OID outside the table encodes as ~oid; a C509 method int
|
|
151
|
-
// outside it fails closed. IA_TO_INT is the derived encode inverse, keyed on the resolved OID.
|
|
152
115
|
var IA_BY_INT = {
|
|
153
116
|
1: _name("ocsp"), 2: _name("caIssuers"), 3: _name("id-ad-timeStamping"), 5: _name("id-ad-caRepository"),
|
|
154
117
|
10: _name("id-ad-rpkiManifest"), 11: _name("id-ad-signedObject"), 13: _name("id-ad-rpkiNotify"),
|
|
@@ -156,9 +119,6 @@ var IA_BY_INT = {
|
|
|
156
119
|
var IA_TO_INT = {};
|
|
157
120
|
Object.keys(IA_BY_INT).forEach(function (k) { IA_TO_INT[oid.byName(IA_BY_INT[k])] = Number(k); });
|
|
158
121
|
|
|
159
|
-
// sec. 8.9 C509 Certificate Policies registry (int -> registered policy OID name; the CA/Browser Forum
|
|
160
|
-
// levels, the RFC 3779 id-cp-ipAddr policies, and the GSMA SGP.22 id-rspRole roles). A policy OID outside
|
|
161
|
-
// this set encodes as ~oid; a C509 policy int outside it fails closed. CP_TO_INT is the derived inverse.
|
|
162
122
|
var CP_BY_INT = {
|
|
163
123
|
0: _name("anyPolicy"), 1: _name("domain-validated"), 2: _name("organization-validated"),
|
|
164
124
|
3: _name("individual-validated"), 4: _name("ev-guidelines"),
|
|
@@ -173,16 +133,11 @@ var CP_BY_INT = {
|
|
|
173
133
|
};
|
|
174
134
|
var CP_TO_INT = {};
|
|
175
135
|
Object.keys(CP_BY_INT).forEach(function (k) { CP_TO_INT[oid.byName(CP_BY_INT[k])] = Number(k); });
|
|
176
|
-
// sec. 8.10 C509 Policies Qualifiers registry (int -> id-qt qualifier OID name; the derived inverse).
|
|
177
136
|
var PQ_BY_INT = { 1: _name("cps"), 2: _name("unotice") };
|
|
178
137
|
var PQ_TO_INT = {};
|
|
179
138
|
Object.keys(PQ_BY_INT).forEach(function (k) { PQ_TO_INT[oid.byName(PQ_BY_INT[k])] = Number(k); });
|
|
180
139
|
|
|
181
|
-
// ---- field readers (the unwrapped ~biguint / ~time / ~oid contracts; draft-20 sec. 3.1) ----
|
|
182
140
|
|
|
183
|
-
// ~biguint (sec. 3.1.2): a BARE byte string (major type 2), big-endian magnitude, the non-negative
|
|
184
|
-
// leading 0x00 OMITTED. This is not the shipped read.biguint, which requires the tag-2 wrapper and
|
|
185
|
-
// rejects <= 8-byte content. A leading 0x00 is non-minimal; content over the cap is rejected.
|
|
186
141
|
function _biguint(node, code, label) {
|
|
187
142
|
if (!node || node.majorType !== 2) throw _err(code, label + " must be an unwrapped CBOR byte string (~biguint)");
|
|
188
143
|
var b = node.content;
|
|
@@ -191,18 +146,14 @@ function _biguint(node, code, label) {
|
|
|
191
146
|
return b.length ? BigInt("0x" + b.toString("hex")) : 0n;
|
|
192
147
|
}
|
|
193
148
|
|
|
194
|
-
// ~time (sec. 3.1.5): a BARE unsigned integer (major type 0), epoch seconds. A major-type-1 / tag / float
|
|
195
|
-
// MUST reject. Bound to the Date window; the CBOR simple null (permitted only for notAfter) -> null.
|
|
196
149
|
function _time(node, allowNull, label) {
|
|
197
|
-
if (allowNull && node.majorType === 7 && node.ai === 22) return null;
|
|
150
|
+
if (allowNull && node.majorType === 7 && node.ai === 22) return null;
|
|
198
151
|
if (node.majorType !== 0) throw _err("c509/bad-validity", label + " must be an unwrapped CBOR epoch integer (~time)");
|
|
199
152
|
var secs = node.argument;
|
|
200
153
|
if (secs > MAX_EPOCH_SECONDS) throw _err("c509/bad-validity", label + " is outside the representable Date range");
|
|
201
154
|
return new Date(C.TIME.seconds(Number(secs)));
|
|
202
155
|
}
|
|
203
156
|
|
|
204
|
-
// ~oid (sec. 3.1.3 etc.): a BARE byte string carrying the BER OID content octets (RFC 9090), no tag-111
|
|
205
|
-
// head. Compose asn1.decodeOidContent -> dotted -> oid.name (the same the tag reader does internally).
|
|
206
157
|
function _oidName(node, code, label) {
|
|
207
158
|
if (!node || node.majorType !== 2) throw _err(code, label + " must be an unwrapped CBOR byte string (~oid)");
|
|
208
159
|
var dotted;
|
|
@@ -211,11 +162,10 @@ function _oidName(node, code, label) {
|
|
|
211
162
|
return { oid: dotted, name: oid.name(dotted) || dotted };
|
|
212
163
|
}
|
|
213
164
|
|
|
214
|
-
// AlgorithmIdentifier (sec. 3.1.3/sec. 3.1.7): int (registry) | ~oid (bare bytes) | [ ~oid, params ].
|
|
215
165
|
function _algorithm(node, byInt, code, label) {
|
|
216
166
|
if (node.majorType === 0 || node.majorType === 1) {
|
|
217
167
|
var i = Number(cbor.read.int(node));
|
|
218
|
-
var mapped = byInt[i];
|
|
168
|
+
var mapped = byInt[i];
|
|
219
169
|
if (mapped === undefined) throw _err("c509/unknown-algorithm", label + " integer " + i + " has no C509 registry row");
|
|
220
170
|
if (typeof mapped === "string") return { name: mapped, oid: oid.byName(mapped) };
|
|
221
171
|
return { name: mapped.alg, oid: oid.byName(mapped.alg), curve: mapped.curve || null };
|
|
@@ -223,27 +173,17 @@ function _algorithm(node, byInt, code, label) {
|
|
|
223
173
|
if (node.majorType === 2) { var r = _oidName(node, code, label); return { name: r.name, oid: r.oid }; }
|
|
224
174
|
if (node.majorType === 4 && node.children && node.children.length === 2) {
|
|
225
175
|
var a = _oidName(node.children[0], code, label);
|
|
226
|
-
// The [~oid, params] form carries the DER parameters as a CBOR byte string; a non-byte-string here
|
|
227
|
-
// is malformed and cannot be reconstructed (b.raw would append garbage), so it fails closed.
|
|
228
176
|
if (node.children[1].majorType !== 2) throw _err(code, label + " algorithm parameters must be a CBOR byte string");
|
|
229
|
-
// The parameters carry one complete DER element spliced into the reconstruction. An empty byte
|
|
230
|
-
// string is no element at all: it rebuilds the same AlgorithmIdentifier as the bare ~oid form,
|
|
231
|
-
// which is how the CDDL already spells "no parameters", so accepting it would give one
|
|
232
|
-
// algorithm two encodings, and one X.509 signature would cover both.
|
|
233
177
|
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)");
|
|
234
178
|
return { name: a.name, oid: a.oid, parameters: node.children[1].content };
|
|
235
179
|
}
|
|
236
180
|
throw _err(code, label + " is not a C509 AlgorithmIdentifier (int / ~oid / [~oid, params])");
|
|
237
181
|
}
|
|
238
182
|
|
|
239
|
-
// SpecialText attribute value (sec. 3.1.4/sec. 3.1.6): text | bytes (even-length-hex optimization) | tag-48
|
|
240
|
-
// (a MAC address, RFC 9542). v1 surfaces the value; the DN string uses the text form.
|
|
241
183
|
function _specialText(node) {
|
|
242
184
|
if (node.majorType === 3) return { text: cbor.read.textString(node) };
|
|
243
185
|
if (node.majorType === 2) return { hex: node.content.toString("hex") };
|
|
244
186
|
if (node.majorType === 6 && Number(node.argument) === 48) {
|
|
245
|
-
// A tag-48 MAC address (RFC 9542) MUST wrap a CBOR byte string of 6 (EUI-48/MAC-48) or 8 (EUI-64)
|
|
246
|
-
// bytes; anything else is malformed and cannot reconstruct a well-formed EUI-64 commonName.
|
|
247
187
|
if (!node.children || !node.children[0] || node.children[0].majorType !== 2) {
|
|
248
188
|
throw _err("c509/bad-name", "a tag-48 MAC-address value must wrap a CBOR byte string");
|
|
249
189
|
}
|
|
@@ -254,34 +194,22 @@ function _specialText(node) {
|
|
|
254
194
|
throw _err("c509/bad-name", "an attribute value is not a C509 SpecialText (text / bytes / tag-48)");
|
|
255
195
|
}
|
|
256
196
|
|
|
257
|
-
// draft sec. 3.1.4 fixes which of the three SpecialText spellings a value takes; the choice belongs to
|
|
258
|
-
// the specification, not the sender. A text string of an even length >= 2 drawn only from '0'-'9'/'a'-'f'
|
|
259
|
-
// 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
|
|
260
|
-
// MAC address, 48-bit when it matches "HH-HH-HH-FF-FE-HH-HH-HH" and 64-bit otherwise; anything else is a
|
|
261
|
-
// text string. Accepting a second spelling of one value would give the reconstructed certificate more than
|
|
262
|
-
// one C509 encoding, so a single X.509 signature would cover them all and two distinct byte strings would
|
|
263
|
-
// name one certificate. A natively signed certificate is exempt: sec. 3.1.4 says bytes and tag 48
|
|
264
|
-
// there "do not correspond to any predefined text string encoding and may also be used for other attribute
|
|
265
|
-
// types", so no canonical text spelling exists to hold one to.
|
|
266
|
-
// Even-length lowercase hex ("HHHH..."), decided by character so no regex match protocol -- a live,
|
|
267
|
-
// replaceable exec -- stands between the canonicalization gate and the value it inspects.
|
|
268
197
|
function _isEvenHexLower(s) {
|
|
269
198
|
var n = s.length;
|
|
270
199
|
if (n < 2 || (n & 1) !== 0) return false;
|
|
271
200
|
for (var i = 0; i < n; i++) {
|
|
272
201
|
var c = s.charCodeAt(i);
|
|
273
|
-
if (!((c >= 48 && c <= 57) || (c >= 97 && c <= 102))) return false;
|
|
202
|
+
if (!((c >= 48 && c <= 57) || (c >= 97 && c <= 102))) return false;
|
|
274
203
|
}
|
|
275
204
|
return true;
|
|
276
205
|
}
|
|
277
|
-
// EUI-64 text form: eight uppercase hex pairs joined by single hyphens ("HH-HH-...-HH", 23 chars).
|
|
278
206
|
function _isEui64Text(s) {
|
|
279
207
|
if (s.length !== 23) return false;
|
|
280
208
|
for (var i = 0; i < 23; i++) {
|
|
281
|
-
if ((i % 3) === 2) { if (s.charCodeAt(i) !== 45) return false; }
|
|
209
|
+
if ((i % 3) === 2) { if (s.charCodeAt(i) !== 45) return false; }
|
|
282
210
|
else {
|
|
283
211
|
var c = s.charCodeAt(i);
|
|
284
|
-
if (!((c >= 48 && c <= 57) || (c >= 65 && c <= 70))) return false;
|
|
212
|
+
if (!((c >= 48 && c <= 57) || (c >= 65 && c <= 70))) return false;
|
|
285
213
|
}
|
|
286
214
|
}
|
|
287
215
|
return true;
|
|
@@ -299,7 +227,6 @@ function _assertCanonicalSpecialText(node, isNative, E) {
|
|
|
299
227
|
return;
|
|
300
228
|
}
|
|
301
229
|
if (node.majorType === 2) {
|
|
302
|
-
// An empty byte string renders as the empty text, whose canonical spelling is a text string.
|
|
303
230
|
if (node.content.length === 0) throw _err(E, "an empty attribute value must be encoded as a CBOR text string (draft sec. 3.1.4)");
|
|
304
231
|
return;
|
|
305
232
|
}
|
|
@@ -309,14 +236,6 @@ function _assertCanonicalSpecialText(node, isNative, E) {
|
|
|
309
236
|
}
|
|
310
237
|
}
|
|
311
238
|
|
|
312
|
-
// A single Name (sec. 3.1.4/sec. 3.1.6): the CBOR simple null (issuer only) | a bare SpecialText single
|
|
313
|
-
// commonName | an array of RDNAttributes. Surfaces { dn, rdns, eui64? } shape-compatible with x509.
|
|
314
|
-
// An rdn's (attribute type, sign) pair DECLARES an X.509 string type, so its text must be valid for that type --
|
|
315
|
-
// a printableString sign over a non-PrintableString character, a non-ASCII emailAddress (IA5String-only), or a
|
|
316
|
-
// value outside the type's SIZE. Assert it by running the same builder the reconstruction uses, so a Name this
|
|
317
|
-
// parse accepts is always one it can rebuild, and a natively-signed certificate (which never reconstructs) is
|
|
318
|
-
// held to the identical rule. Both Name forms get it: the array form and the bare single-commonName form.
|
|
319
|
-
// The builder's asn1/* fault is re-thrown in this module's domain so it cannot leak onto the parse surface.
|
|
320
239
|
function _assertAttrValue(rdn) {
|
|
321
240
|
try { _reconAttrValue(rdn); }
|
|
322
241
|
catch (e) {
|
|
@@ -326,16 +245,10 @@ function _assertAttrValue(rdn) {
|
|
|
326
245
|
}
|
|
327
246
|
|
|
328
247
|
function _name509(node, isSubject, isNative) {
|
|
329
|
-
if (!isSubject && node.majorType === 7 && node.ai === 22) return null;
|
|
330
|
-
// A bare SpecialText (not an array) is a single commonName attribute (attributeType == +1).
|
|
248
|
+
if (!isSubject && node.majorType === 7 && node.ai === 22) return null;
|
|
331
249
|
if (node.majorType === 3 || node.majorType === 2 || node.majorType === 6) {
|
|
332
250
|
var sv = _specialText(node);
|
|
333
251
|
_assertCanonicalSpecialText(node, isNative, "c509/bad-name");
|
|
334
|
-
// The bare form is a single commonName. Its value is held to the same rules the array form applies, so a
|
|
335
|
-
// natively-signed certificate (which never reconstructs) cannot carry a value the reconstruction would refuse.
|
|
336
|
-
// A tag-48 MAC renders to a fixed 17-character EUI-64 string that satisfies every value rule by
|
|
337
|
-
// construction, and _reconAttrValue short-circuits on eui64 before any of them, which makes an assertion
|
|
338
|
-
// here a no-op. The rules bind on the TEXT forms below.
|
|
339
252
|
if (sv.eui64) return { rdns: [{ type: "commonName", eui64: sv.eui64 }], eui64: sv.eui64, dn: "CN=" + _macToEui64String(sv.eui64) };
|
|
340
253
|
var val = sv.text !== undefined ? sv.text : sv.hex;
|
|
341
254
|
var bareRdn = { type: "commonName", value: val };
|
|
@@ -346,88 +259,41 @@ function _name509(node, isSubject, isNative) {
|
|
|
346
259
|
var rdns = [];
|
|
347
260
|
var parts = [];
|
|
348
261
|
var kids = node.children || [];
|
|
349
|
-
// Each RDN attribute is an (attributeType, attributeValue) pair; an odd-length array is a dangling
|
|
350
|
-
// attribute type with no value, so the whole Name is rejected.
|
|
351
262
|
if (kids.length % 2 !== 0) throw _err("c509/bad-name", "a C509 Name array must be attribute-type/value pairs (dangling attribute type)");
|
|
352
|
-
// RFC 5280 sec. 4.1.2.4 -- the issuer MUST be a non-empty distinguished name (only the SUBJECT may be empty,
|
|
353
|
-
// for the subjectAltName case). An empty issuer array would reconstruct a certificate this toolkit's own
|
|
354
|
-
// x509.parse refuses to load (x509/bad-issuer), so the codec must not produce one.
|
|
355
263
|
if (kids.length === 0 && !isSubject) throw _err("c509/bad-name", "the issuer Name must not be empty (RFC 5280 sec. 4.1.2.4)");
|
|
356
264
|
for (var i = 0; i + 1 < kids.length; i += 2) {
|
|
357
|
-
// The attributeType slot must be a CBOR integer (major type 0/1). Guard the major type before the read
|
|
358
|
-
// so a non-integer type slot fails in this module's own domain (c509/bad-name), matching the value slot,
|
|
359
|
-
// and cbor.read.int's cbor/unexpected-major fault never leaks onto the parse surface. Reached with
|
|
360
|
-
// attacker-controlled bytes via the sec. 8.13 directoryName general name (subjectAltName, authorityKey-
|
|
361
|
-
// Identifier issuer, cRLDistributionPoints cRLIssuer, nameConstraints subtree base) and the top-level Name.
|
|
362
265
|
if (kids[i].majorType !== 0 && kids[i].majorType !== 1) throw _err("c509/bad-name", "a C509 Name attribute type must be a CBOR integer");
|
|
363
266
|
var ti = Number(cbor.read.int(kids[i]));
|
|
364
|
-
// draft sec. 3.1.4: "in natively signed C509 certificates all CBOR ints SHALL be non-negative." The sign
|
|
365
|
-
// exists only to reproduce the string type of an original X.509 DER, which a native certificate does not
|
|
366
|
-
// have. A negative attributeType there is an encoding no conformant producer emits, and accepting it
|
|
367
|
-
// would let this parser read a natively-signed name a conformant peer must reject.
|
|
368
267
|
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);
|
|
369
268
|
var tname = ATTR_BY_INT[Math.abs(ti)];
|
|
370
269
|
if (tname === undefined) throw _err("c509/bad-name", "attribute type integer " + ti + " has no C509 registry row");
|
|
371
270
|
var v = _specialText(kids[i + 1]);
|
|
372
271
|
_assertCanonicalSpecialText(kids[i + 1], isNative, "c509/bad-name");
|
|
373
272
|
var vv = v.text !== undefined ? v.text : (v.hex !== undefined ? v.hex : _macToEui64String(v.eui64));
|
|
374
|
-
// The rdn carries the RENDERED text, because the EUI shortcut
|
|
375
|
-
// in _reconAttrValue is unconditional and would take precedence over the attribute's declared string type,
|
|
376
|
-
// rebuilding a tag-48 emailAddress or countryName as a UTF8String. Only the bare-SpecialText form above is
|
|
377
|
-
// always a commonName and may carry it; here the value is held to whatever type the attribute integer declares.
|
|
378
273
|
var rdn = { type: tname, value: vv, printable: ti < 0 };
|
|
379
274
|
_assertAttrValue(rdn);
|
|
380
275
|
rdns.push(rdn);
|
|
381
276
|
parts.push(_shortName(tname) + "=" + guard.name.escapeDnValue(vv));
|
|
382
277
|
}
|
|
383
|
-
// sec. 3.1.4: "If Name contains a single 'common name' attribute with attributeType = +1, it is for
|
|
384
|
-
// compactness encoded as just the SpecialText containing the single attribute value." The array
|
|
385
|
-
// spelling of that one case is therefore a second encoding of a name that already has a canonical
|
|
386
|
-
// one. Only +1 has a bare form; a negative (printableString) commonName keeps the array.
|
|
387
278
|
if (!isNative && rdns.length === 1 && rdns[0].type === "commonName" && !rdns[0].printable) {
|
|
388
279
|
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)");
|
|
389
280
|
}
|
|
390
281
|
return { rdns: rdns, dn: parts.join(", ") };
|
|
391
282
|
}
|
|
392
|
-
// The rendered dn uses the same spelling as every other name renderer here (schema-pkix): the RDNs in
|
|
393
|
-
// certificate order joined by ", ", which is what `openssl x509 -subject` prints. One certificate therefore
|
|
394
|
-
// has one dn string whichever parser read it, and the interop gate can pin both against openssl. It is
|
|
395
|
-
// deliberately not the RFC 4514 / LDAP distinguishedName (which reverses the RDNs and drops the space).
|
|
396
|
-
// What IS taken from RFC 4514 is sec. 2.4 value escaping, through the shared guard: raw concatenation
|
|
397
|
-
// would let a single attribute whose value contains a comma render identically to a genuine multi-RDN
|
|
398
|
-
// name (a spoofable identity string, and the only name surface a natively signed certificate has, since
|
|
399
|
-
// it never reconstructs DER), and would carry control bytes into logs.
|
|
400
|
-
// The label table is the one schema-pkix renders with (pki.C.NAMES.DN_SHORT), not a local list: a hand-rolled
|
|
401
|
-
// one covers whichever attributes it was written for and silently renders the rest by their long name,
|
|
402
|
-
// so the same certificate would still read differently depending on which parser produced the string.
|
|
403
283
|
function _shortName(n) { return (n && constants.NAMES.DN_SHORT[n]) || n; }
|
|
404
284
|
|
|
405
|
-
// ---- compact per-extension value codec (draft-20 sec. 3.3) -------------------
|
|
406
|
-
// Each registered extension in EXT_COMPACT has a specific CBOR encoding of its value that is invertible
|
|
407
|
-
// to/from the DER extnValue inner content (the bytes _reconExtensions wraps in an OCTET STRING). Native
|
|
408
|
-
// C509 MUST use the specific form (sec. 3.7); the decode side reconstructs the DER, the encode side emits
|
|
409
|
-
// the compact form only when it round-trips byte-for-byte (else the ~oid + byte-string fallback).
|
|
410
285
|
|
|
411
|
-
// A CBOR unsigned integer node -> its BigInt value (major type 0 guarantees non-negative). Arbitrary
|
|
412
|
-
// precision: a DER INTEGER holds any width, so there is no cap; the round-trip guard verifies the reconstruction.
|
|
413
286
|
function _cborUint(node, label) {
|
|
414
287
|
if (node.majorType !== 0) throw _err("c509/bad-extensions", "a " + label + " value must be a CBOR unsigned integer");
|
|
415
288
|
return cbor.read.int(node);
|
|
416
289
|
}
|
|
417
|
-
// A CBOR integer node (either sign) -> its BigInt value (basicConstraints uses -2/-1/pathLen).
|
|
418
290
|
function _cborIntVal(node, label) {
|
|
419
291
|
if (node.majorType !== 0 && node.majorType !== 1) throw _err("c509/bad-extensions", "a " + label + " value must be a CBOR integer");
|
|
420
292
|
return cbor.read.int(node);
|
|
421
293
|
}
|
|
422
|
-
// A non-negative reconstruct-side count (a policyConstraints / inhibitAnyPolicy SkipCerts, a basicConstraints
|
|
423
|
-
// pathLenConstraint) narrowed to [0, 2^31-1] through guard.range.uint31, the same bound the toolkit's own DER
|
|
424
|
-
// decoders (schema-pkix) put on these INTEGER (0..MAX) counts. A native CBOR value past 2^31-1 fails closed
|
|
425
|
-
// here; it never reconstructs a DER the toolkit's own decoder would then reject, because the reconstruct path
|
|
426
|
-
// must produce nothing that decoder rejects. Returns a Number; b.integer narrows it to the minimal DER INTEGER.
|
|
427
294
|
function _boundCount(value, label) {
|
|
428
295
|
return guard.range.uint31(value, _err, "c509/bad-extensions", label + " (0..2^31-1)");
|
|
429
296
|
}
|
|
430
|
-
// A C509 KeyPurposeId (int registry alias OR unwrapped ~oid) -> the dotted extended-key-usage OID.
|
|
431
297
|
function _ekuPurposeOid(node) {
|
|
432
298
|
if (node.majorType === 0 || node.majorType === 1) {
|
|
433
299
|
var i = Number(cbor.read.int(node));
|
|
@@ -435,34 +301,16 @@ function _ekuPurposeOid(node) {
|
|
|
435
301
|
if (nm === undefined) throw _err("c509/bad-extensions", "an extKeyUsage int " + i + " has no C509 registry row");
|
|
436
302
|
return oid.byName(nm);
|
|
437
303
|
}
|
|
438
|
-
// A ~oid KeyPurposeId routes through _oidName, which validates the BER OID content and remaps an oid/*
|
|
439
|
-
// fault to c509/bad-extensions (fault preserved as .cause). A non-byte-string node fails closed there too.
|
|
440
304
|
return _oidName(node, "c509/bad-extensions", "an extKeyUsage KeyPurposeId").oid;
|
|
441
305
|
}
|
|
442
306
|
|
|
443
|
-
// ---- the shared C509 GeneralNames value codec (draft-20 sec. 3.3 / sec. 8.13) ------------------
|
|
444
|
-
// One int <-> DER-context-tag pair (_generalNameToDer / _generalNameFromDer) drives every general-name-
|
|
445
|
-
// bearing extension (subjectAltName, nameConstraints, authorityKeyIdentifier, cRLDistributionPoints,
|
|
446
|
-
// authorityInfoAccess, ...), so a tag/int mapping bug cannot diverge across them. The DER -> CBOR side
|
|
447
|
-
// composes the shared pkix.generalName leaf; the CBOR -> DER side builds directly with asn1.build.*, the
|
|
448
|
-
// exact inverse. Every encode stays behind the extension-level byte-exact round-trip guard
|
|
449
|
-
// (_tryCompactExtValue), so a member the codec cannot invert byte-for-byte falls the whole extension back
|
|
450
|
-
// to the conformant ~oid + byte-string form (sec. 3.7). A partial GeneralNames is never emitted.
|
|
451
307
|
|
|
452
|
-
// The unwrapped ~oid CBOR form (a bare byte string of the BER OID content octets; RFC 9090).
|
|
453
308
|
function _oidCbor(dotted) { return cbor.build.byteString(asn1.encodeOidContent(dotted)); }
|
|
454
|
-
// The CBOR simple value null (0xF6): the omitted permitted / excluded / reasons / cRLIssuer field.
|
|
455
309
|
function _isCborNull(node) { return node.majorType === 7 && node.ai === 22; }
|
|
456
310
|
|
|
457
|
-
// A CBOR text node -> the IA5String content octets of an IMPLICIT [n] GeneralName (rfc822Name/dNSName/URI).
|
|
458
|
-
// IA5String is 7-bit ASCII, so a code point > 0x7f cannot be one; a control byte (C0/DEL) is an injection
|
|
459
|
-
// vector (CVE-2009-2408) the shared name guard rejects. That matches the pkix.generalName decode side, so a
|
|
460
|
-
// value it rejects also fails the reconstruction direction (no round-trip guard runs on decode).
|
|
461
311
|
function _ia5Bytes(node, tag) {
|
|
462
312
|
if (node.majorType !== 3) throw _err("c509/bad-extensions", "a GeneralName [" + tag + "] value must be a CBOR text string (IA5String)");
|
|
463
313
|
var s = cbor.read.textString(node);
|
|
464
|
-
// An empty IA5 GeneralName is rejected by the shared pkix.generalName leaf (a non-empty rfc822Name /
|
|
465
|
-
// dNSName / URI), so the reconstruction side rejects it too.
|
|
466
314
|
if (s.length === 0) throw _err("c509/bad-extensions", "a GeneralName [" + tag + "] IA5String must be non-empty");
|
|
467
315
|
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"); }
|
|
468
316
|
var buf = Buffer.from(s, "latin1");
|
|
@@ -470,35 +318,27 @@ function _ia5Bytes(node, tag) {
|
|
|
470
318
|
return buf;
|
|
471
319
|
}
|
|
472
320
|
|
|
473
|
-
// A named-bit-list value (bit i -> named bit i, MSB-first; the 9-bit keyUsage / ReasonFlags range) -> the
|
|
474
|
-
// DER BIT STRING content { unusedBits, bytes }, or null when the value is outside the 9 defined bits.
|
|
475
321
|
function _namedBitsToContent(value) {
|
|
476
322
|
if (!Number.isInteger(value) || value <= 0 || value > 0x1ff) return null;
|
|
477
|
-
var hi = 0; for (var t = value; t; t >>= 1) hi++; hi -= 1;
|
|
323
|
+
var hi = 0; for (var t = value; t; t >>= 1) hi++; hi -= 1;
|
|
478
324
|
var buf = Buffer.alloc((hi >> 3) + 1);
|
|
479
325
|
for (var bit = 0; bit <= hi; bit++) { if (value & (1 << bit)) buf[bit >> 3] |= 0x80 >> (bit & 7); }
|
|
480
326
|
return { unusedBits: 7 - (hi & 7), bytes: buf };
|
|
481
327
|
}
|
|
482
|
-
// The inverse: a DER BIT STRING content -> the named-bit value (bit i -> 2^i), or null when out of range.
|
|
483
328
|
function _namedBitsFromContent(bytes, unusedBits) {
|
|
484
329
|
var total = bytes.length * 8 - unusedBits, value = 0;
|
|
485
330
|
for (var bit = 0; bit < total && bit < 31; bit++) { if (bytes[bit >> 3] & (0x80 >> (bit & 7))) value |= (1 << bit); }
|
|
486
331
|
return value > 0 && value <= 0x1ff ? value : null;
|
|
487
332
|
}
|
|
488
333
|
|
|
489
|
-
// One C509 (int, value) general name -> one DER GeneralName TLV. `ipMode` (name-constraints subtree base)
|
|
490
|
-
// only changes the iPAddress arm: SAN is a bare 4/16-octet address, a subtree base is the RFC 9549 form.
|
|
491
334
|
function _generalNameToDer(intVal, valueNode, ipMode, isNative) {
|
|
492
|
-
if (intVal === 1 || intVal === 2 || intVal === 6) return b.contextPrimitive(intVal, _ia5Bytes(valueNode, intVal));
|
|
493
|
-
if (intVal === 4) return b.explicit(4, _reconName(_name509(valueNode, true, isNative)));
|
|
494
|
-
if (intVal === 7) return b.contextPrimitive(7, ipMode ? _ncIpToDer(valueNode) : _sanIpBytes(valueNode));
|
|
495
|
-
if (intVal === 8) return b.contextPrimitive(8, asn1.encodeOidContent(_oidName(valueNode, "c509/bad-extensions", "a registeredID [8]").oid));
|
|
496
|
-
if (intVal === 0 || intVal === -1 || intVal === -2 || intVal === -3) return _otherNameToDer(valueNode, intVal);
|
|
335
|
+
if (intVal === 1 || intVal === 2 || intVal === 6) return b.contextPrimitive(intVal, _ia5Bytes(valueNode, intVal));
|
|
336
|
+
if (intVal === 4) return b.explicit(4, _reconName(_name509(valueNode, true, isNative)));
|
|
337
|
+
if (intVal === 7) return b.contextPrimitive(7, ipMode ? _ncIpToDer(valueNode) : _sanIpBytes(valueNode));
|
|
338
|
+
if (intVal === 8) return b.contextPrimitive(8, asn1.encodeOidContent(_oidName(valueNode, "c509/bad-extensions", "a registeredID [8]").oid));
|
|
339
|
+
if (intVal === 0 || intVal === -1 || intVal === -2 || intVal === -3) return _otherNameToDer(valueNode, intVal);
|
|
497
340
|
throw _err("c509/bad-extensions", "GeneralName int " + intVal + " has no C509 sec. 8.13 registry row");
|
|
498
341
|
}
|
|
499
|
-
// The DER GeneralName leaf result { tagNumber, value, bytes } -> a [intCbor, valueCbor] pair, or null when
|
|
500
|
-
// the name is not compact-representable (x400Address [3] / ediPartyName [5], a directoryName the compact
|
|
501
|
-
// Name codec cannot hold, a non-canonical name-constraints IP mask).
|
|
502
342
|
function _generalNameFromDer(gn, ipMode) {
|
|
503
343
|
var t = gn.tagNumber;
|
|
504
344
|
if (t === 1 || t === 2 || t === 6) return [cbor.build.int(BigInt(t)), cbor.build.textString(gn.value)];
|
|
@@ -509,13 +349,9 @@ function _generalNameFromDer(gn, ipMode) {
|
|
|
509
349
|
}
|
|
510
350
|
if (t === 8) return [cbor.build.int(8n), _oidCbor(gn.value)];
|
|
511
351
|
if (t === 0) return _otherNameFromDer(gn.value);
|
|
512
|
-
return null;
|
|
352
|
+
return null;
|
|
513
353
|
}
|
|
514
354
|
|
|
515
|
-
// A flat CBOR array [int, value, int, value, ...] -> the list of DER GeneralName TLVs (the caller wraps
|
|
516
|
-
// them: a universal SEQUENCE for SAN/IAN, an implicit [n] for AKI issuer / DP cRLIssuer). These are always
|
|
517
|
-
// SAN-form (the RFC 9549 subtree-base iPAddress form is name-constraints-only and routes through
|
|
518
|
-
// _subtreesToDer / _subtreesFromDer, which call the singular codec with ipMode directly).
|
|
519
355
|
function _generalNamesToDer(node, isNative) {
|
|
520
356
|
if (node.majorType !== 4 || !node.children) throw _err("c509/bad-extensions", "a GeneralNames value must be a CBOR array");
|
|
521
357
|
var kids = node.children;
|
|
@@ -524,8 +360,6 @@ function _generalNamesToDer(node, isNative) {
|
|
|
524
360
|
for (var i = 0; i + 1 < kids.length; i += 2) out.push(_generalNameToDer(Number(_cborIntVal(kids[i], "a GeneralName type")), kids[i + 1], false, isNative));
|
|
525
361
|
return out;
|
|
526
362
|
}
|
|
527
|
-
// The inverse: a list of DER GeneralName nodes -> the flat CBOR items, or null if ANY member is not
|
|
528
|
-
// compact-representable (the whole enclosing extension then falls back, and no partial GeneralNames is produced).
|
|
529
363
|
function _generalNamesFromDer(gnNodes) {
|
|
530
364
|
var out = [];
|
|
531
365
|
for (var i = 0; i < gnNodes.length; i++) {
|
|
@@ -536,85 +370,67 @@ function _generalNamesFromDer(gnNodes) {
|
|
|
536
370
|
return out;
|
|
537
371
|
}
|
|
538
372
|
|
|
539
|
-
// otherName [0] IMPLICIT SEQUENCE { type-id OBJECT IDENTIFIER, value [0] EXPLICIT ANY }. The context tag
|
|
540
|
-
// REPLACES the SEQUENCE tag (RFC 5280 IMPLICIT tagging), so the [0] node holds type-id then value directly
|
|
541
|
-
// (no inner universal SEQUENCE). The generic form (C509 int 0) carries [ ~oid(type-id), bytes(the [0]
|
|
542
|
-
// EXPLICIT inner value's raw TLV) ]; the id-on specials (-1/-2/-3) carry the RFC 4108 / RFC 9598 /
|
|
543
|
-
// lamps-macaddress value shapes and reconstruct the full OtherName.
|
|
544
373
|
function _otherNameToDer(node, intVal) {
|
|
545
374
|
var typeId, inner;
|
|
546
375
|
if (intVal === 0) {
|
|
547
376
|
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");
|
|
548
377
|
typeId = _oidName(node.children[0], "c509/bad-extensions", "an otherName type-id").oid;
|
|
549
378
|
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)");
|
|
550
|
-
// The [0] EXPLICIT inner TLV is an ANY spliced verbatim: strict-validate it, not just its framing.
|
|
551
379
|
inner = _requireStrictDerTlv(node.children[1].content, "c509/bad-extensions", "an otherName value");
|
|
552
|
-
} else if (intVal === -1) {
|
|
380
|
+
} else if (intVal === -1) {
|
|
553
381
|
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");
|
|
554
382
|
if (node.children[1].majorType !== 2) throw _err("c509/bad-extensions", "a hardwareModuleName hwSerialNum must be a CBOR byte string");
|
|
555
383
|
typeId = oid.byName("hardwareModuleName");
|
|
556
384
|
inner = b.sequence([b.oid(_oidName(node.children[0], "c509/bad-extensions", "a hardwareModuleName hwType").oid), b.octetString(node.children[1].content)]);
|
|
557
|
-
} else if (intVal === -2) {
|
|
385
|
+
} else if (intVal === -2) {
|
|
558
386
|
if (node.majorType !== 3) throw _err("c509/bad-extensions", "an id-on-SmtpUTF8Mailbox value must be a CBOR text string");
|
|
559
387
|
var smtp = cbor.read.textString(node);
|
|
560
388
|
if (smtp.length === 0) throw _err("c509/bad-extensions", "an id-on-SmtpUTF8Mailbox value must be non-empty (RFC 9598 SIZE (1..MAX))");
|
|
561
389
|
typeId = oid.byName("smtpUtf8Mailbox");
|
|
562
390
|
inner = b.utf8(smtp);
|
|
563
|
-
} else {
|
|
391
|
+
} else {
|
|
564
392
|
if (node.majorType !== 2) throw _err("c509/bad-extensions", "an id-on-MACAddress value must be a CBOR byte string");
|
|
565
393
|
if (node.content.length !== 6 && node.content.length !== 8) throw _err("c509/bad-extensions", "an id-on-MACAddress value must be 6 (EUI-48) or 8 (EUI-64) octets");
|
|
566
394
|
typeId = oid.byName("macAddress");
|
|
567
395
|
inner = b.octetString(node.content);
|
|
568
396
|
}
|
|
569
|
-
return b.contextConstructed(0, Buffer.concat([b.oid(typeId), b.explicit(0, inner)]));
|
|
397
|
+
return b.contextConstructed(0, Buffer.concat([b.oid(typeId), b.explicit(0, inner)]));
|
|
570
398
|
}
|
|
571
|
-
// The inverse: the pkix.generalName otherName value { typeId, valueBytes } -> a [intCbor, valueCbor] pair,
|
|
572
|
-
// or null when an id-on special's inner value is not its RFC-defined shape (the extension then falls back).
|
|
573
399
|
function _otherNameFromDer(v) {
|
|
574
400
|
var onInt = ON_INT_BY_OID[v.typeId];
|
|
575
401
|
if (onInt === undefined) return [cbor.build.int(0n), cbor.build.array([_oidCbor(v.typeId), cbor.build.byteString(v.valueBytes)])];
|
|
576
402
|
var inner;
|
|
577
|
-
try { inner = asn1.decode(v.valueBytes); } catch (_e) { return null; }
|
|
578
|
-
if (onInt === -1) {
|
|
403
|
+
try { inner = asn1.decode(v.valueBytes); } catch (_e) { return null; } // allow:swallow-unverified -- a malformed otherName value yields null, so the extension does not compact and is carried in C509 general form (raw bytes)
|
|
404
|
+
if (onInt === -1) {
|
|
579
405
|
if (inner.tagClass !== "universal" || inner.tagNumber !== asn1.TAGS.SEQUENCE || !inner.children || inner.children.length !== 2) return null;
|
|
580
406
|
var hwType, hwSerial;
|
|
581
|
-
try { hwType = asn1.read.oid(inner.children[0]); hwSerial = asn1.read.octetString(inner.children[1]); } catch (_e2) { return null; }
|
|
407
|
+
try { hwType = asn1.read.oid(inner.children[0]); hwSerial = asn1.read.octetString(inner.children[1]); } catch (_e2) { return null; } // allow:swallow-unverified -- a malformed hardwareModuleName yields null, so the extension is carried in C509 general form instead
|
|
582
408
|
return [cbor.build.int(-1n), cbor.build.array([_oidCbor(hwType), cbor.build.byteString(hwSerial)])];
|
|
583
409
|
}
|
|
584
|
-
if (onInt === -2) {
|
|
410
|
+
if (onInt === -2) {
|
|
585
411
|
if (inner.tagClass !== "universal" || inner.tagNumber !== asn1.TAGS.UTF8_STRING) return null;
|
|
586
|
-
var txt; try { txt = asn1.read.string(inner); } catch (_e3) { return null; }
|
|
412
|
+
var txt; try { txt = asn1.read.string(inner); } catch (_e3) { return null; } // allow:swallow-unverified -- a non-UTF8String otherName yields null, so the extension is carried in C509 general form instead
|
|
587
413
|
return [cbor.build.int(-2n), cbor.build.textString(txt)];
|
|
588
414
|
}
|
|
589
|
-
if (inner.tagClass !== "universal" || inner.tagNumber !== asn1.TAGS.OCTET_STRING) return null;
|
|
590
|
-
var mac; try { mac = asn1.read.octetString(inner); } catch (_e4) { return null; }
|
|
591
|
-
// mac is a PUBLIC hardware MAC address (an EUI-48/64 identifier, RFC 9542 / lamps-macaddress), not a
|
|
592
|
-
// MAC / HMAC / auth tag / secret; this is a length check.
|
|
593
|
-
// nosemgrep: pki-non-constant-time-secret-compare
|
|
415
|
+
if (inner.tagClass !== "universal" || inner.tagNumber !== asn1.TAGS.OCTET_STRING) return null;
|
|
416
|
+
var mac; try { mac = asn1.read.octetString(inner); } catch (_e4) { return null; } // allow:swallow-unverified -- a malformed octet-string otherName value yields null, so the extension is carried in C509 general form instead
|
|
594
417
|
if (mac.length !== 6 && mac.length !== 8) return null;
|
|
595
418
|
return [cbor.build.int(-3n), cbor.build.byteString(mac)];
|
|
596
419
|
}
|
|
597
420
|
|
|
598
|
-
// directoryName [4] EXPLICIT Name (a CHOICE, hence EXPLICIT) <-> the C509 compact Name. Reuses the file's
|
|
599
|
-
// Name codecs; a Name the compact form cannot hold (a multi-value RDN, an attribute outside sec. 8.6)
|
|
600
|
-
// returns null so the whole extension falls back to ~oid + byte-string.
|
|
601
421
|
function _dirNameToCbor(gnBytes) {
|
|
602
|
-
var node = asn1.decode(gnBytes);
|
|
422
|
+
var node = asn1.decode(gnBytes);
|
|
603
423
|
if (!node.children || node.children.length !== 1) return null;
|
|
604
424
|
var name;
|
|
605
|
-
try { name = _c509NameFromDer(node.children[0].bytes); } catch (_e) { return null; }
|
|
606
|
-
try { return _encName(name, true); } catch (_e2) { return null; }
|
|
425
|
+
try { name = _c509NameFromDer(node.children[0].bytes); } catch (_e) { return null; } // allow:swallow-unverified -- a malformed directoryName yields null, so the extension is carried in C509 general form instead
|
|
426
|
+
try { return _encName(name, true); } catch (_e2) { return null; } // allow:swallow-unverified -- a directoryName that does not re-encode yields null, so the extension is carried in C509 general form instead
|
|
607
427
|
}
|
|
608
428
|
|
|
609
|
-
// iPAddress [7] (SAN form): a bare 4-octet (IPv4) or 16-octet (IPv6) address, CBOR bytes <-> DER content.
|
|
610
429
|
function _sanIpBytes(node) {
|
|
611
430
|
if (node.majorType !== 2) throw _err("c509/bad-extensions", "an iPAddress value must be a CBOR byte string");
|
|
612
431
|
if (node.content.length !== 4 && node.content.length !== 16) throw _err("c509/bad-extensions", "an iPAddress must be 4 (IPv4) or 16 (IPv6) octets");
|
|
613
432
|
return node.content;
|
|
614
433
|
}
|
|
615
|
-
// iPAddress [7] (name-constraints subtree base): RFC 9549 sec. 2.2. CBOR is address || prefix-length (5 or
|
|
616
|
-
// 17 octets, the last octet the prefix bit count); DER is address || mask (8 or 32 octets). CBOR -> DER
|
|
617
|
-
// expands the prefix length to a contiguous mask.
|
|
618
434
|
function _ncIpToDer(node) {
|
|
619
435
|
if (node.majorType !== 2) throw _err("c509/bad-extensions", "a name-constraints iPAddress value must be a CBOR byte string");
|
|
620
436
|
var buf = node.content, addrLen;
|
|
@@ -626,9 +442,6 @@ function _ncIpToDer(node) {
|
|
|
626
442
|
for (var bit = 0; bit < prefixLen; bit++) mask[bit >> 3] |= 0x80 >> (bit & 7);
|
|
627
443
|
return Buffer.concat([buf.subarray(0, addrLen), mask]);
|
|
628
444
|
}
|
|
629
|
-
// DER -> CBOR: address || mask (8/32) -> address || prefix-length (5/17). The mask MUST be a canonical
|
|
630
|
-
// contiguous prefix mask (N leading 1-bits then all 0-bits); a non-prefix mask (host bits set,
|
|
631
|
-
// non-contiguous) is not representable in the prefix form -> null (the extension falls back, losing no bits).
|
|
632
445
|
function _ncIpFromDer(buf) {
|
|
633
446
|
var addrLen = buf.length >> 1, mask = buf.subarray(addrLen), prefixLen = 0, seenZero = false;
|
|
634
447
|
for (var i = 0; i < mask.length; i++) {
|
|
@@ -640,41 +453,14 @@ function _ncIpFromDer(buf) {
|
|
|
640
453
|
return Buffer.concat([buf.subarray(0, addrLen), Buffer.from([prefixLen])]);
|
|
641
454
|
}
|
|
642
455
|
|
|
643
|
-
|
|
644
|
-
//
|
|
645
|
-
// An RFC 3779 IPAddress is a BIT STRING whose unused-bit count carries the prefix length, so the
|
|
646
|
-
// draft maps it to the byte sequence `unusedBits || value`, which "preserves the exact information
|
|
647
|
-
// contained in the ASN.1 BIT STRING", which stays lossless even for a prefix ending in zero bits. Per
|
|
648
|
-
// IPAddressFamily the draft then picks one of two forms and makes the choice a SHALL: if any of the
|
|
649
|
-
// family's byte sequences exceeds 8 octets the whole family uses the bytes form, otherwise the int
|
|
650
|
-
// form. Accepting the wrong one would give one DER two CBOR encodings, so decode enforces it.
|
|
651
|
-
//
|
|
652
|
-
// The int form is the big-endian integer of `(unusedBits + 1) || value`. The +1 guarantees a
|
|
653
|
-
// non-zero leading octet, which is what makes the minimal big-endian representation unambiguous,
|
|
654
|
-
// and every IPAddress after the first is stored as the DIFFERENCE from its predecessor. The chain
|
|
655
|
-
// runs flat over each address in order (a range contributes min then max) and RESETS at each
|
|
656
|
-
// family, so a family always opens with an absolute value.
|
|
657
|
-
//
|
|
658
|
-
// These integers reach 2^64-1: an IPv6 /48 already exceeds 2^53, and the draft's own A.5 vector
|
|
659
|
-
// lands there. Everything below is BigInt end to end and bounds through guard.range.uint64 (the
|
|
660
|
-
// BigInt-preserving guard). The uint31 counter bound is never used here; it would reject a valid prefix.
|
|
661
|
-
|
|
662
|
-
// An asn1 node exposes tagClass + tagNumber (never a combined `.tag`), so a universal-type test
|
|
663
|
-
// has to check both: a context-tagged [16] must not read as a SEQUENCE.
|
|
456
|
+
|
|
664
457
|
function _isUniversal(n, tagNumber) { return !!n && n.tagClass === "universal" && n.tagNumber === tagNumber; }
|
|
665
458
|
|
|
666
|
-
// `unusedBits || value` -> the (unusedBits+1)||value integer. Returns null if the sequence cannot
|
|
667
|
-
// be one (empty, or an unused-bit count DER would not accept).
|
|
668
459
|
function _ipSeqToInt(seq) {
|
|
669
460
|
if (!seq.length || seq[0] > 7) return null;
|
|
670
|
-
// One-shot base-256 parse of (unusedBits+1)||value. A per-byte shift-accumulate is quadratic in
|
|
671
|
-
// the operand width, so the toolkit builds a BigInt from its hex in a single call instead.
|
|
672
461
|
var head = Buffer.from([seq[0] + 1]);
|
|
673
462
|
return BigInt("0x" + Buffer.concat([head, seq.subarray(1)]).toString("hex"));
|
|
674
463
|
}
|
|
675
|
-
// The inverse: the minimal big-endian octets of `n`, with the leading octet decremented back to the
|
|
676
|
-
// unused-bit count. Returns null when `n` cannot be an encoded IPAddress: a leading octet outside
|
|
677
|
-
// 1..8 is not a DER unused-bit count, and past 8 octets the family was required to use the bytes form.
|
|
678
464
|
function _ipIntToSeq(n) {
|
|
679
465
|
if (n < 1n) return null;
|
|
680
466
|
var out = [];
|
|
@@ -683,28 +469,19 @@ function _ipIntToSeq(n) {
|
|
|
683
469
|
out[0] -= 1;
|
|
684
470
|
return Buffer.from(out);
|
|
685
471
|
}
|
|
686
|
-
// The address width in octets of an address family, from its AFI (RFC 3779 sec. 2.2.3.3). Only a
|
|
687
|
-
// family whose width is known can have its canonical form checked, so an unrecognized AFI yields
|
|
688
|
-
// null and the caller declines to compact; it will not compact something it cannot verify.
|
|
689
472
|
var _IP_WIDTH = { 1: 4, 2: 16 };
|
|
690
473
|
|
|
691
|
-
// The lowest address an `unusedBits || value` sequence denotes, as `width` big-endian octets. DER
|
|
692
|
-
// forces the unused bits to zero, so the value IS its own lowest address once zero-extended.
|
|
693
474
|
function _ipLow(seq, width) {
|
|
694
475
|
var v = Buffer.alloc(width);
|
|
695
476
|
seq.subarray(1).copy(v);
|
|
696
477
|
return v;
|
|
697
478
|
}
|
|
698
|
-
// The highest address it denotes: the same prefix with every host bit set (RFC 3779 sec. 2.2.3.8 --
|
|
699
|
-
// a range's max is likewise the prefix with its trailing bits taken as ones).
|
|
700
479
|
function _ipHigh(seq, width) {
|
|
701
480
|
var v = _ipLow(seq, width);
|
|
702
481
|
var bits = (seq.length - 1) * 8 - seq[0];
|
|
703
482
|
for (var i = bits; i < width * 8; i++) v[i >> 3] |= 0x80 >> (i & 7);
|
|
704
483
|
return v;
|
|
705
484
|
}
|
|
706
|
-
// Big-endian octet-string compare, and "is `b` the immediate successor of `a`": the test that
|
|
707
|
-
// distinguishes a legal gap from a contiguous pair the RFC requires be merged.
|
|
708
485
|
function _ipOctCmp(a, b) {
|
|
709
486
|
for (var i = 0; i < a.length; i++) { if (a[i] !== b[i]) return a[i] - b[i]; }
|
|
710
487
|
return 0;
|
|
@@ -712,15 +489,10 @@ function _ipOctCmp(a, b) {
|
|
|
712
489
|
function _ipIsSuccessor(a, b) {
|
|
713
490
|
var carry = 1, inc = Buffer.from(a);
|
|
714
491
|
for (var i = inc.length - 1; i >= 0 && carry; i--) { var s = inc[i] + carry; inc[i] = s & 0xff; carry = s >> 8; }
|
|
715
|
-
if (carry) return false;
|
|
492
|
+
if (carry) return false;
|
|
716
493
|
return _ipOctCmp(inc, b) === 0;
|
|
717
494
|
}
|
|
718
495
|
|
|
719
|
-
// RFC 3779 sec. 2.2.3.7: "any range of addresses that can be encoded as a prefix MUST be encoded
|
|
720
|
-
// using an IPAddress element", with the choice fixed by the spec's own pseudocode. Let N be the
|
|
721
|
-
// count of matching leading bits of the low and high addresses; if every remaining bit of the low
|
|
722
|
-
// is zero AND every remaining bit of the high is one, the span IS the N-bit prefix and the range
|
|
723
|
-
// form is forbidden. Two encodings of one address span would otherwise both be legal.
|
|
724
496
|
function _ipRangeIsPrefix(lo, hi) {
|
|
725
497
|
var bits = lo.length * 8, n = 0;
|
|
726
498
|
while (n < bits) {
|
|
@@ -730,29 +502,18 @@ function _ipRangeIsPrefix(lo, hi) {
|
|
|
730
502
|
}
|
|
731
503
|
for (var i = n; i < bits; i++) {
|
|
732
504
|
var bt = i >> 3, mk = 0x80 >> (i & 7);
|
|
733
|
-
if ((lo[bt] & mk) !== 0) return false;
|
|
734
|
-
if ((hi[bt] & mk) === 0) return false;
|
|
505
|
+
if ((lo[bt] & mk) !== 0) return false;
|
|
506
|
+
if ((hi[bt] & mk) === 0) return false;
|
|
735
507
|
}
|
|
736
508
|
return true;
|
|
737
509
|
}
|
|
738
510
|
|
|
739
|
-
// RFC 3779 sec. 2.2.3.3 orders the families themselves: "There MUST be only one IPAddressFamily
|
|
740
|
-
// SEQUENCE per unique combination of AFI and SAFI. Each SEQUENCE MUST be ordered by ascending
|
|
741
|
-
// addressFamily values (treating the octets as unsigned quantities). An addressFamily without a
|
|
742
|
-
// SAFI MUST precede one that contains an SAFI." A plain unsigned octet-string compare gives all
|
|
743
|
-
// three at once, because a two-octet family is a PREFIX of the three-octet one sharing its AFI and
|
|
744
|
-
// a prefix sorts first. Returns < 0, 0 or > 0; 0 means the same AFI/SAFI appeared twice.
|
|
745
511
|
function _famOctCmp(a, b) {
|
|
746
512
|
var n = Math.min(a.length, b.length);
|
|
747
513
|
for (var i = 0; i < n; i++) { if (a[i] !== b[i]) return a[i] - b[i]; }
|
|
748
514
|
return a.length - b.length;
|
|
749
515
|
}
|
|
750
516
|
|
|
751
|
-
// Is `unusedBits || value` a sequence DER would accept as a BIT STRING? The declared unused low
|
|
752
|
-
// bits MUST be zero (RFC 3779 sec. 2.2.3.8 restates the DER rule), and a value with no octets can
|
|
753
|
-
// only declare zero unused bits. The check lives here, not in the BIT STRING builder: the builder's
|
|
754
|
-
// fault is an asn1/* error surfacing out of a CBOR-layer decode, and a caller handed a malformed
|
|
755
|
-
// compact value should see this module's own verdict, not the ASN.1 layer's.
|
|
756
517
|
function _ipSeqBitsClear(seq) {
|
|
757
518
|
var unused = seq[0], value = seq.subarray(1);
|
|
758
519
|
if (!value.length) return unused === 0;
|
|
@@ -760,50 +521,23 @@ function _ipSeqBitsClear(seq) {
|
|
|
760
521
|
return (value[value.length - 1] & ((1 << unused) - 1)) === 0;
|
|
761
522
|
}
|
|
762
523
|
|
|
763
|
-
// RFC 3779 sec. 2.2.3.6 fixes the canonical form of an address list: entries sorted on
|
|
764
|
-
// `<lowest address> | <prefix length>` (which is neither the DER byte order nor the compact integer
|
|
765
|
-
// order; the RFC warns about the first and the second sorts the same wrong way), no pair
|
|
766
|
-
// overlapping, and any contiguous pair combined into one entry. All three bind together: a list
|
|
767
|
-
// violating any of them is not the one canonical encoding of its address set, so this codec
|
|
768
|
-
// declines to compact it and the extension keeps its original bytes.
|
|
769
|
-
// Two comparisons carry all three rules. `cur.lo > prev.hi` is the no-overlap rule AND the sort
|
|
770
|
-
// rule at once: entries are already known to have lo <= hi, so an entry that started at or before
|
|
771
|
-
// its predecessor's low address would also start at or before its high one. A separate ascending
|
|
772
|
-
// test would therefore be a branch nothing can reach.
|
|
773
524
|
function _ipRangesCanonical(bounds) {
|
|
774
525
|
for (var i = 1; i < bounds.length; i++) {
|
|
775
526
|
var prev = bounds[i - 1], cur = bounds[i];
|
|
776
|
-
if (_ipOctCmp(cur.lo, prev.hi) <= 0) return false;
|
|
777
|
-
if (_ipIsSuccessor(prev.hi, cur.lo)) return false;
|
|
527
|
+
if (_ipOctCmp(cur.lo, prev.hi) <= 0) return false;
|
|
528
|
+
if (_ipIsSuccessor(prev.hi, cur.lo)) return false;
|
|
778
529
|
}
|
|
779
530
|
return true;
|
|
780
531
|
}
|
|
781
532
|
|
|
782
|
-
// One family's IntIPAddressChoice / IPAddressChoice -> the DER IPAddressOrRange SEQUENCE list.
|
|
783
|
-
// The int form stores each address after the first as a DIFFERENCE from its predecessor, flat over
|
|
784
|
-
// every address in order (a range contributes min then max); the chain resets at each family, which
|
|
785
|
-
// is why `prev` starts null here and is never threaded across families. The reconstructed
|
|
786
|
-
// ABSOLUTE is what gets bounded: the delta itself is an arbitrary CBOR int, and bounding it would
|
|
787
|
-
// miss a chain that walks out of range in steps.
|
|
788
533
|
function _ipChoiceToDer(items, afi) {
|
|
789
534
|
var out = [], prev = null, sawBytes = false, sawInt = false, seqs = [];
|
|
790
|
-
// An address may not be wider than its family: RFC 3779 sec. 2.2.3.8 sizes an IPAddress by the
|
|
791
|
-
// family, so a 5-octet address under AFI 1 is not an IPv4 address at all. Without this, a native
|
|
792
|
-
// C509 this codec had accepted would reconstruct into a DER carrying an over-wide address that the
|
|
793
|
-
// RFC forbids and an independent validator refuses. The mirror check lives on the encode side;
|
|
794
|
-
// both directions have to hold or the pair is not a bijection.
|
|
795
|
-
// Without the family's address width none of the RFC 3779 rules below can be evaluated: the
|
|
796
|
-
// width bound has nothing to compare against, and the low/high bounds that drive the order,
|
|
797
|
-
// overlap, adjacency and endpoint checks cannot be computed at all. Accepting such a family
|
|
798
|
-
// would therefore wave every one of those checks through. A family this codec cannot
|
|
799
|
-
// measure is refused outright, matching the encode side, which declines to compact it. An
|
|
800
|
-
// `inherit` family is unaffected: it carries no addresses and never reaches here.
|
|
801
535
|
var width = _IP_WIDTH[afi];
|
|
802
536
|
if (!width) throw _err("c509/bad-extensions", "address family " + afi + " has no known address width, so its addresses cannot be checked (RFC 3779 sec. 2.2.3.3)");
|
|
803
537
|
function widthOk(seq) { return seq.length - 1 <= width; }
|
|
804
538
|
function absolute(node) {
|
|
805
539
|
var seq;
|
|
806
|
-
if (node.majorType === 2) {
|
|
540
|
+
if (node.majorType === 2) {
|
|
807
541
|
sawBytes = true;
|
|
808
542
|
if (node.content.length === 0) throw _err("c509/bad-extensions", "an IPAddress byte sequence must be non-empty");
|
|
809
543
|
if (node.content[0] > 7) throw _err("c509/bad-extensions", "an IPAddress unused-bit count must be 0..7 (DER)");
|
|
@@ -813,8 +547,6 @@ function _ipChoiceToDer(items, afi) {
|
|
|
813
547
|
var d = _cborIntVal(node, "an IPAddress");
|
|
814
548
|
var abs = prev === null ? d : prev + d;
|
|
815
549
|
prev = abs;
|
|
816
|
-
// Bound the reconstructed absolute through the BigInt-preserving guard: this domain reaches
|
|
817
|
-
// 2^64-1, so narrowing to Number would corrupt the value being guarded.
|
|
818
550
|
guard.range.uint64(abs, _err, "c509/bad-extensions", "an IPAddress");
|
|
819
551
|
seq = _ipIntToSeq(abs);
|
|
820
552
|
if (!seq) throw _err("c509/bad-extensions", "an IPAddress integer does not encode a DER BIT STRING (sec. 3.3)");
|
|
@@ -830,37 +562,27 @@ function _ipChoiceToDer(items, afi) {
|
|
|
830
562
|
var bounds = [];
|
|
831
563
|
for (var i = 0; i < items.length; i++) {
|
|
832
564
|
var it = items[i];
|
|
833
|
-
if (it.majorType === 4) {
|
|
565
|
+
if (it.majorType === 4) {
|
|
834
566
|
if (!it.children || it.children.length !== 2) throw _err("c509/bad-extensions", "an IPAddress range must be exactly [min, max]");
|
|
835
567
|
var lo = absolute(it.children[0]), hi = absolute(it.children[1]);
|
|
836
568
|
seqs.push(lo); seqs.push(hi);
|
|
837
569
|
var rlo = _ipLow(lo, width), rhi = _ipHigh(hi, width);
|
|
838
570
|
{
|
|
839
|
-
// A span expressible as a prefix MUST use the prefix form (RFC 3779 sec. 2.2.3.7), or one
|
|
840
|
-
// address span would have two legal encodings.
|
|
841
571
|
if (_ipRangeIsPrefix(rlo, rhi)) throw _err("c509/bad-extensions", "an address range that is exactly a prefix must use the prefix form (RFC 3779 sec. 2.2.3.7)");
|
|
842
572
|
bounds.push({ lo: rlo, hi: rhi });
|
|
843
573
|
}
|
|
844
574
|
out.push(b.sequence([b.bitString(lo.subarray(1), lo[0]), b.bitString(hi.subarray(1), hi[0])]));
|
|
845
|
-
} else {
|
|
575
|
+
} else {
|
|
846
576
|
var pfx = absolute(it);
|
|
847
577
|
seqs.push(pfx);
|
|
848
578
|
bounds.push({ lo: _ipLow(pfx, width), hi: _ipHigh(pfx, width) });
|
|
849
579
|
out.push(b.bitString(pfx.subarray(1), pfx[0]));
|
|
850
580
|
}
|
|
851
581
|
}
|
|
852
|
-
// The form choice is a SHALL, so the wrong one would give one DER two CBOR encodings. A family
|
|
853
|
-
// mixing the two arms is not a choice either admits; a bytes-form family whose every sequence
|
|
854
|
-
// fits 8 octets was required to use the int form.
|
|
855
582
|
if (sawBytes && sawInt) throw _err("c509/bad-extensions", "an IPAddressFamily must use one address form throughout (sec. 3.3)");
|
|
856
583
|
if (sawBytes && !seqs.some(function (s) { return s.length > 8; })) {
|
|
857
584
|
throw _err("c509/bad-extensions", "an IPAddressFamily whose addresses all fit 8 octets must use the integer form (sec. 3.3)");
|
|
858
585
|
}
|
|
859
|
-
// The same RFC 3779 sec. 2.2.3.6 canonical form the encode side requires, enforced here too.
|
|
860
|
-
// draft sec. 3.3 says "The limitations specified in [RFC3779] apply here as well", so a compact
|
|
861
|
-
// list that is unsorted, overlapping, or unmerged is malformed C509, and reconstructing it
|
|
862
|
-
// would emit a certificate an independent validator refuses, from CBOR this codec had accepted.
|
|
863
|
-
// Both directions must hold or the pair is not a bijection.
|
|
864
586
|
for (var r = 0; r < bounds.length; r++) {
|
|
865
587
|
if (_ipOctCmp(bounds[r].lo, bounds[r].hi) > 0) throw _err("c509/bad-extensions", "an IPAddress range must not end below its start (RFC 3779 sec. 2.2.3.9)");
|
|
866
588
|
}
|
|
@@ -870,13 +592,8 @@ function _ipChoiceToDer(items, afi) {
|
|
|
870
592
|
return out;
|
|
871
593
|
}
|
|
872
594
|
|
|
873
|
-
// DER IPAddressChoice (a SEQUENCE of BIT STRING / SEQUENCE-of-two-BIT-STRING) -> the compact CBOR
|
|
874
|
-
// array. The form is chosen per family and is a SHALL: the bytes form applies to the whole family
|
|
875
|
-
// as soon as any one sequence exceeds 8 octets, otherwise every member takes the delta-coded int
|
|
876
|
-
// form. Returns null on any shape the compact form cannot carry exactly.
|
|
877
595
|
function _ipChoiceFromDer(ch, afi) {
|
|
878
596
|
if (!_isUniversal(ch, asn1.TAGS.SEQUENCE) || !ch.children || ch.children.length === 0) return null;
|
|
879
|
-
// Collect each address as its `unusedBits || value` sequence, keeping the range grouping.
|
|
880
597
|
var groups = [], flat = [];
|
|
881
598
|
for (var i = 0; i < ch.children.length; i++) {
|
|
882
599
|
var el = ch.children[i], pair;
|
|
@@ -892,32 +609,16 @@ function _ipChoiceFromDer(ch, afi) {
|
|
|
892
609
|
groups.push(pair);
|
|
893
610
|
for (var k = 0; k < pair.length; k++) flat.push(pair[k]);
|
|
894
611
|
}
|
|
895
|
-
// The list must be in RFC 3779 sec. 2.2.3.6 canonical form: sorted, non-overlapping, and with
|
|
896
|
-
// every contiguous pair already merged. A list that is not is not the one canonical encoding of
|
|
897
|
-
// its address set, so it is never re-encoded into a conforming one. This returns null and the
|
|
898
|
-
// extension rides the byte-string form, keeping its exact bytes and leaving the defect visible to
|
|
899
|
-
// a validator. Checking overlap and adjacency needs the family's address width, so a family whose
|
|
900
|
-
// AFI this codec does not know declines too, since there is no way to verify what it would compact.
|
|
901
|
-
// Defense-in-depth, and deliberately explicit: without the width the bound arithmetic below would
|
|
902
|
-
// fault and the caller's catch would produce the same fallback, so this line changes no observable
|
|
903
|
-
// verdict and no vector can isolate it. It stays because a fallback that depends on an exception
|
|
904
|
-
// being raised somewhere downstream is one refactor away from becoming an accepted value.
|
|
905
612
|
var width = _IP_WIDTH[afi];
|
|
906
613
|
if (!width) return null;
|
|
907
614
|
var bounds = [];
|
|
908
615
|
for (var g = 0; g < groups.length; g++) {
|
|
909
616
|
var bg = groups[g];
|
|
910
|
-
if (bg[0].length - 1 > width || bg[bg.length - 1].length - 1 > width) return null;
|
|
617
|
+
if (bg[0].length - 1 > width || bg[bg.length - 1].length - 1 > width) return null;
|
|
911
618
|
var blo = _ipLow(bg[0], width), bhi = _ipHigh(bg[bg.length - 1], width);
|
|
912
|
-
// The mirror of the decode-side rule: a range that is exactly a prefix had to be written as one.
|
|
913
|
-
// No vector can isolate this line today, because the encode path's round-trip self-verify
|
|
914
|
-
// re-parses what it produced and the decode rule rejects it there, yielding the same fallback.
|
|
915
|
-
// It stays explicit because that makes encode's correctness depend on decode continuing to
|
|
916
|
-
// throw: soften the decode rule and this path would silently start compacting again.
|
|
917
619
|
if (bg.length === 2 && _ipRangeIsPrefix(blo, bhi)) return null;
|
|
918
620
|
bounds.push({ lo: blo, hi: bhi });
|
|
919
621
|
}
|
|
920
|
-
// A range's own endpoints must ascend too, which a single-entry list has no chance to violate.
|
|
921
622
|
for (var r = 0; r < bounds.length; r++) {
|
|
922
623
|
if (_ipOctCmp(bounds[r].lo, bounds[r].hi) > 0) return null;
|
|
923
624
|
}
|
|
@@ -955,20 +656,13 @@ function _ipChoiceFromDer(ch, afi) {
|
|
|
955
656
|
return cbor.build.array(out);
|
|
956
657
|
}
|
|
957
658
|
|
|
958
|
-
// One ASId in the delta chain -> its absolute value, bounded to the RFC 3779 32-bit ASId domain.
|
|
959
|
-
// Safe to narrow here (2^32-1 < 2^53), unlike the IP domain.
|
|
960
659
|
function _asDelta(node, prev) {
|
|
961
660
|
var d = _cborIntVal(node, "an ASIdentifier");
|
|
962
661
|
var abs = prev === null ? d : prev + d;
|
|
963
|
-
// Bound only -- the narrowed Number the guard returns is deliberately discarded, because the
|
|
964
|
-
// chain's next step adds a BigInt delta to this value and mixing the two throws.
|
|
965
662
|
guard.range.int(abs, 0n, 4294967295n, _err, "c509/bad-extensions", "an ASIdentifier");
|
|
966
663
|
return abs;
|
|
967
664
|
}
|
|
968
665
|
|
|
969
|
-
// GeneralSubtrees = [ + GeneralName ] (the flat int/value array) <-> the concatenated GeneralSubtree
|
|
970
|
-
// SEQUENCEs (RFC 5280 sec. 4.2.1.10: SEQUENCE { base, minimum [0] DEFAULT 0, maximum [1] OPTIONAL }); the
|
|
971
|
-
// C509 profile omits minimum/maximum, so each GeneralSubtree is base-only.
|
|
972
666
|
function _subtreesToDer(node, isNative) {
|
|
973
667
|
if (node.majorType !== 4 || !node.children) throw _err("c509/bad-extensions", "a GeneralSubtrees value must be a CBOR array");
|
|
974
668
|
var kids = node.children;
|
|
@@ -982,7 +676,7 @@ function _subtreesFromDer(subtreeNodes) {
|
|
|
982
676
|
var out = [];
|
|
983
677
|
for (var i = 0; i < subtreeNodes.length; i++) {
|
|
984
678
|
var st = subtreeNodes[i];
|
|
985
|
-
if (st.tagClass !== "universal" || st.tagNumber !== asn1.TAGS.SEQUENCE || !st.children || st.children.length !== 1) return null;
|
|
679
|
+
if (st.tagClass !== "universal" || st.tagNumber !== asn1.TAGS.SEQUENCE || !st.children || st.children.length !== 1) return null;
|
|
986
680
|
var pair = _generalNameFromDer(schema.walk(GN_LEAF_SUBTREE, st.children[0], NS), true);
|
|
987
681
|
if (pair == null) return null;
|
|
988
682
|
out.push(pair[0]); out.push(pair[1]);
|
|
@@ -990,7 +684,6 @@ function _subtreesFromDer(subtreeNodes) {
|
|
|
990
684
|
return out;
|
|
991
685
|
}
|
|
992
686
|
|
|
993
|
-
// A CBOR accessMethod (int, sec. 8.11 registry, or ~oid) -> the dotted access-method OID.
|
|
994
687
|
function _accessMethodOid(node) {
|
|
995
688
|
if (node.majorType === 0 || node.majorType === 1) {
|
|
996
689
|
var i = Number(cbor.read.int(node)), nm = IA_BY_INT[i];
|
|
@@ -999,37 +692,24 @@ function _accessMethodOid(node) {
|
|
|
999
692
|
}
|
|
1000
693
|
return _oidName(node, "c509/bad-extensions", "an accessMethod").oid;
|
|
1001
694
|
}
|
|
1002
|
-
// A reasons uint (bit i -> named ReasonFlags bit i) -> the [1] IMPLICIT ReasonFlags BIT STRING TLV.
|
|
1003
695
|
function _reasonsBitsToDer(value) {
|
|
1004
|
-
// ReasonFlags bit 0 is reserved "unused" (RFC 5280 sec. 4.2.1.13; the reason bits are 1..8), so a value
|
|
1005
|
-
// that sets it is not a valid ReasonFlags even though the shared 9-bit range check would accept it. Bit 0
|
|
1006
|
-
// is a legitimate keyUsage bit (digitalSignature), so this reason-specific reject lives here, not in the
|
|
1007
|
-
// shared named-bit helper. A malformed native C509 reasons value fails closed; it never reconstructs
|
|
1008
|
-
// into a semantically invalid X.509 ReasonFlags.
|
|
1009
696
|
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)");
|
|
1010
697
|
var c = _namedBitsToContent(value);
|
|
1011
698
|
if (c == null) throw _err("c509/bad-extensions", "a cRLDistributionPoints reasons value must be within the 9 defined ReasonFlags bits");
|
|
1012
699
|
return b.contextPrimitive(1, Buffer.concat([Buffer.from([c.unusedBits]), c.bytes]));
|
|
1013
700
|
}
|
|
1014
|
-
// A CBOR ~biguint serial node -> the DER INTEGER content octets (minimal two's-complement of the
|
|
1015
|
-
// non-negative serial): the bare magnitude, prefixed with a 0x00 sign octet when its top bit is set.
|
|
1016
701
|
function _serialIntContent(node) {
|
|
1017
702
|
var mag = _minBytes(_biguint(node, "c509/bad-extensions", "an authorityKeyIdentifier serial"));
|
|
1018
|
-
if (mag.length === 0) return Buffer.from([0x00]);
|
|
703
|
+
if (mag.length === 0) return Buffer.from([0x00]);
|
|
1019
704
|
return (mag[0] & 0x80) ? Buffer.concat([Buffer.from([0x00]), mag]) : mag;
|
|
1020
705
|
}
|
|
1021
706
|
|
|
1022
|
-
// ---- certificatePolicies (draft-20 sec. 3.3 / sec. 8.9 / sec. 8.10; RFC 5280 sec. 4.2.1.4) ----
|
|
1023
|
-
// A CBOR text -> a UNIVERSAL IA5String TLV, running the same 7-bit + CVE-2009-2408 control-byte guard the
|
|
1024
|
-
// general-name IA5 arms use (never the bare b.ia5, which permits control bytes). Non-empty (the general-name
|
|
1025
|
-
// IA5 arms reject empty too; this is a CPSuri parity tightening, since RFC 5280 sets no SIZE floor on CPSuri).
|
|
1026
707
|
function _ia5Universal(text, label) {
|
|
1027
708
|
if (text.length === 0) throw _err("c509/bad-extensions", label + " must be non-empty");
|
|
1028
709
|
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"); }
|
|
1029
710
|
guard.name.assertPrintableIa5(Buffer.from(text, "latin1"), _err, "c509/bad-extensions", label);
|
|
1030
711
|
return b.ia5(text);
|
|
1031
712
|
}
|
|
1032
|
-
// A CBOR policyIdentifier (a sec. 8.9 registry int, or an unwrapped ~oid) -> the dotted policy OID.
|
|
1033
713
|
function _policyIdToDerOid(node) {
|
|
1034
714
|
if (node.majorType === 0 || node.majorType === 1) {
|
|
1035
715
|
var i = Number(cbor.read.int(node)), nm = CP_BY_INT[i];
|
|
@@ -1038,54 +718,36 @@ function _policyIdToDerOid(node) {
|
|
|
1038
718
|
}
|
|
1039
719
|
return _oidName(node, "c509/bad-extensions", "a certificatePolicies policyIdentifier").oid;
|
|
1040
720
|
}
|
|
1041
|
-
// The inverse: a dotted policy OID -> CBOR (a sec. 8.9 int if registered, else an unwrapped ~oid).
|
|
1042
721
|
function _policyIdFromDer(dotted) {
|
|
1043
722
|
var i = CP_TO_INT[dotted];
|
|
1044
723
|
return i !== undefined ? cbor.build.int(BigInt(i)) : _oidCbor(dotted);
|
|
1045
724
|
}
|
|
1046
|
-
// (qualifierId CBOR, qualifier-text CBOR) -> one DER PolicyQualifierInfo SEQUENCE { policyQualifierId OID,
|
|
1047
|
-
// qualifier ANY DEFINED BY }. The qualifierId resolves to a sec. 8.10 int and nothing else: a ~oid / unregistered
|
|
1048
|
-
// qualifierId has no defined qualifier:text semantics, so it is not compact-representable (fail closed, never
|
|
1049
|
-
// guess the ANY type). id-qt-cps -> CPSuri IA5String; id-qt-unotice -> UserNotice { explicitText utf8String }.
|
|
1050
725
|
function _qualifierToDer(qidNode, qtextNode) {
|
|
1051
726
|
if (qidNode.majorType !== 0 && qidNode.majorType !== 1) throw _err("c509/bad-extensions", "a policyQualifierId must be a C509 sec. 8.10 int (a ~oid qualifier is not compact-representable)");
|
|
1052
727
|
var qi = Number(cbor.read.int(qidNode)), nm = PQ_BY_INT[qi];
|
|
1053
728
|
if (nm === undefined) throw _err("c509/bad-extensions", "a policyQualifierId int " + qi + " has no C509 sec. 8.10 registry row");
|
|
1054
729
|
if (qtextNode.majorType !== 3) throw _err("c509/bad-extensions", "a policyQualifier value must be a CBOR text string");
|
|
1055
730
|
var text = cbor.read.textString(qtextNode);
|
|
1056
|
-
if (qi === 1) return b.sequence([b.oid(oid.byName("cps")), _ia5Universal(text, "a CPSuri")]);
|
|
1057
|
-
// id-qt-unotice: UserNotice ::= SEQUENCE { explicitText utf8String }, noticeRef omitted. Only the SIZE
|
|
1058
|
-
// (1..200) FLOOR is enforced: an empty explicitText is a degenerate value no encoder can have produced,
|
|
1059
|
-
// while RFC 5280 sec. 4.2.1.4 directs certificate users to gracefully handle a notice ABOVE 200
|
|
1060
|
-
// characters, and draft-20 sec. 3.3's compact predicate is SIZE-silent. An over-long notice
|
|
1061
|
-
// therefore transcodes and is not refused.
|
|
731
|
+
if (qi === 1) return b.sequence([b.oid(oid.byName("cps")), _ia5Universal(text, "a CPSuri")]);
|
|
1062
732
|
if (text.length === 0) throw _err("c509/bad-extensions", "a UserNotice explicitText must be non-empty (DisplayText SIZE 1..200)");
|
|
1063
733
|
return b.sequence([b.oid(oid.byName("unotice")), b.sequence([b.utf8(text)])]);
|
|
1064
734
|
}
|
|
1065
|
-
// One DER PolicyQualifierInfo node -> [ qidCbor, qtextCbor ], or null when not compact-representable: an
|
|
1066
|
-
// unregistered qualifierId, a cps qualifier whose value is not a plain IA5String, or a UserNotice that is
|
|
1067
|
-
// anything but SEQUENCE { explicitText utf8String } (a noticeRef, a non-UTF8String DisplayText arm, or more
|
|
1068
|
-
// than the one explicitText child) -> whole-extension ~oid fallback.
|
|
1069
735
|
function _qualifierFromDer(pq) {
|
|
1070
736
|
var qid = asn1.read.oid(pq.children[0]), qi = PQ_TO_INT[qid];
|
|
1071
737
|
if (qi === undefined) return null;
|
|
1072
738
|
var q = pq.children[1];
|
|
1073
|
-
if (qi === 1) {
|
|
739
|
+
if (qi === 1) {
|
|
1074
740
|
if (q.tagClass !== "universal" || q.tagNumber !== asn1.TAGS.IA5_STRING) return null;
|
|
1075
|
-
var uri; try { uri = asn1.read.string(q); } catch (_e) { return null; }
|
|
741
|
+
var uri; try { uri = asn1.read.string(q); } catch (_e) { return null; } // allow:swallow-unverified -- a malformed URI policy qualifier yields null, so the extension is carried in C509 general form instead
|
|
1076
742
|
return [cbor.build.int(1n), cbor.build.textString(uri)];
|
|
1077
743
|
}
|
|
1078
|
-
// id-qt-unotice: SEQUENCE { explicitText utf8String } and nothing more (noticeRef absent).
|
|
1079
744
|
if (q.tagClass !== "universal" || q.tagNumber !== asn1.TAGS.SEQUENCE || !q.children || q.children.length !== 1) return null;
|
|
1080
745
|
var dt = q.children[0];
|
|
1081
746
|
if (dt.tagClass !== "universal" || dt.tagNumber !== asn1.TAGS.UTF8_STRING) return null;
|
|
1082
|
-
var txt; try { txt = asn1.read.string(dt); } catch (_e2) { return null; }
|
|
747
|
+
var txt; try { txt = asn1.read.string(dt); } catch (_e2) { return null; } // allow:swallow-unverified -- a malformed displayText qualifier yields null, so the extension is carried in C509 general form instead
|
|
1083
748
|
return [cbor.build.int(2n), cbor.build.textString(txt)];
|
|
1084
749
|
}
|
|
1085
750
|
|
|
1086
|
-
// One CBOR DistributionPointName [ fullName, reasons, cRLIssuer ] -> one DER DistributionPoint SEQUENCE.
|
|
1087
|
-
// distributionPoint [0] is EXPLICIT (it wraps the DistributionPointName CHOICE); fullName [0], reasons [1],
|
|
1088
|
-
// cRLIssuer [2] are IMPLICIT (RFC 5280 sec. 4.2.1.13); mixing these is the classic byte-exactness trap.
|
|
1089
751
|
function _dpToDer(dpNode, isNative) {
|
|
1090
752
|
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");
|
|
1091
753
|
var fullName = dpNode.children[0], reasons = dpNode.children[1], crlIssuer = dpNode.children[2];
|
|
@@ -1095,45 +757,41 @@ function _dpToDer(dpNode, isNative) {
|
|
|
1095
757
|
if (fullName.children.length < 2) throw _err("c509/bad-extensions", "a DistributionPoint fullName array must hold 2 or more URIs (a single URI is a bare text; sec. 3.3)");
|
|
1096
758
|
uris = fullName.children.map(function (u) { return b.contextPrimitive(6, _ia5Bytes(u, 6)); });
|
|
1097
759
|
} else throw _err("c509/bad-extensions", "a DistributionPoint fullName must be a URI text or an array of URIs");
|
|
1098
|
-
var fields = [b.explicit(0, b.contextConstructed(0, Buffer.concat(uris)))];
|
|
760
|
+
var fields = [b.explicit(0, b.contextConstructed(0, Buffer.concat(uris)))];
|
|
1099
761
|
if (!_isCborNull(reasons)) fields.push(_reasonsBitsToDer(Number(_cborUint(reasons, "cRLDistributionPoints reasons"))));
|
|
1100
|
-
if (!_isCborNull(crlIssuer)) fields.push(b.contextConstructed(2, b.explicit(4, _reconName(_name509(crlIssuer, true, isNative)))));
|
|
762
|
+
if (!_isCborNull(crlIssuer)) fields.push(b.contextConstructed(2, b.explicit(4, _reconName(_name509(crlIssuer, true, isNative)))));
|
|
1101
763
|
return b.sequence(fields);
|
|
1102
764
|
}
|
|
1103
|
-
// One DER DistributionPoint SEQUENCE -> the pieces for the CBOR DistributionPointName, or null when the DP
|
|
1104
|
-
// is not compact-representable (no fullName, a non-URI fullName member, a nameRelativeToCRLIssuer, or a
|
|
1105
|
-
// cRLIssuer that is not a single directoryName). oneUri is set when fullName is exactly one URI, so the
|
|
1106
|
-
// whole-extension bare-text shortcut can fire.
|
|
1107
765
|
function _dpFromDer(dp) {
|
|
1108
766
|
if (dp.tagClass !== "universal" || dp.tagNumber !== asn1.TAGS.SEQUENCE || !dp.children) return null;
|
|
1109
767
|
var fullNameUris = null, reasonsVal = null, crlIssuerCbor = null, lastTag = -1;
|
|
1110
768
|
for (var i = 0; i < dp.children.length; i++) {
|
|
1111
769
|
var f = dp.children[i];
|
|
1112
|
-
if (f.tagClass !== "context" || f.tagNumber <= lastTag) return null;
|
|
770
|
+
if (f.tagClass !== "context" || f.tagNumber <= lastTag) return null;
|
|
1113
771
|
lastTag = f.tagNumber;
|
|
1114
772
|
if (f.tagNumber === 0) {
|
|
1115
773
|
if (!f.children || f.children.length !== 1) return null;
|
|
1116
774
|
var dpn = f.children[0];
|
|
1117
|
-
if (dpn.tagClass !== "context" || dpn.tagNumber !== 0 || !dpn.children || !dpn.children.length) return null;
|
|
775
|
+
if (dpn.tagClass !== "context" || dpn.tagNumber !== 0 || !dpn.children || !dpn.children.length) return null;
|
|
1118
776
|
fullNameUris = [];
|
|
1119
777
|
for (var j = 0; j < dpn.children.length; j++) {
|
|
1120
778
|
var mm = dpn.children[j];
|
|
1121
|
-
if (mm.tagClass !== "context" || mm.tagNumber !== 6) return null;
|
|
779
|
+
if (mm.tagClass !== "context" || mm.tagNumber !== 6) return null;
|
|
1122
780
|
fullNameUris.push(schema.walk(GN_LEAF, mm, NS).value);
|
|
1123
781
|
}
|
|
1124
782
|
} else if (f.tagNumber === 1) {
|
|
1125
783
|
var bs;
|
|
1126
|
-
try { bs = asn1.read.bitStringImplicit(f, 1); } catch (_e) { return null; }
|
|
784
|
+
try { bs = asn1.read.bitStringImplicit(f, 1); } catch (_e) { return null; } // allow:swallow-unverified -- a malformed reason-flags bit string yields null, so the distributionPoint does not compact and is carried in C509 general form
|
|
1127
785
|
reasonsVal = _namedBitsFromContent(bs.bytes, bs.unusedBits);
|
|
1128
786
|
if (reasonsVal == null) return null;
|
|
1129
787
|
} else if (f.tagNumber === 2) {
|
|
1130
788
|
if (!f.children || f.children.length !== 1) return null;
|
|
1131
789
|
var ci = f.children[0];
|
|
1132
|
-
if (ci.tagClass !== "context" || ci.tagNumber !== 4 || !ci.children || ci.children.length !== 1) return null;
|
|
1133
|
-
try { crlIssuerCbor = _encName(_c509NameFromDer(ci.children[0].bytes), true); } catch (_e2) { return null; }
|
|
790
|
+
if (ci.tagClass !== "context" || ci.tagNumber !== 4 || !ci.children || ci.children.length !== 1) return null;
|
|
791
|
+
try { crlIssuerCbor = _encName(_c509NameFromDer(ci.children[0].bytes), true); } catch (_e2) { return null; } // allow:swallow-unverified -- a malformed CRL-issuer name yields null, so the distributionPoint is carried in C509 general form instead
|
|
1134
792
|
} else return null;
|
|
1135
793
|
}
|
|
1136
|
-
if (fullNameUris == null) return null;
|
|
794
|
+
if (fullNameUris == null) return null;
|
|
1137
795
|
var oneUri = fullNameUris.length === 1 ? fullNameUris[0] : null;
|
|
1138
796
|
var fnCbor = oneUri != null ? cbor.build.textString(oneUri) : cbor.build.array(fullNameUris.map(function (u) { return cbor.build.textString(u); }));
|
|
1139
797
|
return {
|
|
@@ -1142,34 +800,32 @@ function _dpFromDer(dp) {
|
|
|
1142
800
|
};
|
|
1143
801
|
}
|
|
1144
802
|
|
|
1145
|
-
// Decode a compact extension value (a decoded CBOR node) to the DER extnValue inner content. Fails closed
|
|
1146
|
-
// (c509/bad-extensions) on a CBOR shape the named extension does not define.
|
|
1147
803
|
function _extValueToDer(name, node, isNative) {
|
|
1148
804
|
switch (name) {
|
|
1149
|
-
case "subjectKeyIdentifier":
|
|
805
|
+
case "subjectKeyIdentifier":
|
|
1150
806
|
if (node.majorType !== 2) throw _err("c509/bad-extensions", "a subjectKeyIdentifier value must be a CBOR byte string");
|
|
1151
807
|
return b.octetString(node.content);
|
|
1152
|
-
case "keyUsage":
|
|
808
|
+
case "keyUsage":
|
|
1153
809
|
return _reconKeyUsageBits(Number(_cborUint(node, "keyUsage")));
|
|
1154
|
-
case "basicConstraints": {
|
|
810
|
+
case "basicConstraints": {
|
|
1155
811
|
var iv = _cborIntVal(node, "basicConstraints");
|
|
1156
|
-
if (iv === -2n) return b.sequence([]);
|
|
1157
|
-
if (iv === -1n) return b.sequence([b.boolean(true)]);
|
|
1158
|
-
if (iv >= 0n) return b.sequence([b.boolean(true), b.integer(_boundCount(iv, "a basicConstraints pathLenConstraint"))]);
|
|
812
|
+
if (iv === -2n) return b.sequence([]);
|
|
813
|
+
if (iv === -1n) return b.sequence([b.boolean(true)]);
|
|
814
|
+
if (iv >= 0n) return b.sequence([b.boolean(true), b.integer(_boundCount(iv, "a basicConstraints pathLenConstraint"))]);
|
|
1159
815
|
throw _err("c509/bad-extensions", "a basicConstraints int " + iv + " is outside the -2/-1/pathLen range");
|
|
1160
816
|
}
|
|
1161
|
-
case "authorityKeyIdentifier":
|
|
817
|
+
case "authorityKeyIdentifier":
|
|
1162
818
|
if (node.majorType === 2) return b.sequence([b.contextPrimitive(0, node.content)]);
|
|
1163
819
|
if (node.majorType === 4 && node.children && node.children.length === 3) {
|
|
1164
820
|
if (node.children[0].majorType !== 2) throw _err("c509/bad-extensions", "an authorityKeyIdentifier keyIdentifier must be a CBOR byte string");
|
|
1165
821
|
return b.sequence([
|
|
1166
|
-
b.contextPrimitive(0, node.children[0].content),
|
|
1167
|
-
b.contextConstructed(1, Buffer.concat(_generalNamesToDer(node.children[1], isNative))),
|
|
1168
|
-
b.contextPrimitive(2, _serialIntContent(node.children[2])),
|
|
822
|
+
b.contextPrimitive(0, node.children[0].content),
|
|
823
|
+
b.contextConstructed(1, Buffer.concat(_generalNamesToDer(node.children[1], isNative))),
|
|
824
|
+
b.contextPrimitive(2, _serialIntContent(node.children[2])),
|
|
1169
825
|
]);
|
|
1170
826
|
}
|
|
1171
827
|
throw _err("c509/bad-extensions", "an authorityKeyIdentifier value must be a keyId byte string or the [ keyId, authorityCertIssuer, serial ] array (sec. 3.3)");
|
|
1172
|
-
case "extKeyUsage": {
|
|
828
|
+
case "extKeyUsage": {
|
|
1173
829
|
var items;
|
|
1174
830
|
if (node.majorType === 4) {
|
|
1175
831
|
items = node.children || [];
|
|
@@ -1179,37 +835,34 @@ function _extValueToDer(name, node, isNative) {
|
|
|
1179
835
|
}
|
|
1180
836
|
return b.sequence(items.map(function (it) { return b.oid(_ekuPurposeOid(it)); }));
|
|
1181
837
|
}
|
|
1182
|
-
case "inhibitAnyPolicy":
|
|
838
|
+
case "inhibitAnyPolicy":
|
|
1183
839
|
return b.integer(_boundCount(_cborUint(node, "inhibitAnyPolicy"), "an inhibitAnyPolicy SkipCerts"));
|
|
1184
|
-
case "ocspNoCheck":
|
|
840
|
+
case "ocspNoCheck":
|
|
1185
841
|
if (node.majorType !== 7 || !(Buffer.isBuffer(node.bytes) && node.bytes.length === 1 && node.bytes[0] === 0xf6)) throw _err("c509/bad-extensions", "an ocspNoCheck value must be the CBOR simple value null");
|
|
1186
842
|
return b.nullValue();
|
|
1187
|
-
case "tlsFeature": {
|
|
843
|
+
case "tlsFeature": {
|
|
1188
844
|
if (node.majorType !== 4) throw _err("c509/bad-extensions", "a tlsFeature value must be a CBOR array");
|
|
1189
845
|
return b.sequence((node.children || []).map(function (f) { return b.integer(_cborUint(f, "tlsFeature feature")); }));
|
|
1190
846
|
}
|
|
1191
847
|
case "subjectAltName":
|
|
1192
|
-
case "issuerAltName":
|
|
848
|
+
case "issuerAltName":
|
|
1193
849
|
if (node.majorType === 3) return b.sequence([b.contextPrimitive(2, _ia5Bytes(node, 2))]);
|
|
1194
|
-
// sec. 3.3: "If subjectAltName contains exactly one dNSName, the array and the int are omitted and
|
|
1195
|
-
// extensionValue is the dNSName encoded as a CBOR text string." The array spelling of that one case
|
|
1196
|
-
// is a second encoding of the same extension value.
|
|
1197
850
|
if (!isNative && node.majorType === 4 && node.children && node.children.length === 2 &&
|
|
1198
851
|
node.children[0].majorType === 0 && Number(cbor.read.int(node.children[0])) === 2 &&
|
|
1199
852
|
node.children[1].majorType === 3) {
|
|
1200
853
|
throw _err("c509/bad-extensions", "a " + name + " holding exactly one dNSName must be encoded as a bare CBOR text string, not an array (draft sec. 3.3)");
|
|
1201
854
|
}
|
|
1202
855
|
return b.sequence(_generalNamesToDer(node, isNative));
|
|
1203
|
-
case "nameConstraints": {
|
|
856
|
+
case "nameConstraints": {
|
|
1204
857
|
if (node.majorType !== 4 || !node.children || node.children.length !== 2) throw _err("c509/bad-extensions", "a nameConstraints value must be a 2-element CBOR array [ permitted, excluded ] (sec. 3.3)");
|
|
1205
858
|
var ncFields = [];
|
|
1206
|
-
if (!_isCborNull(node.children[0])) ncFields.push(b.contextConstructed(0, _subtreesToDer(node.children[0], isNative)));
|
|
1207
|
-
if (!_isCborNull(node.children[1])) ncFields.push(b.contextConstructed(1, _subtreesToDer(node.children[1], isNative)));
|
|
859
|
+
if (!_isCborNull(node.children[0])) ncFields.push(b.contextConstructed(0, _subtreesToDer(node.children[0], isNative)));
|
|
860
|
+
if (!_isCborNull(node.children[1])) ncFields.push(b.contextConstructed(1, _subtreesToDer(node.children[1], isNative)));
|
|
1208
861
|
if (ncFields.length === 0) throw _err("c509/bad-extensions", "nameConstraints must contain permittedSubtrees or excludedSubtrees (RFC 5280 sec. 4.2.1.10)");
|
|
1209
862
|
return b.sequence(ncFields);
|
|
1210
863
|
}
|
|
1211
864
|
case "authorityInfoAccess":
|
|
1212
|
-
case "subjectInfoAccess": {
|
|
865
|
+
case "subjectInfoAccess": {
|
|
1213
866
|
if (node.majorType !== 4 || !node.children) throw _err("c509/bad-extensions", "an " + name + " value must be a CBOR array");
|
|
1214
867
|
var aiaKids = node.children;
|
|
1215
868
|
if (aiaKids.length === 0 || aiaKids.length % 2 !== 0) throw _err("c509/bad-extensions", "an " + name + " array must be non-empty (accessMethod, uri) pairs (sec. 3.3)");
|
|
@@ -1218,12 +871,10 @@ function _extValueToDer(name, node, isNative) {
|
|
|
1218
871
|
return b.sequence(descs);
|
|
1219
872
|
}
|
|
1220
873
|
case "cRLDistributionPoints":
|
|
1221
|
-
case "freshestCRL":
|
|
874
|
+
case "freshestCRL":
|
|
1222
875
|
if (node.majorType === 3) return b.sequence([b.sequence([b.explicit(0, b.contextConstructed(0, b.contextPrimitive(6, _ia5Bytes(node, 6))))])]);
|
|
1223
876
|
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)");
|
|
1224
877
|
return b.sequence(node.children.map(function (dp) { return _dpToDer(dp, isNative); }));
|
|
1225
|
-
// IPAddrBlocks (and its RFC 8360 v2 twin): a FLAT array of (AFI, SAFI, choice) triples --
|
|
1226
|
-
// IPAddressFamily is a parenthesized CDDL group, so it splices and does not nest.
|
|
1227
878
|
case "ipAddrBlocks":
|
|
1228
879
|
case "ipAddrBlocksV2": {
|
|
1229
880
|
if (node.majorType !== 4 || !node.children) throw _err("c509/bad-extensions", "an IPAddrBlocks value must be a CBOR array");
|
|
@@ -1239,16 +890,13 @@ function _extValueToDer(name, node, isNative) {
|
|
|
1239
890
|
if (safi > 0xffn) throw _err("c509/bad-extensions", "an IPAddrBlocks SAFI must fit one octet (RFC 3779 sec. 2.2.3.3)");
|
|
1240
891
|
famBytes.push(Number(safi));
|
|
1241
892
|
}
|
|
1242
|
-
// The families themselves are ordered and unique (RFC 3779 sec. 2.2.3.3), the same way the
|
|
1243
|
-
// addresses inside one are. Without this a value could name AFI 2 before AFI 1, or repeat a
|
|
1244
|
-
// family, and reconstruct a certificate an independent validator refuses.
|
|
1245
893
|
var famOct = Buffer.from(famBytes);
|
|
1246
894
|
if (prevFam !== null && _famOctCmp(prevFam, famOct) >= 0) {
|
|
1247
895
|
throw _err("c509/bad-extensions", "IPAddrBlocks address families must be unique and in ascending addressFamily order (RFC 3779 sec. 2.2.3.3)");
|
|
1248
896
|
}
|
|
1249
897
|
prevFam = famOct;
|
|
1250
898
|
var choice = ipKids[fi + 2], famFields = [b.octetString(famOct)];
|
|
1251
|
-
if (_isCborNull(choice)) {
|
|
899
|
+
if (_isCborNull(choice)) {
|
|
1252
900
|
famFields.push(b.nullValue());
|
|
1253
901
|
} else {
|
|
1254
902
|
if (choice.majorType !== 4 || !choice.children || choice.children.length === 0) {
|
|
@@ -1260,27 +908,16 @@ function _extValueToDer(name, node, isNative) {
|
|
|
1260
908
|
}
|
|
1261
909
|
return b.sequence(families);
|
|
1262
910
|
}
|
|
1263
|
-
// ASIdentifiers (and its v2 twin): null = inherit, else a flat array of uint / [min,max].
|
|
1264
|
-
// Only the asnum field is representable -- a present rdi has no compact form (sec. 3.3), so a
|
|
1265
|
-
// certificate carrying one rides the ~oid byte-string form and never reaches this arm.
|
|
1266
911
|
case "autonomousSysIds":
|
|
1267
912
|
case "autonomousSysIdsV2": {
|
|
1268
|
-
if (_isCborNull(node)) return b.sequence([b.explicit(0, b.nullValue())]);
|
|
913
|
+
if (_isCborNull(node)) return b.sequence([b.explicit(0, b.nullValue())]);
|
|
1269
914
|
if (node.majorType !== 4 || !node.children || node.children.length === 0) {
|
|
1270
915
|
throw _err("c509/bad-extensions", "an ASIdentifiers value must be null (inherit) or a non-empty CBOR array");
|
|
1271
916
|
}
|
|
1272
|
-
// The deltas are `uint` in the draft's CDDL precisely because RFC 3779 sec. 3.2.3.4 sorts AS
|
|
1273
|
-
// ids by increasing value: a negative delta would walk the chain backwards. That section also
|
|
1274
|
-
// forbids a pair overlapping and requires a contiguous series to be one range: the same
|
|
1275
|
-
// three rules the encode side applies, tracked here across members through `asPrevHigh`.
|
|
1276
|
-
// A negative delta needs no separate test: on the first entry it drives the absolute below
|
|
1277
|
-
// zero and the range guard refuses it, and on any later entry it lands at or below the
|
|
1278
|
-
// previous high, which the canonical test already refuses. A separate uint check would be a
|
|
1279
|
-
// branch nothing can reach.
|
|
1280
917
|
var asKids = node.children, asDers = [], asPrev = null, asPrevHigh = null;
|
|
1281
918
|
for (var asi2 = 0; asi2 < asKids.length; asi2++) {
|
|
1282
919
|
var it = asKids[asi2];
|
|
1283
|
-
if (it.majorType === 4) {
|
|
920
|
+
if (it.majorType === 4) {
|
|
1284
921
|
if (!it.children || it.children.length !== 2) throw _err("c509/bad-extensions", "an ASIdentifiers range must be exactly [min, max]");
|
|
1285
922
|
var amin = _asDelta(it.children[0], asPrev), amax = _asDelta(it.children[1], amin);
|
|
1286
923
|
if (amax <= amin) throw _err("c509/bad-extensions", "an ASIdentifiers range must be ascending (RFC 3779 sec. 3.2.3.6)");
|
|
@@ -1288,7 +925,7 @@ function _extValueToDer(name, node, isNative) {
|
|
|
1288
925
|
asDers.push(b.sequence([b.integer(amin), b.integer(amax)]));
|
|
1289
926
|
asPrev = amax;
|
|
1290
927
|
asPrevHigh = amax;
|
|
1291
|
-
} else {
|
|
928
|
+
} else {
|
|
1292
929
|
var aid = _asDelta(it, asPrev);
|
|
1293
930
|
if (asPrevHigh !== null && aid <= asPrevHigh + 1n) throw _err("c509/bad-extensions", "ASIdentifiers must be sorted, non-overlapping and maximally merged (RFC 3779 sec. 3.2.3.4)");
|
|
1294
931
|
asDers.push(b.integer(aid));
|
|
@@ -1298,7 +935,7 @@ function _extValueToDer(name, node, isNative) {
|
|
|
1298
935
|
}
|
|
1299
936
|
return b.sequence([b.explicit(0, b.sequence(asDers))]);
|
|
1300
937
|
}
|
|
1301
|
-
case "certificatePolicies": {
|
|
938
|
+
case "certificatePolicies": {
|
|
1302
939
|
if (node.majorType !== 4 || !node.children) throw _err("c509/bad-extensions", "a certificatePolicies value must be a CBOR array");
|
|
1303
940
|
var cpKids = node.children;
|
|
1304
941
|
if (cpKids.length === 0 || cpKids.length % 2 !== 0) throw _err("c509/bad-extensions", "a certificatePolicies array must be non-empty (policyIdentifier, qualifiers) pairs (sec. 3.3)");
|
|
@@ -1306,9 +943,6 @@ function _extValueToDer(name, node, isNative) {
|
|
|
1306
943
|
for (var cpi = 0; cpi + 1 < cpKids.length; cpi += 2) {
|
|
1307
944
|
var quals = cpKids[cpi + 1];
|
|
1308
945
|
if (quals.majorType !== 4 || !quals.children) throw _err("c509/bad-extensions", "a certificatePolicies qualifiers slot must be a CBOR array");
|
|
1309
|
-
// A policy OID MUST NOT appear more than once (RFC 5280 sec. 4.2.1.4); the toolkit's own DER decoder
|
|
1310
|
-
// rejects a duplicate (schema-pkix certExtensionDecoders), so reconstruct fails closed here and
|
|
1311
|
-
// never produces a certificatePolicies extension that decoder would reject.
|
|
1312
946
|
var policyOid = _policyIdToDerOid(cpKids[cpi]);
|
|
1313
947
|
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)");
|
|
1314
948
|
seenPolicy[policyOid] = true;
|
|
@@ -1317,83 +951,71 @@ function _extValueToDer(name, node, isNative) {
|
|
|
1317
951
|
if (quals.children.length % 2 !== 0) throw _err("c509/bad-extensions", "a policyQualifiers array must be (policyQualifierId, qualifier) pairs");
|
|
1318
952
|
var pqDers = [];
|
|
1319
953
|
for (var qk = 0; qk + 1 < quals.children.length; qk += 2) pqDers.push(_qualifierToDer(quals.children[qk], quals.children[qk + 1]));
|
|
1320
|
-
polFields.push(b.sequence(pqDers));
|
|
954
|
+
polFields.push(b.sequence(pqDers));
|
|
1321
955
|
}
|
|
1322
956
|
polInfos.push(b.sequence(polFields));
|
|
1323
957
|
}
|
|
1324
958
|
return b.sequence(polInfos);
|
|
1325
959
|
}
|
|
1326
|
-
case "policyMappings": {
|
|
960
|
+
case "policyMappings": {
|
|
1327
961
|
if (node.majorType !== 4 || !node.children) throw _err("c509/bad-extensions", "a policyMappings value must be a CBOR array");
|
|
1328
962
|
var pmKids = node.children;
|
|
1329
963
|
if (pmKids.length === 0 || pmKids.length % 2 !== 0) throw _err("c509/bad-extensions", "a policyMappings array must be non-empty (issuerDomainPolicy, subjectDomainPolicy) pairs (sec. 3.3)");
|
|
1330
964
|
var maps = [];
|
|
1331
|
-
// each member is a CertPolicyId -- the same sec. 8.9 int/~oid policy space as certificatePolicies (an int
|
|
1332
|
-
// outside sec. 8.9 fails closed in _policyIdToDerOid). anyPolicy is accepted: RFC 5280 sec. 4.2.1.5's
|
|
1333
|
-
// "MUST NOT map to/from anyPolicy" is a generation rule the toolkit's own DER decoder does not reject.
|
|
1334
965
|
for (var mi = 0; mi + 1 < pmKids.length; mi += 2) maps.push(b.sequence([b.oid(_policyIdToDerOid(pmKids[mi])), b.oid(_policyIdToDerOid(pmKids[mi + 1]))]));
|
|
1335
966
|
return b.sequence(maps);
|
|
1336
967
|
}
|
|
1337
|
-
case "policyConstraints": {
|
|
968
|
+
case "policyConstraints": {
|
|
1338
969
|
if (node.majorType !== 4 || !node.children || node.children.length !== 2) throw _err("c509/bad-extensions", "a policyConstraints value must be a 2-element CBOR array [ requireExplicitPolicy, inhibitPolicyMapping ] (sec. 3.3)");
|
|
1339
970
|
var pcFields = [], repN = node.children[0], ipmN = node.children[1];
|
|
1340
|
-
if (!_isCborNull(repN)) pcFields.push(b.implicit(0, b.integer(_boundCount(_cborUint(repN, "a policyConstraints requireExplicitPolicy"), "a policyConstraints requireExplicitPolicy"))));
|
|
1341
|
-
if (!_isCborNull(ipmN)) pcFields.push(b.implicit(1, b.integer(_boundCount(_cborUint(ipmN, "a policyConstraints inhibitPolicyMapping"), "a policyConstraints inhibitPolicyMapping"))));
|
|
1342
|
-
// both-null is the empty-SEQUENCE case RFC 5280 sec. 4.2.1.11 forbids ("either ... MUST be present"); the
|
|
1343
|
-
// toolkit's own DER decoder rejects the empty PolicyConstraints, so the reconstruct fails closed too.
|
|
971
|
+
if (!_isCborNull(repN)) pcFields.push(b.implicit(0, b.integer(_boundCount(_cborUint(repN, "a policyConstraints requireExplicitPolicy"), "a policyConstraints requireExplicitPolicy"))));
|
|
972
|
+
if (!_isCborNull(ipmN)) pcFields.push(b.implicit(1, b.integer(_boundCount(_cborUint(ipmN, "a policyConstraints inhibitPolicyMapping"), "a policyConstraints inhibitPolicyMapping"))));
|
|
1344
973
|
if (pcFields.length === 0) throw _err("c509/bad-extensions", "policyConstraints must contain requireExplicitPolicy or inhibitPolicyMapping (RFC 5280 sec. 4.2.1.11)");
|
|
1345
|
-
return b.sequence(pcFields);
|
|
974
|
+
return b.sequence(pcFields);
|
|
1346
975
|
}
|
|
1347
|
-
case "subjectDirectoryAttributes":
|
|
976
|
+
case "subjectDirectoryAttributes":
|
|
1348
977
|
return _sdaToDer(node, isNative);
|
|
1349
978
|
default:
|
|
1350
979
|
throw _err("c509/bad-extensions", "extension " + name + " has no compact value decoder");
|
|
1351
980
|
}
|
|
1352
981
|
}
|
|
1353
982
|
|
|
1354
|
-
// Encode a DER extnValue inner content to its compact CBOR value bytes, or null when the DER is not the
|
|
1355
|
-
// canonical form the compact encoding covers (the caller then emits the ~oid + byte-string fallback).
|
|
1356
983
|
function _extValueFromDer(name, der) {
|
|
1357
984
|
var node;
|
|
1358
|
-
// A malformed DER extnValue (an ill-formed OID/INTEGER child, a truncated SEQUENCE) is simply not
|
|
1359
|
-
// compact-encodable; any decode/read fault surfaces as null so the caller emits the ~oid byte-string form.
|
|
1360
|
-
// Wrong-type children throw from asn1.read.* / cbor.build.uint (a negative value) and surface as the null
|
|
1361
|
-
// fallback; the encode-side round-trip guard is the final net, so only the structural dispatch is inline.
|
|
1362
985
|
try {
|
|
1363
986
|
node = asn1.decode(der);
|
|
1364
987
|
switch (name) {
|
|
1365
|
-
case "subjectKeyIdentifier":
|
|
988
|
+
case "subjectKeyIdentifier":
|
|
1366
989
|
return cbor.build.byteString(asn1.read.octetString(node));
|
|
1367
|
-
case "keyUsage": {
|
|
990
|
+
case "keyUsage": {
|
|
1368
991
|
var bits = _keyUsageBitsFromDer(der);
|
|
1369
992
|
return bits == null ? null : cbor.build.uint(BigInt(bits));
|
|
1370
993
|
}
|
|
1371
|
-
case "basicConstraints": {
|
|
994
|
+
case "basicConstraints": {
|
|
1372
995
|
var kids = node.children || [];
|
|
1373
|
-
if (kids.length === 0) return cbor.build.int(-2n);
|
|
1374
|
-
if (asn1.read.boolean(kids[0]) !== true) return null;
|
|
996
|
+
if (kids.length === 0) return cbor.build.int(-2n);
|
|
997
|
+
if (asn1.read.boolean(kids[0]) !== true) return null;
|
|
1375
998
|
if (kids.length === 1) return cbor.build.int(-1n);
|
|
1376
999
|
if (kids.length !== 2) return null;
|
|
1377
|
-
return cbor.build.uint(asn1.read.integer(kids[1]));
|
|
1000
|
+
return cbor.build.uint(asn1.read.integer(kids[1]));
|
|
1378
1001
|
}
|
|
1379
|
-
case "authorityKeyIdentifier": {
|
|
1002
|
+
case "authorityKeyIdentifier": {
|
|
1380
1003
|
var akids = node.children || [];
|
|
1381
1004
|
if (akids.length === 1 && akids[0].tagClass === "context" && akids[0].tagNumber === 0) {
|
|
1382
|
-
return cbor.build.byteString(asn1.read.octetStringImplicit(akids[0], 0));
|
|
1005
|
+
return cbor.build.byteString(asn1.read.octetStringImplicit(akids[0], 0));
|
|
1383
1006
|
}
|
|
1384
|
-
// the [ keyIdentifier [0], authorityCertIssuer [1], authorityCertSerialNumber [2] ] all-three form.
|
|
1385
1007
|
if (akids.length === 3 && akids[0].tagClass === "context" && akids[0].tagNumber === 0 &&
|
|
1386
1008
|
akids[1].tagClass === "context" && akids[1].tagNumber === 1 && akids[1].children &&
|
|
1387
1009
|
akids[2].tagClass === "context" && akids[2].tagNumber === 2) {
|
|
1388
1010
|
var akiIssuer = _generalNamesFromDer(akids[1].children);
|
|
1389
1011
|
if (akiIssuer == null) return null;
|
|
1390
1012
|
var akiSerial = asn1.read.integerImplicit(akids[2], 2);
|
|
1391
|
-
if (akiSerial < 0n) return null;
|
|
1013
|
+
if (akiSerial < 0n) return null;
|
|
1392
1014
|
return cbor.build.array([cbor.build.byteString(asn1.read.octetStringImplicit(akids[0], 0)), cbor.build.array(akiIssuer), cbor.build.byteString(_minBytes(akiSerial))]);
|
|
1393
1015
|
}
|
|
1394
|
-
return null;
|
|
1016
|
+
return null;
|
|
1395
1017
|
}
|
|
1396
|
-
case "extKeyUsage": {
|
|
1018
|
+
case "extKeyUsage": {
|
|
1397
1019
|
var purposes = node.children || [];
|
|
1398
1020
|
if (!purposes.length) return null;
|
|
1399
1021
|
var out = purposes.map(function (p) {
|
|
@@ -1402,29 +1024,29 @@ function _extValueFromDer(name, der) {
|
|
|
1402
1024
|
});
|
|
1403
1025
|
return out.length === 1 ? out[0] : cbor.build.array(out);
|
|
1404
1026
|
}
|
|
1405
|
-
case "inhibitAnyPolicy":
|
|
1027
|
+
case "inhibitAnyPolicy":
|
|
1406
1028
|
return cbor.build.uint(asn1.read.integer(node));
|
|
1407
|
-
case "ocspNoCheck":
|
|
1029
|
+
case "ocspNoCheck":
|
|
1408
1030
|
asn1.read.nullValue(node);
|
|
1409
1031
|
return cbor.build.nullValue();
|
|
1410
|
-
case "tlsFeature":
|
|
1032
|
+
case "tlsFeature":
|
|
1411
1033
|
return cbor.build.array((node.children || []).map(function (f) { return cbor.build.uint(asn1.read.integer(f)); }));
|
|
1412
1034
|
case "subjectAltName":
|
|
1413
|
-
case "issuerAltName": {
|
|
1035
|
+
case "issuerAltName": {
|
|
1414
1036
|
if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children || node.children.length < 1) return null;
|
|
1415
1037
|
var sanMembers = node.children;
|
|
1416
1038
|
if (sanMembers.length === 1 && sanMembers[0].tagClass === "context" && sanMembers[0].tagNumber === 2) {
|
|
1417
|
-
return cbor.build.textString(schema.walk(GN_LEAF, sanMembers[0], NS).value);
|
|
1039
|
+
return cbor.build.textString(schema.walk(GN_LEAF, sanMembers[0], NS).value);
|
|
1418
1040
|
}
|
|
1419
1041
|
var sanItems = _generalNamesFromDer(sanMembers);
|
|
1420
1042
|
return sanItems == null ? null : cbor.build.array(sanItems);
|
|
1421
1043
|
}
|
|
1422
|
-
case "nameConstraints": {
|
|
1044
|
+
case "nameConstraints": {
|
|
1423
1045
|
if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children || node.children.length < 1) return null;
|
|
1424
1046
|
var permCbor = cbor.build.nullValue(), exclCbor = cbor.build.nullValue(), sawNc = false, ncLast = -1;
|
|
1425
1047
|
for (var nci = 0; nci < node.children.length; nci++) {
|
|
1426
1048
|
var ncf = node.children[nci];
|
|
1427
|
-
if (ncf.tagClass !== "context" || ncf.tagNumber <= ncLast) return null;
|
|
1049
|
+
if (ncf.tagClass !== "context" || ncf.tagNumber <= ncLast) return null;
|
|
1428
1050
|
ncLast = ncf.tagNumber;
|
|
1429
1051
|
var subItems = _subtreesFromDer(ncf.children || []);
|
|
1430
1052
|
if (subItems == null) return null;
|
|
@@ -1435,7 +1057,7 @@ function _extValueFromDer(name, der) {
|
|
|
1435
1057
|
return sawNc ? cbor.build.array([permCbor, exclCbor]) : null;
|
|
1436
1058
|
}
|
|
1437
1059
|
case "authorityInfoAccess":
|
|
1438
|
-
case "subjectInfoAccess": {
|
|
1060
|
+
case "subjectInfoAccess": {
|
|
1439
1061
|
var aiaDescs = node.children || [];
|
|
1440
1062
|
if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || aiaDescs.length < 1) return null;
|
|
1441
1063
|
var aiaOut = [];
|
|
@@ -1443,7 +1065,7 @@ function _extValueFromDer(name, der) {
|
|
|
1443
1065
|
var d = aiaDescs[adi];
|
|
1444
1066
|
if (d.tagClass !== "universal" || d.tagNumber !== asn1.TAGS.SEQUENCE || !d.children || d.children.length !== 2) return null;
|
|
1445
1067
|
var aiaLoc = d.children[1];
|
|
1446
|
-
if (aiaLoc.tagClass !== "context" || aiaLoc.tagNumber !== 6) return null;
|
|
1068
|
+
if (aiaLoc.tagClass !== "context" || aiaLoc.tagNumber !== 6) return null;
|
|
1447
1069
|
var methodDotted = asn1.read.oid(d.children[0]), mint = IA_TO_INT[methodDotted];
|
|
1448
1070
|
aiaOut.push(mint !== undefined ? cbor.build.int(BigInt(mint)) : _oidCbor(methodDotted));
|
|
1449
1071
|
aiaOut.push(cbor.build.textString(schema.walk(GN_LEAF, aiaLoc, NS).value));
|
|
@@ -1451,7 +1073,7 @@ function _extValueFromDer(name, der) {
|
|
|
1451
1073
|
return cbor.build.array(aiaOut);
|
|
1452
1074
|
}
|
|
1453
1075
|
case "cRLDistributionPoints":
|
|
1454
|
-
case "freshestCRL": {
|
|
1076
|
+
case "freshestCRL": {
|
|
1455
1077
|
var dps = node.children || [];
|
|
1456
1078
|
if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || dps.length < 1) return null;
|
|
1457
1079
|
var dpResults = [];
|
|
@@ -1459,8 +1081,6 @@ function _extValueFromDer(name, der) {
|
|
|
1459
1081
|
if (dpResults.length === 1 && dpResults[0].oneUri != null && dpResults[0].noReasons && dpResults[0].noIssuer) return cbor.build.textString(dpResults[0].oneUri);
|
|
1460
1082
|
return cbor.build.array(dpResults.map(function (r) { return r.triple; }));
|
|
1461
1083
|
}
|
|
1462
|
-
// IPAddrBlocks -> the flat (AFI, SAFI, choice) array. Any shape the compact form cannot
|
|
1463
|
-
// represent exactly returns null, so the extension rides the ~oid byte-string form intact.
|
|
1464
1084
|
case "ipAddrBlocks":
|
|
1465
1085
|
case "ipAddrBlocksV2": {
|
|
1466
1086
|
if (!_isUniversal(node, asn1.TAGS.SEQUENCE) || !node.children || node.children.length === 0) return null;
|
|
@@ -1469,36 +1089,28 @@ function _extValueFromDer(name, der) {
|
|
|
1469
1089
|
var fam = node.children[ifi];
|
|
1470
1090
|
if (!_isUniversal(fam, asn1.TAGS.SEQUENCE) || !fam.children || fam.children.length !== 2) return null;
|
|
1471
1091
|
var famOct = asn1.read.octetString(fam.children[0]);
|
|
1472
|
-
if (famOct.length !== 2 && famOct.length !== 3) return null;
|
|
1473
|
-
// Families ordered and unique (RFC 3779 sec. 2.2.3.3) -- the mirror of the decode side.
|
|
1092
|
+
if (famOct.length !== 2 && famOct.length !== 3) return null;
|
|
1474
1093
|
if (prevFamOct !== null && _famOctCmp(prevFamOct, famOct) >= 0) return null;
|
|
1475
1094
|
prevFamOct = famOct;
|
|
1476
1095
|
var afiVal = (famOct[0] << 8) | famOct[1];
|
|
1477
1096
|
ipOut.push(cbor.build.uint(BigInt(afiVal)));
|
|
1478
1097
|
ipOut.push(famOct.length === 3 ? cbor.build.uint(BigInt(famOct[2])) : cbor.build.nullValue());
|
|
1479
1098
|
var ch = fam.children[1];
|
|
1480
|
-
if (_isUniversal(ch, asn1.TAGS.NULL)) { ipOut.push(cbor.build.nullValue()); continue; }
|
|
1099
|
+
if (_isUniversal(ch, asn1.TAGS.NULL)) { ipOut.push(cbor.build.nullValue()); continue; }
|
|
1481
1100
|
var chOut = _ipChoiceFromDer(ch, afiVal);
|
|
1482
1101
|
if (!chOut) return null;
|
|
1483
1102
|
ipOut.push(chOut);
|
|
1484
1103
|
}
|
|
1485
1104
|
return cbor.build.array(ipOut);
|
|
1486
1105
|
}
|
|
1487
|
-
// ASIdentifiers -> null (inherit) or the flat delta array. Only asnum is representable: a
|
|
1488
|
-
// present rdi has no compact form (sec. 3.3), so such a certificate returns null here.
|
|
1489
1106
|
case "autonomousSysIds":
|
|
1490
1107
|
case "autonomousSysIdsV2": {
|
|
1491
1108
|
if (!_isUniversal(node, asn1.TAGS.SEQUENCE) || !node.children || node.children.length !== 1) return null;
|
|
1492
1109
|
var asnum = node.children[0];
|
|
1493
1110
|
if (asnum.tagClass !== "context" || asnum.tagNumber !== 0 || !asnum.children || asnum.children.length !== 1) return null;
|
|
1494
1111
|
var inner = asnum.children[0];
|
|
1495
|
-
if (_isUniversal(inner, asn1.TAGS.NULL)) return cbor.build.nullValue();
|
|
1112
|
+
if (_isUniversal(inner, asn1.TAGS.NULL)) return cbor.build.nullValue();
|
|
1496
1113
|
if (!_isUniversal(inner, asn1.TAGS.SEQUENCE) || !inner.children || inner.children.length === 0) return null;
|
|
1497
|
-
// RFC 3779 sec. 3.2.3.4 fixes the canonical form the same way sec. 2.2.3.6 does for addresses:
|
|
1498
|
-
// sorted by increasing value, no pair overlapping, and any contiguous series already merged
|
|
1499
|
-
// into one range. `asPrevHigh` carries the previous entry's upper bound so all three hold
|
|
1500
|
-
// ACROSS members. Checking only within a range would let a descending or adjacent pair
|
|
1501
|
-
// through. A list that is not canonical is left uncompacted with its bytes intact.
|
|
1502
1114
|
var asOut = [], asPrevOut = null, asPrevHigh = null;
|
|
1503
1115
|
for (var aoi = 0; aoi < inner.children.length; aoi++) {
|
|
1504
1116
|
var el = inner.children[aoi], elLo, elHi;
|
|
@@ -1511,7 +1123,7 @@ function _extValueFromDer(name, der) {
|
|
|
1511
1123
|
elHi = asn1.read.integer(el.children[1]);
|
|
1512
1124
|
if (elLo < 0n || elHi > 4294967295n || elHi <= elLo) return null;
|
|
1513
1125
|
} else return null;
|
|
1514
|
-
if (asPrevHigh !== null && elLo <= asPrevHigh + 1n) return null;
|
|
1126
|
+
if (asPrevHigh !== null && elLo <= asPrevHigh + 1n) return null;
|
|
1515
1127
|
if (el.tagNumber === asn1.TAGS.INTEGER) {
|
|
1516
1128
|
asOut.push(cbor.build.int(asPrevOut === null ? elLo : elLo - asPrevOut));
|
|
1517
1129
|
asPrevOut = elLo;
|
|
@@ -1526,7 +1138,7 @@ function _extValueFromDer(name, der) {
|
|
|
1526
1138
|
}
|
|
1527
1139
|
return cbor.build.array(asOut);
|
|
1528
1140
|
}
|
|
1529
|
-
case "certificatePolicies": {
|
|
1141
|
+
case "certificatePolicies": {
|
|
1530
1142
|
if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children || node.children.length < 1) return null;
|
|
1531
1143
|
var cpOut = [];
|
|
1532
1144
|
for (var pli = 0; pli < node.children.length; pli++) {
|
|
@@ -1536,20 +1148,20 @@ function _extValueFromDer(name, der) {
|
|
|
1536
1148
|
var qArr = [];
|
|
1537
1149
|
if (pol.children.length === 2) {
|
|
1538
1150
|
var qseq = pol.children[1];
|
|
1539
|
-
if (qseq.tagClass !== "universal" || qseq.tagNumber !== asn1.TAGS.SEQUENCE || !qseq.children || !qseq.children.length) return null;
|
|
1151
|
+
if (qseq.tagClass !== "universal" || qseq.tagNumber !== asn1.TAGS.SEQUENCE || !qseq.children || !qseq.children.length) return null;
|
|
1540
1152
|
for (var qj = 0; qj < qseq.children.length; qj++) {
|
|
1541
1153
|
var pq = qseq.children[qj];
|
|
1542
1154
|
if (pq.tagClass !== "universal" || pq.tagNumber !== asn1.TAGS.SEQUENCE || !pq.children || pq.children.length !== 2) return null;
|
|
1543
1155
|
var qpair = _qualifierFromDer(pq);
|
|
1544
|
-
if (qpair == null) return null;
|
|
1156
|
+
if (qpair == null) return null;
|
|
1545
1157
|
qArr.push(qpair[0]); qArr.push(qpair[1]);
|
|
1546
1158
|
}
|
|
1547
1159
|
}
|
|
1548
|
-
cpOut.push(cbor.build.array(qArr));
|
|
1160
|
+
cpOut.push(cbor.build.array(qArr));
|
|
1549
1161
|
}
|
|
1550
1162
|
return cbor.build.array(cpOut);
|
|
1551
1163
|
}
|
|
1552
|
-
case "policyMappings": {
|
|
1164
|
+
case "policyMappings": {
|
|
1553
1165
|
if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children || node.children.length < 1) return null;
|
|
1554
1166
|
var pmOut = [];
|
|
1555
1167
|
for (var pm = 0; pm < node.children.length; pm++) {
|
|
@@ -1560,22 +1172,22 @@ function _extValueFromDer(name, der) {
|
|
|
1560
1172
|
}
|
|
1561
1173
|
return cbor.build.array(pmOut);
|
|
1562
1174
|
}
|
|
1563
|
-
case "policyConstraints": {
|
|
1175
|
+
case "policyConstraints": {
|
|
1564
1176
|
if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children || node.children.length < 1 || node.children.length > 2) return null;
|
|
1565
1177
|
var repC = cbor.build.nullValue(), ipmC = cbor.build.nullValue(), pcLast = -1;
|
|
1566
1178
|
for (var pci = 0; pci < node.children.length; pci++) {
|
|
1567
1179
|
var f = node.children[pci];
|
|
1568
|
-
if (f.tagClass !== "context" || f.tagNumber <= pcLast) return null;
|
|
1180
|
+
if (f.tagClass !== "context" || f.tagNumber <= pcLast) return null;
|
|
1569
1181
|
pcLast = f.tagNumber;
|
|
1570
1182
|
var sv = asn1.read.integerImplicit(f, f.tagNumber);
|
|
1571
|
-
if (sv < 0n) return null;
|
|
1183
|
+
if (sv < 0n) return null;
|
|
1572
1184
|
if (f.tagNumber === 0) repC = cbor.build.uint(sv);
|
|
1573
1185
|
else if (f.tagNumber === 1) ipmC = cbor.build.uint(sv);
|
|
1574
1186
|
else return null;
|
|
1575
1187
|
}
|
|
1576
1188
|
return cbor.build.array([repC, ipmC]);
|
|
1577
1189
|
}
|
|
1578
|
-
case "subjectDirectoryAttributes":
|
|
1190
|
+
case "subjectDirectoryAttributes":
|
|
1579
1191
|
return _sdaFromDer(node);
|
|
1580
1192
|
default:
|
|
1581
1193
|
return null;
|
|
@@ -1585,67 +1197,15 @@ function _extValueFromDer(name, der) {
|
|
|
1585
1197
|
}
|
|
1586
1198
|
}
|
|
1587
1199
|
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
// sec. 8.6 registry int (the sign selects the string type) with text values, or an unwrapped ~oid with raw DER
|
|
1593
|
-
// value bytes (an unregistered type stays compact via its own ~oid form, never dropping the whole extension).
|
|
1594
|
-
// The extension-level _tryCompactExtValue round-trip guard is the byte-exactness net.
|
|
1595
|
-
|
|
1596
|
-
// A ~oid-form attributeValue must be exactly one non-empty, well-formed DER AttributeValue TLV. An empty byte
|
|
1597
|
-
// string would build a SET OF SIZE 0 (RFC 5280 sec. 4.2.1.8 requires at least one value); a byte string carrying
|
|
1598
|
-
// more than one TLV would fan one declared value into several SET members (draft-20 sec. 3.3: one `bytes` is one
|
|
1599
|
-
// AttributeValue). There is no downstream DER decoder to catch either (SDA ships c509-only), so the reconstruct
|
|
1600
|
-
// arm self-enforces the single-TLV shape, so a degenerate or invalid Attribute is never spliced.
|
|
1601
|
-
// ---- the shared strict-DER gate for a raw-spliced ANY value --------------------------------------
|
|
1602
|
-
// Several reconstruct sites splice CALLER/ATTACKER-supplied bytes into the rebuilt certificate verbatim (an
|
|
1603
|
-
// AlgorithmIdentifier's `parameters`, a generic otherName's `value [0] EXPLICIT ANY`, a subjectDirectoryAttributes
|
|
1604
|
-
// `~oid`-form AttributeValue). asn1.decode validates TLV framing alone. It rejects truncation, trailing bytes and
|
|
1605
|
-
// non-minimal lengths, and enforces the DER primitive/constructed rules, but it does not check per-type content:
|
|
1606
|
-
// it frames an empty INTEGER, the reserved end-of-contents tag 0, and an out-of-alphabet string quite happily.
|
|
1607
|
-
// Splicing those produces a certificate an independent decoder refuses to load, and one this toolkit's own readers
|
|
1608
|
-
// reject. Every such site therefore routes through the one gate below instead of a bare asn1.decode.
|
|
1609
|
-
|
|
1610
|
-
// Every universal PRIMITIVE tag whose content this toolkit can strictly validate, mapped to its reader. A
|
|
1611
|
-
// universal primitive outside this map has no strict content validator here, so the gate REJECTS it and
|
|
1612
|
-
// nothing is spliced unchecked. The documented residual: a value whose type this toolkit cannot yet strictly
|
|
1613
|
-
// validate is not compact-representable. The ASN.1 types in that residual are `VideotexString` /
|
|
1614
|
-
// `GraphicString` / `GeneralString` / `ObjectDescriptor` / `REAL` (no content reader in this toolkit), and a
|
|
1615
|
-
// fractional-seconds GeneralizedTime (the X.690 sec. 11.7 relaxation is deliberately scoped to the codec
|
|
1616
|
-
// and RFC 3161 timestamping, and must not creep into a third consumer). On the ENCODE path such a value simply
|
|
1617
|
-
// degrades to the byte-exact ~oid + byte-string form, losing nothing; on the DECODE path it is a fail-closed
|
|
1618
|
-
// verdict.
|
|
1619
|
-
// The per-type strict content readers, the SET canonical-order rule and the recursive element
|
|
1620
|
-
// walk all moved to guard.der, so this module and pki-build cannot drift on what "one strictly
|
|
1621
|
-
// valid DER element" means.
|
|
1622
|
-
|
|
1623
|
-
// A universal SET's required member order depends on a type the ANY does not carry: X.690 sec. 11.6 orders a
|
|
1624
|
-
// SET OF by the members' full encodings, while a structured SET is ordered by TAG (X.680 sec. 8.6), and the two
|
|
1625
|
-
// differ whenever the constructed bit does (a SEQUENCE member, tag 16, sorts before a PrintableString, tag 19, by
|
|
1626
|
-
// tag but after it by octets). A structured SET cannot repeat a tag, so a repeated tag proves SET OF and the
|
|
1627
|
-
// octet rule binds; with all-distinct tags either reading is possible, so accept a value that satisfies EITHER
|
|
1628
|
-
// (rejecting only what is non-canonical under both readings: sound in both directions, never a guess).
|
|
1629
|
-
|
|
1630
|
-
// Strict-validate a decoded DER element at ANY depth, in the caller's error domain. Rejects the reserved EOC tag
|
|
1631
|
-
// 0; runs a universal primitive through its strict content reader (or rejects a type with none); recurses into a
|
|
1632
|
-
// constructed element's children; and holds a universal SET to a canonical order. Only SEQUENCE and SET are
|
|
1633
|
-
// accepted as universal CONSTRUCTED types. An EXTERNAL / EMBEDDED PDV / CHARACTER STRING has mandatory
|
|
1634
|
-
// components this gate cannot verify (the degenerate empty form is not a valid encoding of any of them), so it is
|
|
1635
|
-
// refused for the same reason an unvalidatable primitive is. A NON-universal element (a legitimately context- or
|
|
1636
|
-
// application-tagged ANY) passes on its framing, but its constructed children are still walked.
|
|
1637
|
-
// Raw ANY bytes about to be spliced verbatim must be exactly one non-empty, well-formed AND strictly-valid DER
|
|
1638
|
-
// element. The rule itself lives in guard.der -- framing + no-trailing-data, then content / structure / SET
|
|
1639
|
-
// order -- so this module and the certificate builder cannot drift apart on what "one DER element" means. This
|
|
1640
|
-
// stays as a named local because every reconstruct site here reads better naming the intent than the guard.
|
|
1200
|
+
|
|
1201
|
+
|
|
1202
|
+
|
|
1203
|
+
|
|
1641
1204
|
|
|
1642
1205
|
function _requireStrictDerTlv(content, code, label) {
|
|
1643
1206
|
return guard.der.tlv(content, _err, code, label);
|
|
1644
1207
|
}
|
|
1645
1208
|
|
|
1646
|
-
// asn1.build.set SORTS its members, so handing it a non-canonically-ordered values list would silently rewrite
|
|
1647
|
-
// the declared order, and many distinct C509 encodings would reconstruct one and the same certificate. Require the
|
|
1648
|
-
// declared order to already BE canonical, so the compact form maps one-to-one onto the DER it rebuilds.
|
|
1649
1209
|
function _derSetInDeclaredOrder(vals) {
|
|
1650
1210
|
for (var i = 1; i < vals.length; i++) {
|
|
1651
1211
|
if (Buffer.compare(vals[i - 1], vals[i]) > 0) throw _err("c509/bad-extensions", "a subjectDirectoryAttributes attributeValue list must be in DER ascending order (X.690 sec. 11.6)");
|
|
@@ -1653,8 +1213,6 @@ function _derSetInDeclaredOrder(vals) {
|
|
|
1653
1213
|
return b.set(vals);
|
|
1654
1214
|
}
|
|
1655
1215
|
|
|
1656
|
-
// [ type1, values1, ... ] -> the DER SubjectDirectoryAttributes. A malformed native value fails closed
|
|
1657
|
-
// (c509/bad-extensions); on the encode path the same throw is a round-trip mismatch -> whole-ext ~oid fallback.
|
|
1658
1216
|
function _sdaToDer(node, isNative) {
|
|
1659
1217
|
if (node.majorType !== 4 || !node.children) throw _err("c509/bad-extensions", "a subjectDirectoryAttributes value must be a CBOR array");
|
|
1660
1218
|
var kids = node.children;
|
|
@@ -1664,28 +1222,19 @@ function _sdaToDer(node, isNative) {
|
|
|
1664
1222
|
var typeNode = kids[i], valuesNode = kids[i + 1];
|
|
1665
1223
|
if (valuesNode.majorType !== 4 || !valuesNode.children || valuesNode.children.length < 1) throw _err("c509/bad-extensions", "a subjectDirectoryAttributes attributeValue must be a non-empty CBOR array (SET OF, SIZE 1..MAX, RFC 5280 sec. 4.2.1.8)");
|
|
1666
1224
|
var vals = [];
|
|
1667
|
-
if (typeNode.majorType === 0 || typeNode.majorType === 1) {
|
|
1225
|
+
if (typeNode.majorType === 0 || typeNode.majorType === 1) {
|
|
1668
1226
|
var ti = Number(cbor.read.int(typeNode));
|
|
1669
|
-
// draft sec. 3.1.4 applies to every int in a natively signed certificate, not just the top-level Name's.
|
|
1670
1227
|
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);
|
|
1671
1228
|
var tname = ATTR_BY_INT[Math.abs(ti)];
|
|
1672
1229
|
if (tname === undefined) throw _err("c509/bad-extensions", "a subjectDirectoryAttributes attribute type int " + ti + " has no C509 sec. 8.6 registry row");
|
|
1673
|
-
// countryName / serialNumber carry a CHARACTER restriction (draft sec. 3.1.4 "SHALL contain only
|
|
1674
|
-
// characters from the 74-character ASCII subset permitted by PrintableString"), and not a sign override --
|
|
1675
|
-
// _reconAttrValue asserts the charset and honors the declared string type. Requiring the negative sign
|
|
1676
|
-
// here would also make these attributes unrepresentable in a NATIVE certificate, whose ints SHALL all be
|
|
1677
|
-
// non-negative (same sec.), so the rule is the charset, not the sign.
|
|
1678
1230
|
for (var vi = 0; vi < valuesNode.children.length; vi++) {
|
|
1679
1231
|
var vn = valuesNode.children[vi];
|
|
1680
1232
|
if (vn.majorType !== 3) throw _err("c509/bad-extensions", "a subjectDirectoryAttributes int-form attribute value must be a CBOR text string (a non-string value requires the ~oid form)");
|
|
1681
|
-
// _reconAttrValue -> b.printable / b.utf8 throws only an Asn1Error for a value outside its string type's
|
|
1682
|
-
// alphabet (a printableString sign over non-PrintableString characters); remap it to this module's
|
|
1683
|
-
// domain so attacker-controlled native input fails closed as c509/bad-extensions, not a leaked asn1/*.
|
|
1684
1233
|
try { vals.push(_reconAttrValue({ type: tname, value: cbor.read.textString(vn), printable: ti < 0 })); }
|
|
1685
1234
|
catch (e) { throw _err("c509/bad-extensions", "a subjectDirectoryAttributes " + tname + " value is not valid for its string type", e); }
|
|
1686
1235
|
}
|
|
1687
1236
|
attrs.push(b.sequence([b.oid(oid.byName(tname)), _derSetInDeclaredOrder(vals)]));
|
|
1688
|
-
} else if (typeNode.majorType === 2) {
|
|
1237
|
+
} else if (typeNode.majorType === 2) {
|
|
1689
1238
|
var dotted = _oidName(typeNode, "c509/bad-extensions", "a subjectDirectoryAttributes attribute type").oid;
|
|
1690
1239
|
for (var vj = 0; vj < valuesNode.children.length; vj++) {
|
|
1691
1240
|
var vb = valuesNode.children[vj];
|
|
@@ -1700,15 +1249,9 @@ function _sdaToDer(node, isNative) {
|
|
|
1700
1249
|
return b.sequence(attrs);
|
|
1701
1250
|
}
|
|
1702
1251
|
|
|
1703
|
-
// One DER Attribute's SET members -> the int-form { sign, values:[CBOR text] }, or null when the int form is
|
|
1704
|
-
// not confidently byte-exact (an unregistered type, a non-utf8/printable value, or a SET mixing utf8String and
|
|
1705
|
-
// printableString, which one sign cannot express). A null routes that one attribute to the always-byte-exact
|
|
1706
|
-
// ~oid + raw-bytes form (a still-compact sec. 3.3 SDA form); the extension round-trip guard is the ultimate net.
|
|
1707
1252
|
function _sdaTryIntForm(tint, name, valueNodes) {
|
|
1708
1253
|
if (tint === undefined) return null;
|
|
1709
1254
|
var i;
|
|
1710
|
-
// An IA5String-only attribute (emailAddress) rides its non-negative int with IA5String values. Its type,
|
|
1711
|
-
// not a sign, fixes the string type, so it is matched separately from the utf8/printable sign convention.
|
|
1712
1255
|
if (name === "emailAddress") {
|
|
1713
1256
|
var mails = [];
|
|
1714
1257
|
for (i = 0; i < valueNodes.length; i++) {
|
|
@@ -1717,22 +1260,19 @@ function _sdaTryIntForm(tint, name, valueNodes) {
|
|
|
1717
1260
|
}
|
|
1718
1261
|
return { sign: 1, values: mails };
|
|
1719
1262
|
}
|
|
1720
|
-
var sign = 0, out = [];
|
|
1263
|
+
var sign = 0, out = [];
|
|
1721
1264
|
for (i = 0; i < valueNodes.length; i++) {
|
|
1722
1265
|
var vn = valueNodes[i], s;
|
|
1723
1266
|
if (vn.tagClass === "universal" && vn.tagNumber === asn1.TAGS.UTF8_STRING) s = 1;
|
|
1724
1267
|
else if (vn.tagClass === "universal" && vn.tagNumber === asn1.TAGS.PRINTABLE_STRING) s = -1;
|
|
1725
|
-
else return null;
|
|
1268
|
+
else return null;
|
|
1726
1269
|
if (sign === 0) sign = s;
|
|
1727
|
-
else if (sign !== s) return null;
|
|
1270
|
+
else if (sign !== s) return null;
|
|
1728
1271
|
out.push(cbor.build.textString(asn1.read.string(vn)));
|
|
1729
1272
|
}
|
|
1730
1273
|
return { sign: sign, values: out };
|
|
1731
1274
|
}
|
|
1732
1275
|
|
|
1733
|
-
// The DER SubjectDirectoryAttributes -> the flat compact CBOR array, or null (whole-ext ~oid fallback). Per
|
|
1734
|
-
// attribute: the int form when the type is sec. 8.6-registered AND every value is a single-signed utf8/
|
|
1735
|
-
// printable string, else the ~oid + raw-bytes form (always byte-exact).
|
|
1736
1276
|
function _sdaFromDer(node) {
|
|
1737
1277
|
if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children || node.children.length < 1) return null;
|
|
1738
1278
|
var out = [];
|
|
@@ -1740,7 +1280,7 @@ function _sdaFromDer(node) {
|
|
|
1740
1280
|
var attr = node.children[ai];
|
|
1741
1281
|
if (attr.tagClass !== "universal" || attr.tagNumber !== asn1.TAGS.SEQUENCE || !attr.children || attr.children.length !== 2) return null;
|
|
1742
1282
|
var setNode = attr.children[1];
|
|
1743
|
-
if (setNode.tagClass !== "universal" || setNode.tagNumber !== asn1.TAGS.SET || !setNode.children || setNode.children.length < 1) return null;
|
|
1283
|
+
if (setNode.tagClass !== "universal" || setNode.tagNumber !== asn1.TAGS.SET || !setNode.children || setNode.children.length < 1) return null;
|
|
1744
1284
|
var dotted = asn1.read.oid(attr.children[0]);
|
|
1745
1285
|
var nm = oid.name(dotted), tint = nm != null ? ATTR_TO_INT[nm] : undefined;
|
|
1746
1286
|
var intForm = _sdaTryIntForm(tint, nm, setNode.children);
|
|
@@ -1755,13 +1295,6 @@ function _sdaFromDer(node) {
|
|
|
1755
1295
|
return cbor.build.array(out);
|
|
1756
1296
|
}
|
|
1757
1297
|
|
|
1758
|
-
// Encode-side guard: emit the compact value only when it decodes back to the EXACT DER extnValue, so a
|
|
1759
|
-
// non-canonical or unrepresentable value can never produce a lossy compact form (it falls back to ~oid).
|
|
1760
|
-
// Some values _extValueFromDer produced cannot be reconstructed by _extValueToDer: an empty GeneralNames the
|
|
1761
|
-
// DER builder rejects (an AKI authorityCertIssuer [1] with no members), or any future codec asymmetry. None of
|
|
1762
|
-
// them is compact-representable, and the round-trip reconstruction throwing is treated identically to a byte
|
|
1763
|
-
// mismatch. encode() falls back to the ~oid form and does not raise on a certificate x509.parse
|
|
1764
|
-
// accepts. The final encode() self-verify still gates byte-exactness end to end.
|
|
1765
1298
|
function _tryCompactExtValue(name, der) {
|
|
1766
1299
|
var compact = _extValueFromDer(name, der);
|
|
1767
1300
|
if (compact == null) return null;
|
|
@@ -1774,10 +1307,7 @@ function _tryCompactExtValue(name, der) {
|
|
|
1774
1307
|
return recon.equals(der) ? compact : null;
|
|
1775
1308
|
}
|
|
1776
1309
|
|
|
1777
|
-
// extensions (sec. 3.1.10/sec. 3.3/sec. 8.8): [ * Extension ] | a single keyUsage int-shortcut.
|
|
1778
1310
|
function _extensions(node, isNative) {
|
|
1779
|
-
// The keyUsage int-shortcut (sec. 3.1.10): a bare int -> one keyUsage extension, criticality from the
|
|
1780
|
-
// sign, value = abs(int) (Appendix A.1.1: the single int 1 -> non-critical keyUsage digitalSignature).
|
|
1781
1311
|
if (node.majorType === 0 || node.majorType === 1) {
|
|
1782
1312
|
var iv = Number(cbor.read.int(node));
|
|
1783
1313
|
return [{ name: "keyUsage", oid: oid.byName("keyUsage"), critical: iv < 0, keyUsageBits: Math.abs(iv) }];
|
|
@@ -1785,12 +1315,7 @@ function _extensions(node, isNative) {
|
|
|
1785
1315
|
if (node.majorType !== 4) throw _err("c509/bad-extensions", "C509 extensions must be an array or a keyUsage int shortcut");
|
|
1786
1316
|
var out = [];
|
|
1787
1317
|
var kids = node.children || [];
|
|
1788
|
-
// Each extension is an (extensionID, extensionValue) pair; an odd-length array is a dangling
|
|
1789
|
-
// extension identifier with no value, so the whole extensions array is rejected.
|
|
1790
1318
|
if (kids.length % 2 !== 0) throw _err("c509/bad-extensions", "a C509 extensions array must be id/value pairs (dangling extension identifier)");
|
|
1791
|
-
// sec. 3.1.10: "If the CBOR array contains exactly two ints and the absolute value of the first int is
|
|
1792
|
-
// 2 (corresponding to keyUsage), the CBOR array is omitted and the 'extensions' field is encoded as a
|
|
1793
|
-
// single CBOR int." The array spelling of that one case is a second encoding of the same extensions.
|
|
1794
1319
|
if (!isNative && kids.length === 2 &&
|
|
1795
1320
|
(kids[0].majorType === 0 || kids[0].majorType === 1) && (kids[1].majorType === 0 || kids[1].majorType === 1) &&
|
|
1796
1321
|
Math.abs(Number(cbor.read.int(kids[0]))) === 2) {
|
|
@@ -1804,11 +1329,6 @@ function _extensions(node, isNative) {
|
|
|
1804
1329
|
name = EXT_BY_INT[Math.abs(ei)];
|
|
1805
1330
|
if (name === undefined) throw _err("c509/bad-extensions", "extension type integer " + ei + " has no C509 registry row");
|
|
1806
1331
|
extOid = oid.byName(name); critical = ei < 0;
|
|
1807
|
-
// A registered extension with a compact value form (sec. 3.3) decodes its specific CBOR value to the
|
|
1808
|
-
// DER extnValue inner content. A registered extension without one (subjectAltName, until the general-
|
|
1809
|
-
// name value codec lands) can only reconstruct a byte-string value (the raw DER extnValue); a
|
|
1810
|
-
// non-byte-string value there is an unsupported compact form and MUST fail closed. A text/array value
|
|
1811
|
-
// is not raw DER, and copying its bytes would reconstruct a structurally invalid extension.
|
|
1812
1332
|
if (EXT_COMPACT[name]) {
|
|
1813
1333
|
valContent = _extValueToDer(name, valNode, isNative);
|
|
1814
1334
|
} else if (valNode.majorType === 2) {
|
|
@@ -1819,8 +1339,6 @@ function _extensions(node, isNative) {
|
|
|
1819
1339
|
} else {
|
|
1820
1340
|
var r = _oidName(idNode, "c509/bad-extensions", "an extension id");
|
|
1821
1341
|
name = r.name; extOid = r.oid;
|
|
1822
|
-
// ~oid extension (sec. 3.1.10): the extnValue is a byte string, either BARE (non-critical) or wrapped in a
|
|
1823
|
-
// single-element array (critical). Validate the shape so a malformed value fails closed at decode.
|
|
1824
1342
|
critical = valNode.majorType === 4;
|
|
1825
1343
|
if (critical) {
|
|
1826
1344
|
if (!valNode.children || valNode.children.length !== 1 || valNode.children[0].majorType !== 2) {
|
|
@@ -1837,13 +1355,7 @@ function _extensions(node, isNative) {
|
|
|
1837
1355
|
return out;
|
|
1838
1356
|
}
|
|
1839
1357
|
|
|
1840
|
-
// ---- type-3 DER reconstruction (byte-exact inversion; draft-20 sec. 3 / sec. 5) ----
|
|
1841
|
-
// Type 3 is an INVERTIBLE transform: the reconstruction reproduces the ORIGINAL DER Certificate
|
|
1842
|
-
// byte-for-byte (so the original signature verifies). A field outside the covered set fails closed
|
|
1843
|
-
// (c509/non-invertible); it never emits a partial or best-effort DER.
|
|
1844
1358
|
|
|
1845
|
-
// A C509 tag-48 MAC (RFC 9542) reconstructs to the commonName EUI-64 string: a 6-byte value is a 48-bit
|
|
1846
|
-
// 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.
|
|
1847
1359
|
function _macToEui64String(buf) {
|
|
1848
1360
|
var bytes = buf.length === 6 ? Buffer.concat([buf.subarray(0, 3), Buffer.from([0xff, 0xfe]), buf.subarray(3)]) : buf;
|
|
1849
1361
|
var s = [];
|
|
@@ -1851,64 +1363,40 @@ function _macToEui64String(buf) {
|
|
|
1851
1363
|
return s.join("-");
|
|
1852
1364
|
}
|
|
1853
1365
|
|
|
1854
|
-
// One RDN attribute value -> its DER string. The C509 sign convention: a positive attribute int ->
|
|
1855
|
-
// utf8String, a negative -> printableString; countryName / serialNumber are PrintableString-restricted.
|
|
1856
1366
|
function _reconAttrValue(rdn) {
|
|
1857
|
-
if (rdn.eui64) return b.utf8(_macToEui64String(rdn.eui64));
|
|
1367
|
+
if (rdn.eui64) return b.utf8(_macToEui64String(rdn.eui64));
|
|
1858
1368
|
var s = String(rdn.value);
|
|
1859
|
-
// The attribute's registered ASN.1 type carries a SIZE constraint as well as an alphabet, and a value outside
|
|
1860
|
-
// it is not a valid encoding of that type: every DirectoryString-valued attribute is SIZE (1..MAX) and
|
|
1861
|
-
// emailAddress is IA5String SIZE (1..MAX) (RFC 5280 App. A.1), so an empty value is never valid; countryName
|
|
1862
|
-
// "SHALL have length 2" (draft sec. 3.1.4, X.520 SIZE (2)). Enforced HERE, the single place the value is built
|
|
1863
|
-
// for its declared type, so the decode, reconstruct and subject-directory-attributes paths cannot disagree.
|
|
1864
1369
|
if (s.length === 0) throw _err("c509/bad-name", "a " + rdn.type + " value must be non-empty (SIZE (1..MAX))");
|
|
1865
1370
|
if (rdn.type === "countryName" && s.length !== 2) throw _err("c509/bad-name", "a countryName value must have length 2 (draft sec. 3.1.4)");
|
|
1866
|
-
// emailAddress is an IA5String-only attribute (draft sec. 3.1.4): its type, not the int's sign, fixes the
|
|
1867
|
-
// string type, so it reconstructs as an IA5String whichever way the non-negative int was written.
|
|
1868
1371
|
if (rdn.type === "emailAddress") return b.ia5(s);
|
|
1869
|
-
// serialNumber / countryName carry a CHARACTER restriction, not a string-type override: draft sec. 3.1.4
|
|
1870
|
-
// "SHALL contain only characters from the 74-character ASCII subset permitted by PrintableString". Enforce
|
|
1871
|
-
// that on the CHARACTERS and still honor the sign for the string type. Coercing them to PrintableString
|
|
1872
|
-
// regardless of sign would make the +N and -N encodings of one value reconstruct IDENTICAL DER, so a single
|
|
1873
|
-
// X.509 signature would cover two distinct C509 encodings (a malleability window in the type-3 transform).
|
|
1874
|
-
// b.printable IS the PrintableString charset authority -- run it for the assert even when the sign selects
|
|
1875
|
-
// utf8String, so the restriction binds on the characters without overriding the declared string type.
|
|
1876
1372
|
if (!rdn.printable && (rdn.type === "countryName" || rdn.type === "serialNumber")) b.printable(s);
|
|
1877
1373
|
return rdn.printable ? b.printable(s) : b.utf8(s);
|
|
1878
1374
|
}
|
|
1879
1375
|
|
|
1880
|
-
// A Name -> the DER RDNSequence (SEQUENCE OF SET OF SEQUENCE{ type, value }); one attribute per RDN.
|
|
1881
1376
|
function _reconName(name) {
|
|
1882
1377
|
return b.sequence(name.rdns.map(function (rdn) {
|
|
1883
1378
|
return b.set([b.sequence([b.oid(oid.byName(rdn.type)), _reconAttrValue(rdn)])]);
|
|
1884
1379
|
}));
|
|
1885
1380
|
}
|
|
1886
1381
|
|
|
1887
|
-
// A validity instant -> UTCTime (RFC 5280 sec. 4.1.2.5: year < 2050) or GeneralizedTime. A null notAfter
|
|
1888
|
-
// -> the no-well-defined-expiry sentinel 99991231235959Z.
|
|
1889
1382
|
function _reconTime(date) {
|
|
1890
1383
|
if (date === null) return b.generalizedTime(new Date(Date.UTC(9999, 11, 31, 23, 59, 59)));
|
|
1891
1384
|
return date.getUTCFullYear() < 2050 ? b.utcTime(date) : b.generalizedTime(date);
|
|
1892
1385
|
}
|
|
1893
1386
|
|
|
1894
|
-
// subjectPublicKeyInfo -> DER. EC: rebuild AlgorithmIdentifier{ ecPublicKey, namedCurve } + the BIT
|
|
1895
|
-
// STRING point, de-compressing a C509 0xFE/0xFD marker back to the original uncompressed 0x04||X||Y.
|
|
1896
1387
|
function _reconSpki(spkAlg, keyBytes, rsaKey) {
|
|
1897
1388
|
if (spkAlg.name === "ecPublicKey") {
|
|
1898
1389
|
var fieldSize = EC_FIELD_BYTES[spkAlg.curve];
|
|
1899
1390
|
if (!fieldSize) throw _err("c509/non-invertible", "unsupported EC curve " + spkAlg.curve);
|
|
1900
1391
|
if (!keyBytes || keyBytes.length === 0) throw _err("c509/non-invertible", "the EC subjectPublicKey byte string is empty");
|
|
1901
1392
|
var head = keyBytes[0], point;
|
|
1902
|
-
// The point length must match the curve field size for its encoding: an uncompressed 0x04 point is
|
|
1903
|
-
// 1 + 2*fieldSize, and a compressed 0x02/0x03/0xFE/0xFD point is 1 + fieldSize. A truncated / padded
|
|
1904
|
-
// point cannot be re-emitted as a valid (or byte-exact) SubjectPublicKeyInfo.
|
|
1905
1393
|
if (head === 0x04) {
|
|
1906
1394
|
if (keyBytes.length !== 1 + 2 * fieldSize) throw _err("c509/non-invertible", "uncompressed EC point length " + keyBytes.length + " does not match " + spkAlg.curve);
|
|
1907
1395
|
point = keyBytes;
|
|
1908
1396
|
} else if (head === 0x02 || head === 0x03) {
|
|
1909
1397
|
if (keyBytes.length !== 1 + fieldSize) throw _err("c509/non-invertible", "compressed EC point length " + keyBytes.length + " does not match " + spkAlg.curve);
|
|
1910
1398
|
point = keyBytes;
|
|
1911
|
-
} else if (head === 0xfe || head === 0xfd) {
|
|
1399
|
+
} else if (head === 0xfe || head === 0xfd) {
|
|
1912
1400
|
if (keyBytes.length !== 1 + fieldSize) throw _err("c509/non-invertible", "C509-marked EC point length " + keyBytes.length + " does not match " + spkAlg.curve);
|
|
1913
1401
|
var sec1 = Buffer.concat([Buffer.from([head === 0xfe ? 0x02 : 0x03]), keyBytes.subarray(1)]);
|
|
1914
1402
|
point = webcrypto.decompressEcPoint(sec1, spkAlg.curve, _err, "c509/non-invertible");
|
|
@@ -1916,32 +1404,23 @@ function _reconSpki(spkAlg, keyBytes, rsaKey) {
|
|
|
1916
1404
|
return b.sequence([b.sequence([b.oid(oid.byName("ecPublicKey")), b.oid(oid.byName(spkAlg.curve))]), b.bitString(point, 0)]);
|
|
1917
1405
|
}
|
|
1918
1406
|
if (spkAlg.name === "rsaEncryption") {
|
|
1919
|
-
// draft-20 sec. 3.2.1: the RSA key is [modulus, exponent] ~biguints, OR just the modulus ~biguint
|
|
1920
|
-
// when the exponent is 65537 (parse has already resolved rsaKey to { modulus, exponent }). Reconstruct
|
|
1921
|
-
// AlgorithmIdentifier{ rsaEncryption, NULL } + the BIT STRING wrapping RSAPublicKey ::= SEQUENCE {
|
|
1922
|
-
// modulus INTEGER, publicExponent INTEGER }.
|
|
1923
1407
|
var rsaPk = b.sequence([b.integer(rsaKey.modulus), b.integer(rsaKey.exponent)]);
|
|
1924
1408
|
return b.sequence([b.sequence([b.oid(oid.byName("rsaEncryption")), b.nullValue()]), b.bitString(rsaPk, 0)]);
|
|
1925
1409
|
}
|
|
1926
1410
|
throw _err("c509/non-invertible", "subjectPublicKey algorithm " + spkAlg.name + " is not in the type-3 reconstruction covered set");
|
|
1927
1411
|
}
|
|
1928
1412
|
|
|
1929
|
-
// The keyUsage int value -> the DER KeyUsage BIT STRING (RFC 5280 sec. 4.2.1.3): bit i of the value ->
|
|
1930
|
-
// BIT STRING named bit i (bit 0 = digitalSignature = the MSB of the first content octet). Composes the
|
|
1931
|
-
// shared named-bit content builder (also used by the cRLDistributionPoints reasons transform); a
|
|
1932
|
-
// non-positive, non-integer, or > 0x1FF value is not a valid KeyUsage and fails closed.
|
|
1933
1413
|
function _reconKeyUsageBits(value) {
|
|
1934
1414
|
var c = _namedBitsToContent(value);
|
|
1935
1415
|
if (c == null) throw _err("c509/non-invertible", "a keyUsage value must be a positive integer within the 9 defined bits");
|
|
1936
1416
|
return b.bitString(c.bytes, c.unusedBits);
|
|
1937
1417
|
}
|
|
1938
1418
|
|
|
1939
|
-
// extensions -> the [3] EXPLICIT SEQUENCE OF Extension DER.
|
|
1940
1419
|
function _reconExtensions(exts) {
|
|
1941
1420
|
var items = exts.map(function (ext) {
|
|
1942
1421
|
var extnValue;
|
|
1943
1422
|
if (ext.name === "keyUsage" && typeof ext.keyUsageBits === "number") extnValue = _reconKeyUsageBits(ext.keyUsageBits);
|
|
1944
|
-
else if (Buffer.isBuffer(ext.value)) extnValue = ext.value;
|
|
1423
|
+
else if (Buffer.isBuffer(ext.value)) extnValue = ext.value;
|
|
1945
1424
|
else throw _err("c509/non-invertible", "extension " + ext.name + " has no reconstructable value in the covered set");
|
|
1946
1425
|
var fields = [b.oid(ext.oid || oid.byName(ext.name))];
|
|
1947
1426
|
if (ext.critical) fields.push(b.boolean(true));
|
|
@@ -1951,55 +1430,35 @@ function _reconExtensions(exts) {
|
|
|
1951
1430
|
return b.explicit(3, b.sequence(items));
|
|
1952
1431
|
}
|
|
1953
1432
|
|
|
1954
|
-
// An AlgorithmIdentifier -> DER SEQUENCE { algorithm OID, parameters? }. A C509 [~oid, params]
|
|
1955
|
-
// algorithm carries its DER parameters bytes, which MUST be reproduced so the reconstruction inverts
|
|
1956
|
-
// byte-exact (silently dropping them would change the signed bytes); the int / ~oid forms carry no
|
|
1957
|
-
// parameters (ecdsaWith* and the like omit them).
|
|
1958
1433
|
function _reconAlgId(alg) {
|
|
1959
1434
|
var fields = [b.oid(alg.oid)];
|
|
1960
1435
|
if (alg.parameters && alg.parameters.length) {
|
|
1961
|
-
// AlgorithmIdentifier.parameters is ANY, spliced verbatim, so it gets the same strict gate as every other
|
|
1962
|
-
// raw-ANY splice, not just a framing check. Framing alone admits an empty INTEGER / reserved tag 0 / a
|
|
1963
|
-
// non-minimal INTEGER, each of which reconstructs an AlgorithmIdentifier that an independent X.509 decoder
|
|
1964
|
-
// refuses to load and that this toolkit's own readers reject.
|
|
1965
1436
|
fields.push(b.raw(_requireStrictDerTlv(alg.parameters, "c509/non-invertible", "algorithm parameters")));
|
|
1966
1437
|
}
|
|
1967
1438
|
return b.sequence(fields);
|
|
1968
1439
|
}
|
|
1969
1440
|
|
|
1970
|
-
// The full type-3 -> DER Certificate reconstruction, byte-for-byte.
|
|
1971
1441
|
function _reconstructDer(r, sigNode) {
|
|
1972
1442
|
var sigAlgSeq = _reconAlgId(r.signatureAlgorithm);
|
|
1973
1443
|
var subjectName = _reconName(r.subject);
|
|
1974
1444
|
var spelledIssuer = r.issuer && r.issuer.rdns ? _reconName(r.issuer) : null;
|
|
1975
|
-
// sec. 3.1.4: an issuer identical to the subject MUST be the CBOR simple value null. Spelling it
|
|
1976
|
-
// out instead rebuilds the same DER, so one certificate would have two C509 encodings and the one
|
|
1977
|
-
// X.509 signature over that DER would cover both.
|
|
1978
1445
|
if (spelledIssuer !== null && spelledIssuer.equals(subjectName)) {
|
|
1979
1446
|
throw _err("c509/bad-name", "an issuer identical to the subject must be encoded as the CBOR simple value null (draft sec. 3.1.4)");
|
|
1980
1447
|
}
|
|
1981
1448
|
var tbsFields = [
|
|
1982
|
-
b.explicit(0, b.integer(2n)),
|
|
1449
|
+
b.explicit(0, b.integer(2n)),
|
|
1983
1450
|
b.integer(r.serialNumber),
|
|
1984
1451
|
sigAlgSeq,
|
|
1985
|
-
spelledIssuer === null ? subjectName : spelledIssuer,
|
|
1452
|
+
spelledIssuer === null ? subjectName : spelledIssuer,
|
|
1986
1453
|
b.sequence([_reconTime(r.validity.notBefore), _reconTime(r.validity.notAfter)]),
|
|
1987
1454
|
subjectName,
|
|
1988
1455
|
_reconSpki(r.subjectPublicKeyAlgorithm, r.subjectPublicKey, r.rsaPublicKey),
|
|
1989
1456
|
];
|
|
1990
|
-
// RFC 5280 sec. 4.1: the [3] extensions field is OPTIONAL and, when present, SHALL contain at least one
|
|
1991
|
-
// extension, so an empty C509 extensions array reconstructs to an OMITTED field, not an empty SEQUENCE.
|
|
1992
1457
|
if (r.extensions.length) tbsFields.push(_reconExtensions(r.extensions));
|
|
1993
1458
|
var tbs = b.sequence(tbsFields);
|
|
1994
|
-
// The signature is re-wrapped as a DER ECDSA-Sig-Value from the fixed-width r||s, so only an ECDSA
|
|
1995
|
-
// signature algorithm is in the type-3 reconstruction covered set (an RSA/EdDSA signature is raw bytes,
|
|
1996
|
-
// not r||s, so it is rejected). A wrong-length r||s surfaces the caller's typed code.
|
|
1997
1459
|
if (String(r.signatureAlgorithm.name || "").toLowerCase().indexOf("ecdsa") !== 0) {
|
|
1998
1460
|
throw _err("c509/non-invertible", "type-3 signature reconstruction covers only ECDSA; got " + r.signatureAlgorithm.name);
|
|
1999
1461
|
}
|
|
2000
|
-
// The fixed-width r||s must split at a real curve field width: P-256/384/521 = 64/96/132 bytes
|
|
2001
|
-
// (RFC 9053 sec. 2.1). A width that is not 2x a supported field size is not a valid ECDSA signature and
|
|
2002
|
-
// cannot be re-wrapped byte-exact, so it surfaces the caller's typed code.
|
|
2003
1462
|
var coordLen = r.signatureValue.length / 2;
|
|
2004
1463
|
if (coordLen !== 32 && coordLen !== 48 && coordLen !== 66) {
|
|
2005
1464
|
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)");
|
|
@@ -2008,7 +1467,6 @@ function _reconstructDer(r, sigNode) {
|
|
|
2008
1467
|
return b.sequence([tbs, sigAlgSeq, b.bitString(sigValue, 0)]);
|
|
2009
1468
|
}
|
|
2010
1469
|
|
|
2011
|
-
// ---- the parse ----
|
|
2012
1470
|
|
|
2013
1471
|
/**
|
|
2014
1472
|
* @primitive pki.schema.c509.parse
|
|
@@ -2041,8 +1499,6 @@ function parse(input) {
|
|
|
2041
1499
|
var f = root.children;
|
|
2042
1500
|
if (f.length !== 11) throw _err("c509/bad-tbs", "a C509 certificate must be an array of exactly 11 elements, got " + f.length);
|
|
2043
1501
|
|
|
2044
|
-
// The type slot must be a CBOR integer -- guard the major type here so a text/array/byte-string field 0 fails
|
|
2045
|
-
// in this module's domain rather than leaking cbor.read.int's cbor/unexpected-major fault to the caller.
|
|
2046
1502
|
if (f[0].majorType !== 0 && f[0].majorType !== 1) throw _err("c509/bad-certificate-type", "c509CertificateType must be a CBOR integer");
|
|
2047
1503
|
var type = Number(cbor.read.int(f[0]));
|
|
2048
1504
|
if (type !== 2 && type !== 3) throw _err("c509/bad-certificate-type", "c509CertificateType must be 2 (native) or 3 (re-encoded), got " + type);
|
|
@@ -2056,16 +1512,12 @@ function parse(input) {
|
|
|
2056
1512
|
var notBefore = _time(f[4], false, "validityNotBefore");
|
|
2057
1513
|
var notAfter = _time(f[5], true, "validityNotAfter");
|
|
2058
1514
|
var subject = _name509(f[6], true, type === 2);
|
|
2059
|
-
// A CBOR-null issuer means issuer == subject (self-signed), so the EFFECTIVE issuer is the subject -- and it
|
|
2060
|
-
// must still satisfy RFC 5280 sec. 4.1.2.4. Checking only the array form would let the null form rebuild the
|
|
2061
|
-
// very empty issuer the array form is refused for.
|
|
2062
1515
|
if (issuer === null && (!subject || !subject.rdns || subject.rdns.length === 0)) {
|
|
2063
1516
|
throw _err("c509/bad-name", "a self-signed C509 (issuer == subject) requires a non-empty subject, since it is also the issuer (RFC 5280 sec. 4.1.2.4)");
|
|
2064
1517
|
}
|
|
2065
1518
|
var spkAlg = _algorithm(f[7], PK_ALG_BY_INT, "c509/unknown-algorithm", "subjectPublicKeyAlgorithm");
|
|
2066
1519
|
var subjectPublicKey = null, rsaKey = null;
|
|
2067
1520
|
if (spkAlg.name === "rsaEncryption") {
|
|
2068
|
-
// draft-20 sec. 3.2.1: [modulus, exponent] ~biguints, OR a bare modulus ~biguint (exponent = 65537).
|
|
2069
1521
|
if (f[8].majorType === 2) rsaKey = { modulus: _biguint(f[8], "c509/bad-spki", "RSA modulus"), exponent: 65537n };
|
|
2070
1522
|
else if (f[8].majorType === 4 && f[8].children && f[8].children.length === 2) {
|
|
2071
1523
|
rsaKey = { modulus: _biguint(f[8].children[0], "c509/bad-spki", "RSA modulus"), exponent: _biguint(f[8].children[1], "c509/bad-spki", "RSA exponent") };
|
|
@@ -2094,42 +1546,21 @@ function parse(input) {
|
|
|
2094
1546
|
signatureValue: signatureValue,
|
|
2095
1547
|
};
|
|
2096
1548
|
|
|
2097
|
-
// The RAW bytes of CBOR-array elements 0..9 (NOT the outer array head, NOT the signature) -- a zero-copy
|
|
2098
|
-
// subarray. For a native (type-2) certificate this is the signed region a verifier hashes (sec. 3.1.12);
|
|
2099
|
-
// surfaced as `_fieldBytes` for BOTH types so encode() re-emits a parsed certificate byte-for-byte (a
|
|
2100
|
-
// re-derivation from the decoded values could differ on a canonical-equivalent form, breaking a type-2
|
|
2101
|
-
// signature or a type-3 DER reconstruction). `signedData` keeps its type-2-only signed-region meaning.
|
|
2102
|
-
// From the DECODED root's own bytes (root.bytes == array head + all 11 elements), not offset arithmetic
|
|
2103
|
-
// on `input` (which breaks when input is a Uint8Array the codec normalized to a different buffer): the
|
|
2104
|
-
// fields region is root.bytes minus the 1-byte array(11) head and minus the trailing signatureValue.
|
|
2105
1549
|
result._fieldBytes = root.bytes.subarray(1, root.bytes.length - f[10].bytes.length);
|
|
2106
1550
|
if (type === 2) result.signedData = result._fieldBytes;
|
|
2107
1551
|
|
|
2108
|
-
// Type-3 is an invertible re-encoding of a DER X.509 certificate: reconstruct the original DER
|
|
2109
|
-
// byte-for-byte so the original signature verifies and x509.parse recovers the certificate.
|
|
2110
1552
|
if (type === 3) result.reconstructedDer = _reconstructDer(result, f[10]);
|
|
2111
1553
|
|
|
2112
1554
|
return result;
|
|
2113
1555
|
}
|
|
2114
1556
|
|
|
2115
|
-
// matches(node) -- a STRUCTURAL probe over a DECODED CBOR node: an array of 11 whose first element is
|
|
2116
|
-
// a major-type-0/1 int equal to 2 or 3. Not wired into the DER orchestrator (C509 is CBOR, not DER).
|
|
2117
1557
|
function matches(node) {
|
|
2118
1558
|
return !!node && node.majorType === 4 && !!node.children && node.children.length === 11 &&
|
|
2119
1559
|
(node.children[0].majorType === 0 || node.children[0].majorType === 1) &&
|
|
2120
1560
|
(Number(node.children[0].argument) === 2 || Number(node.children[0].argument) === 3);
|
|
2121
1561
|
}
|
|
2122
1562
|
|
|
2123
|
-
// ---- the encode (the producing side; draft-20 sec. 3) -----------------------
|
|
2124
|
-
// The byte-exact inverse of parse/reconstruct: emit the 11-element deterministic-CBOR C509 array via the
|
|
2125
|
-
// cbor.build.* emitter. Two inputs, dispatched structurally: a DER X.509 certificate (Buffer/PEM) -> the
|
|
2126
|
-
// FLAGSHIP type-3 forward transform (parse(encode(der)).reconstructedDer == der, so the original signature
|
|
2127
|
-
// verifies); a c509.parse result -> re-emit its native array. Signing-free (mirrors ct.encodeSctList).
|
|
2128
1563
|
|
|
2129
|
-
// The registry INVERSE tables (name -> canonical int). The RDN-attribute and extension inverses are
|
|
2130
|
-
// DERIVED from the *_BY_INT decode tables so the two directions cannot drift apart (a hand-kept inverse
|
|
2131
|
-
// once carried a stale pre-draft-20 numbering); the extension inverse keeps only the compact-encodable
|
|
2132
|
-
// rows, so a registered extension without a value codec (subjectAltName) routes to the ~oid form.
|
|
2133
1564
|
var SIG_ALG_TO_INT = { ecdsaWithSHA256: 0, ecdsaWithSHA384: 1, ecdsaWithSHA512: 2 };
|
|
2134
1565
|
var PK_ALG_TO_INT = { rsaEncryption: 0, "ecPublicKey|prime256v1": 1, "ecPublicKey|secp384r1": 2, "ecPublicKey|secp521r1": 3 };
|
|
2135
1566
|
var ATTR_TO_INT = {};
|
|
@@ -2137,40 +1568,28 @@ Object.keys(ATTR_BY_INT).forEach(function (k) { ATTR_TO_INT[ATTR_BY_INT[k]] = Nu
|
|
|
2137
1568
|
var EXT_TO_INT = {};
|
|
2138
1569
|
Object.keys(EXT_BY_INT).forEach(function (k) { if (EXT_COMPACT[EXT_BY_INT[k]]) EXT_TO_INT[EXT_BY_INT[k]] = Number(k); });
|
|
2139
1570
|
|
|
2140
|
-
// A non-negative BigInt -> its minimal big-endian ~biguint bytes (the leading 0x00 sign octet omitted).
|
|
2141
1571
|
function _minBytes(n) {
|
|
2142
1572
|
if (n < 0n) throw _err("c509/bad-serial", "a ~biguint value must be non-negative");
|
|
2143
1573
|
if (n === 0n) return Buffer.alloc(0);
|
|
2144
1574
|
var hex = n.toString(16); if (hex.length % 2) hex = "0" + hex;
|
|
2145
1575
|
return Buffer.from(hex, "hex");
|
|
2146
1576
|
}
|
|
2147
|
-
// A C509 AlgorithmIdentifier -> int (registry) | ~oid (bare bytes) | [~oid, params]. `key` selects the row.
|
|
2148
|
-
// The bare-~oid arm is unreachable from either call site as the type-3 encoder stands, and is kept as
|
|
2149
|
-
// the honest fallback rather than an assumption: the signature slot refuses every non-ECDSA algorithm
|
|
2150
|
-
// before this runs, and every curve the subjectPublicKey slot accepts (prime256v1 / secp384r1 /
|
|
2151
|
-
// secp521r1) carries an ecPublicKey registry row. Adding a curve without its row would reach it.
|
|
2152
1577
|
function _encAlgorithm(alg, toInt, key) {
|
|
2153
1578
|
var i = toInt[key];
|
|
2154
1579
|
if (i !== undefined && !(alg.parameters && alg.parameters.length)) return cbor.build.int(BigInt(i));
|
|
2155
|
-
var oidBytes = cbor.build.byteString(asn1.encodeOidContent(alg.oid));
|
|
1580
|
+
var oidBytes = cbor.build.byteString(asn1.encodeOidContent(alg.oid));
|
|
2156
1581
|
if (alg.parameters && alg.parameters.length) return cbor.build.array([oidBytes, cbor.build.byteString(alg.parameters)]);
|
|
2157
1582
|
return oidBytes;
|
|
2158
1583
|
}
|
|
2159
|
-
// A SpecialText attribute value -> CBOR, walking the sec. 3.1.4 cascade: an EUI-64 is a tag-48 MAC
|
|
2160
|
-
// address, a text string of even length >= 2 drawn only from '0'-'9'/'a'-'f' is a byte string, and
|
|
2161
|
-
// anything else is a text string. The decoder holds an incoming certificate to exactly this cascade,
|
|
2162
|
-
// so the encoder walks it too -- a spelling chosen here that the decoder refuses would make this
|
|
2163
|
-
// encoder emit certificates its own parser rejects.
|
|
2164
1584
|
function _encSpecialText(rdn) {
|
|
2165
1585
|
if (rdn.eui64) return cbor.build.tag(48, cbor.build.byteString(rdn.eui64));
|
|
2166
1586
|
var v = String(rdn.value);
|
|
2167
1587
|
if (v.length >= 2 && _isEvenHexLower(v)) return cbor.build.byteString(Buffer.from(v, "hex"));
|
|
2168
1588
|
return cbor.build.textString(v);
|
|
2169
1589
|
}
|
|
2170
|
-
// A Name -> CBOR: null (issuer only) | a bare SpecialText single utf8 commonName | an array of RDN pairs.
|
|
2171
1590
|
function _encName(name, isSubject) {
|
|
2172
1591
|
if (name === null || name === undefined) {
|
|
2173
|
-
if (!isSubject) return cbor.build.nullValue();
|
|
1592
|
+
if (!isSubject) return cbor.build.nullValue();
|
|
2174
1593
|
throw _err("c509/bad-name", "the subject Name is required");
|
|
2175
1594
|
}
|
|
2176
1595
|
var rdns = name.rdns || [];
|
|
@@ -2179,12 +1598,11 @@ function _encName(name, isSubject) {
|
|
|
2179
1598
|
rdns.forEach(function (rdn) {
|
|
2180
1599
|
var ai = ATTR_TO_INT[rdn.type];
|
|
2181
1600
|
if (ai === undefined) throw _err("c509/bad-name", "attribute type " + rdn.type + " has no C509 registry int");
|
|
2182
|
-
items.push(cbor.build.int(BigInt(rdn.printable ? -ai : ai)));
|
|
1601
|
+
items.push(cbor.build.int(BigInt(rdn.printable ? -ai : ai)));
|
|
2183
1602
|
items.push(_encSpecialText(rdn));
|
|
2184
1603
|
});
|
|
2185
1604
|
return cbor.build.array(items);
|
|
2186
1605
|
}
|
|
2187
|
-
// subjectPublicKey -> CBOR: EC point byte string, or an RSA ~biguint modulus ([modulus, exponent] when e != 65537).
|
|
2188
1606
|
function _encSpk(r) {
|
|
2189
1607
|
if (r.rsaPublicKey) {
|
|
2190
1608
|
var mod = cbor.build.byteString(_minBytes(r.rsaPublicKey.modulus));
|
|
@@ -2194,7 +1612,6 @@ function _encSpk(r) {
|
|
|
2194
1612
|
if (!Buffer.isBuffer(r.subjectPublicKey)) throw _err("c509/bad-spki", "the subjectPublicKey bytes are missing");
|
|
2195
1613
|
return cbor.build.byteString(r.subjectPublicKey);
|
|
2196
1614
|
}
|
|
2197
|
-
// extensions -> CBOR: the keyUsage int-shortcut (a lone keyUsage), else an array of [extID, extValue] pairs.
|
|
2198
1615
|
function _encExtensions(exts) {
|
|
2199
1616
|
if (exts.length === 1 && exts[0].name === "keyUsage" && typeof exts[0].keyUsageBits === "number") {
|
|
2200
1617
|
return cbor.build.int(BigInt(exts[0].critical ? -exts[0].keyUsageBits : exts[0].keyUsageBits));
|
|
@@ -2202,26 +1619,20 @@ function _encExtensions(exts) {
|
|
|
2202
1619
|
var items = [];
|
|
2203
1620
|
exts.forEach(function (ext) {
|
|
2204
1621
|
var ei = EXT_TO_INT[ext.name];
|
|
2205
|
-
// A registered extension with a compact value form emits int extID + the specific CBOR value, but
|
|
2206
|
-
// only when that value inverts to the EXACT DER extnValue (the round-trip guard); otherwise it falls
|
|
2207
|
-
// through to the conformant ~oid + byte-string form (sec. 3.7) -- never a lossy compact encoding.
|
|
2208
1622
|
var compact = (ei !== undefined && Buffer.isBuffer(ext.value)) ? _tryCompactExtValue(ext.name, ext.value) : null;
|
|
2209
1623
|
if (compact != null) {
|
|
2210
1624
|
items.push(cbor.build.int(BigInt(ext.critical ? -ei : ei)));
|
|
2211
1625
|
items.push(compact);
|
|
2212
1626
|
} else {
|
|
2213
|
-
items.push(cbor.build.byteString(asn1.encodeOidContent(ext.oid || oid.byName(ext.name))));
|
|
1627
|
+
items.push(cbor.build.byteString(asn1.encodeOidContent(ext.oid || oid.byName(ext.name))));
|
|
2214
1628
|
if (!Buffer.isBuffer(ext.value)) throw _err("c509/non-invertible", "extension " + (ext.oid || ext.name) + " has no byte-string value to encode");
|
|
2215
1629
|
var bs = cbor.build.byteString(ext.value);
|
|
2216
|
-
items.push(ext.critical ? cbor.build.array([bs]) : bs);
|
|
1630
|
+
items.push(ext.critical ? cbor.build.array([bs]) : bs);
|
|
2217
1631
|
}
|
|
2218
1632
|
});
|
|
2219
1633
|
return cbor.build.array(items);
|
|
2220
1634
|
}
|
|
2221
|
-
// forward-declared below; the DER X.509 -> type-3 C509 structured result.
|
|
2222
1635
|
var _derToType3;
|
|
2223
|
-
// A hand-built (no _fieldBytes) result must carry the structured fields the encode below reads; a missing or
|
|
2224
|
-
// wrong-typed field fails closed with a typed verdict rather than a raw property-access crash.
|
|
2225
1636
|
function _requireResultShape(r) {
|
|
2226
1637
|
if (r.certificateType == null) throw _err("c509/bad-input", "a C509 result must carry certificateType");
|
|
2227
1638
|
if (r.serialNumber == null && r.serialNumberHex == null) throw _err("c509/bad-input", "a C509 result must carry serialNumber or serialNumberHex");
|
|
@@ -2231,24 +1642,16 @@ function _requireResultShape(r) {
|
|
|
2231
1642
|
if (!Array.isArray(r.extensions)) throw _err("c509/bad-input", "a C509 result must carry an extensions array");
|
|
2232
1643
|
if (!Buffer.isBuffer(r.signatureValue)) throw _err("c509/bad-input", "a C509 result must carry a Buffer signatureValue");
|
|
2233
1644
|
}
|
|
2234
|
-
// A validity Date -> its C509 ~time (a non-negative CBOR epoch uint). A pre-epoch date cannot be
|
|
2235
|
-
// represented (the parser accepts only an unwrapped major-type-0 integer) and fails closed here.
|
|
2236
1645
|
function _validityUint(date, label) {
|
|
2237
1646
|
var secs = Math.floor(guard.time.instantOf(date) / 1000);
|
|
2238
1647
|
if (!isFinite(secs) || secs < 0) throw _err("c509/bad-validity", label + " is before the Unix epoch or not a valid date; C509 ~time is a non-negative CBOR epoch");
|
|
2239
1648
|
return cbor.build.uint(BigInt(secs));
|
|
2240
1649
|
}
|
|
2241
|
-
// A structured C509 result -> the 11-element deterministic-CBOR array.
|
|
2242
1650
|
function _encodeC509Array(r) {
|
|
2243
|
-
// Re-emit a PARSED certificate's raw fields (elements 0..9) VERBATIM -- re-deriving from the decoded
|
|
2244
|
-
// values could differ on a canonical-equivalent form (a byte-string attribute value, a registry alias)
|
|
2245
|
-
// and break a type-2 native signature (which covers these bytes) or a type-3 DER reconstruction (which
|
|
2246
|
-
// depends on the field values). Both types preserve the exact bytes; only a hand-built result (no
|
|
2247
|
-
// _fieldBytes) re-derives from the structured values below.
|
|
2248
1651
|
if (Buffer.isBuffer(r._fieldBytes)) {
|
|
2249
1652
|
if (!Buffer.isBuffer(r.signatureValue)) throw _err("c509/bad-input", "a re-emitted certificate must carry a Buffer signatureValue");
|
|
2250
|
-
var out = Buffer.concat([Buffer.from([0x8b]), r._fieldBytes, cbor.build.byteString(r.signatureValue)]);
|
|
2251
|
-
parse(out);
|
|
1653
|
+
var out = Buffer.concat([Buffer.from([0x8b]), r._fieldBytes, cbor.build.byteString(r.signatureValue)]);
|
|
1654
|
+
parse(out);
|
|
2252
1655
|
return out;
|
|
2253
1656
|
}
|
|
2254
1657
|
_requireResultShape(r);
|
|
@@ -2266,7 +1669,7 @@ function _encodeC509Array(r) {
|
|
|
2266
1669
|
_encExtensions(r.extensions),
|
|
2267
1670
|
cbor.build.byteString(r.signatureValue),
|
|
2268
1671
|
]);
|
|
2269
|
-
parse(arr);
|
|
1672
|
+
parse(arr);
|
|
2270
1673
|
return arr;
|
|
2271
1674
|
}
|
|
2272
1675
|
|
|
@@ -2310,11 +1713,6 @@ function _encodeC509Array(r) {
|
|
|
2310
1713
|
* var cbor = pki.schema.c509.encode(signerCertDer); // a DER cert -> a compact type-3 C509
|
|
2311
1714
|
* pki.schema.c509.parse(cbor).certificateType; // 3
|
|
2312
1715
|
*/
|
|
2313
|
-
// A commonName string that is a MAC / EUI address ("HH-HH-HH-FF-FE-HH-HH-HH") -> the C509 tag-48 byte
|
|
2314
|
-
// value (draft-20 sec. 3.2.3): an FF-FE-in-the-middle EUI-64 collapses to its 6-byte EUI-48, else the
|
|
2315
|
-
// 8-byte EUI-64 verbatim. A non-MAC commonName returns null (encoded as text). The exact inverse of
|
|
2316
|
-
// _macToEui64String, so the reconstruction rebuilds the identical DER commonName string.
|
|
2317
|
-
// The "-" characters removed from a string, by character scan (no regex replace protocol).
|
|
2318
1716
|
function _stripHyphens(s) {
|
|
2319
1717
|
var out = "";
|
|
2320
1718
|
for (var i = 0; i < s.length; i++) { var ch = s.charAt(i); if (ch !== "-") out += ch; }
|
|
@@ -2326,8 +1724,6 @@ function _euiFromCn(value) {
|
|
|
2326
1724
|
if (bytes[3] === 0xff && bytes[4] === 0xfe) return Buffer.concat([bytes.subarray(0, 3), bytes.subarray(5)]);
|
|
2327
1725
|
return bytes;
|
|
2328
1726
|
}
|
|
2329
|
-
// A DER Name -> the C509 structured rdns, decoding each attribute's string type (PrintableString ->
|
|
2330
|
-
// printable, UTF8String -> utf8; the C509 int sign carries this). Single-attribute RDNs only (v1).
|
|
2331
1727
|
function _c509NameFromDer(nameBytes) {
|
|
2332
1728
|
var node = asn1.decode(nameBytes);
|
|
2333
1729
|
var rdns = [];
|
|
@@ -2337,47 +1733,35 @@ function _c509NameFromDer(nameBytes) {
|
|
|
2337
1733
|
var attrName = oid.name(asn1.read.oid(attr.children[0]));
|
|
2338
1734
|
if (attrName == null || ATTR_TO_INT[attrName] === undefined) throw _err("c509/non-invertible", "attribute type " + attrName + " has no C509 registry integer");
|
|
2339
1735
|
var valNode = attr.children[1];
|
|
2340
|
-
// A value whose string type this codec cannot represent (NumericString and the other non-DirectoryString
|
|
2341
|
-
// types read.string declines) is NOT compact-representable -- report that in THIS module's domain rather
|
|
2342
|
-
// than leaking the codec's own asn1/* fault out of encode(), which is what every sibling shape does.
|
|
2343
1736
|
var value;
|
|
2344
1737
|
try { value = asn1.read.string(valNode); }
|
|
2345
1738
|
catch (e) { throw _err("c509/non-invertible", "attribute " + attrName + " carries a value whose string type the C509 sec. 8.6 int form cannot represent", e); }
|
|
2346
|
-
// The IA5String type belongs ONLY to an IA5-only attribute (emailAddress, draft sec. 3.1.4), whose value
|
|
2347
|
-
// reconstructs from its type rather than the int's sign. Refuse either mismatch here with a precise verdict
|
|
2348
|
-
// instead of emitting an int form whose reconstruction would differ from the source bytes.
|
|
2349
1739
|
var isIa5 = valNode.tagClass === "universal" && valNode.tagNumber === asn1.TAGS.IA5_STRING;
|
|
2350
1740
|
if ((attrName === "emailAddress") !== isIa5) throw _err("c509/non-invertible", "attribute " + attrName + " carries a " + (isIa5 ? "IA5String" : "non-IA5String") + " value the C509 sec. 8.6 int form cannot represent");
|
|
2351
1741
|
var eui = attrName === "commonName" ? _euiFromCn(value) : null;
|
|
2352
|
-
if (eui) rdns.push({ type: attrName, value: value, eui64: eui });
|
|
1742
|
+
if (eui) rdns.push({ type: attrName, value: value, eui64: eui });
|
|
2353
1743
|
else rdns.push({ type: attrName, value: value, printable: valNode.tagNumber === asn1.TAGS.PRINTABLE_STRING });
|
|
2354
1744
|
});
|
|
2355
1745
|
return { rdns: rdns };
|
|
2356
1746
|
}
|
|
2357
|
-
// A keyUsage extnValue (a DER KeyUsage BIT STRING) -> the C509 integer whose bit i is the named bit i.
|
|
2358
|
-
// Composes the shared named-bit reader (also used by the cRLDistributionPoints reasons transform).
|
|
2359
1747
|
function _keyUsageBitsFromDer(extnValue) {
|
|
2360
1748
|
var bs;
|
|
2361
1749
|
try {
|
|
2362
1750
|
bs = asn1.read.bitString(asn1.decode(extnValue));
|
|
2363
1751
|
} catch (_e) {
|
|
2364
|
-
return null;
|
|
1752
|
+
return null;
|
|
2365
1753
|
}
|
|
2366
1754
|
return _namedBitsFromContent(bs.bytes, bs.unusedBits);
|
|
2367
1755
|
}
|
|
2368
1756
|
var _NO_EXPIRY = Date.UTC(9999, 11, 31, 23, 59, 59);
|
|
2369
|
-
// The C509 (OID/node) curve names <-> the WebCrypto namedCurve the P1363 converter expects, and the
|
|
2370
|
-
// RFC 5480 standard digest<->curve pairing an ECDSA signature algorithm implies.
|
|
2371
1757
|
var NODE_TO_WEBCRYPTO = { "prime256v1": "P-256", "secp384r1": "P-384", "secp521r1": "P-521" };
|
|
2372
1758
|
var WEBCRYPTO_FIELD_BYTES = { "P-256": 32, "P-384": 48, "P-521": 66 };
|
|
2373
1759
|
var SIG_ALG_TO_CURVE = { "ecdsaWithSHA256": "P-256", "ecdsaWithSHA384": "P-384", "ecdsaWithSHA512": "P-521" };
|
|
2374
|
-
// A DER INTEGER magnitude byte length: the content past any leading sign/pad octet.
|
|
2375
1760
|
function _magBytes(intNode) {
|
|
2376
1761
|
var c = intNode.content, i = 0;
|
|
2377
1762
|
while (i < c.length - 1 && c[i] === 0x00) i++;
|
|
2378
1763
|
return c.length - i;
|
|
2379
1764
|
}
|
|
2380
|
-
// The max r/s magnitude width of a DER ECDSA signature (validating its two-INTEGER shape).
|
|
2381
1765
|
function _sigMagWidth(derSig) {
|
|
2382
1766
|
var n;
|
|
2383
1767
|
try { n = asn1.decode(derSig); } catch (e) { throw _err("c509/bad-signature", "the ECDSA issuer signature is not valid DER", e); }
|
|
@@ -2388,21 +1772,11 @@ function _sigMagWidth(derSig) {
|
|
|
2388
1772
|
}
|
|
2389
1773
|
return Math.max(_magBytes(n.children[0]), _magBytes(n.children[1]));
|
|
2390
1774
|
}
|
|
2391
|
-
var CURVE_ORDER = ["P-256", "P-384", "P-521"];
|
|
2392
|
-
// The smallest supported curve whose field holds a magnitude, or null if none does.
|
|
1775
|
+
var CURVE_ORDER = ["P-256", "P-384", "P-521"];
|
|
2393
1776
|
function _minCurveForMag(mag) {
|
|
2394
1777
|
for (var i = 0; i < CURVE_ORDER.length; i++) { if (mag <= WEBCRYPTO_FIELD_BYTES[CURVE_ORDER[i]]) return CURVE_ORDER[i]; }
|
|
2395
1778
|
return null;
|
|
2396
1779
|
}
|
|
2397
|
-
// The ISSUER signature's fixed-width r||s is sized by the ISSUER's signing curve. The DER ECDSA signature
|
|
2398
|
-
// does NOT carry the curve and the r/s magnitudes are only a lower bound (a P-384 signature with small r/s
|
|
2399
|
-
// is byte-indistinguishable from a P-256 one), so the curve is resolved from an AUTHORITATIVE source, never
|
|
2400
|
-
// a guess: (1) an explicit opts.issuerCurve, or (2) the RFC 5480 standard digest<->curve pairing the
|
|
2401
|
-
// signature algorithm implies -- and ONLY when that pairing is the smallest curve the magnitude admits.
|
|
2402
|
-
// Matching issuer/subject Names are NOT treated as proof of self-signing (a self-issued certificate may be
|
|
2403
|
-
// cross-signed by a different key on a different curve). A signature whose r/s exceed the digest's standard
|
|
2404
|
-
// curve (a larger key) OR whose magnitude also fits a SMALLER curve (a smaller key signing with a larger
|
|
2405
|
-
// digest) leaves the curve undetermined: it fails closed and directs the caller to opts.issuerCurve.
|
|
2406
1780
|
function _resolveIssuerSigCurve(c, opts) {
|
|
2407
1781
|
var mag = _sigMagWidth(c.signatureValue.bytes), curve;
|
|
2408
1782
|
if (opts && opts.issuerCurve != null) {
|
|
@@ -2420,27 +1794,15 @@ function _resolveIssuerSigCurve(c, opts) {
|
|
|
2420
1794
|
if (mag > WEBCRYPTO_FIELD_BYTES[curve]) throw _err("c509/non-invertible", "the ECDSA signature r/s width " + mag + " does not fit the resolved " + curve + " field");
|
|
2421
1795
|
return curve;
|
|
2422
1796
|
}
|
|
2423
|
-
// Compress an uncompressed SEC1 EC point (0x04||X||Y) to the C509 marker form (draft-20 sec. 3.2.2): the
|
|
2424
|
-
// sign-of-Y marker 0xFE (Y even) / 0xFD (Y odd) followed by X. The inverse of webcrypto.decompressEcPoint,
|
|
2425
|
-
// so the type-3 reconstruction recovers the exact original point. A non-0x04 point is kept verbatim.
|
|
2426
1797
|
function _compressEcPoint(point, coordLen) {
|
|
2427
1798
|
if (!point.length || point[0] !== 0x04) return point;
|
|
2428
1799
|
if (point.length !== 1 + 2 * coordLen) throw _err("c509/non-invertible", "uncompressed EC point length " + point.length + " does not match the curve field size");
|
|
2429
1800
|
var x = point.subarray(1, 1 + coordLen), y = point.subarray(1 + coordLen);
|
|
2430
1801
|
return Buffer.concat([Buffer.from([(y[y.length - 1] & 1) ? 0xfd : 0xfe]), x]);
|
|
2431
1802
|
}
|
|
2432
|
-
// A DER X.509 v3 certificate -> the type-3 C509 structured result (the inverse of _reconstructDer). Only
|
|
2433
|
-
// the reconstruction's covered set is invertible; encode() self-verifies the byte-exact round trip.
|
|
2434
1803
|
_derToType3 = function (input, opts) {
|
|
2435
1804
|
var c;
|
|
2436
1805
|
try { c = x509.parse(input); } catch (e) { throw _err("c509/bad-input", "the input is not a valid X.509 certificate", e); }
|
|
2437
|
-
// Both C509 certificate types are defined over X.509 v3 (draft-ietf-cose-cbor-encoded-cert
|
|
2438
|
-
// sec. 1), and the encoding carries no version field -- reconstruction always emits v3. So a v1
|
|
2439
|
-
// or v2 certificate is outside the format, not a codec limitation, and saying so here keeps it
|
|
2440
|
-
// from falling through to the byte-compare below, whose "does not reconstruct byte-for-byte"
|
|
2441
|
-
// reads as a defect in this encoder rather than a certificate the format does not cover.
|
|
2442
|
-
// (A v3 certificate with the extensions field OMITTED is fully supported: sec. 3.1.10 encodes
|
|
2443
|
-
// an omitted 'extensions' field as an empty CBOR array.)
|
|
2444
1806
|
if (c.version !== 3) throw _err("c509/non-invertible", "C509 covers X.509 v3 certificates; got v" + c.version);
|
|
2445
1807
|
if (String(c.signatureAlgorithm.name || "").toLowerCase().indexOf("ecdsa") !== 0) throw _err("c509/non-invertible", "type-3 C509 encoding covers only ECDSA-signed certificates; got " + (c.signatureAlgorithm.name || "an unregistered algorithm"));
|
|
2446
1808
|
if (c.subjectPublicKeyInfo.algorithm.name !== "ecPublicKey") throw _err("c509/non-invertible", "type-3 C509 encoding covers only EC (ecPublicKey) certificates in v1; got " + (c.subjectPublicKeyInfo.algorithm.name || "an unregistered algorithm"));
|
|
@@ -2448,27 +1810,21 @@ _derToType3 = function (input, opts) {
|
|
|
2448
1810
|
var curve = oid.name(curveOid);
|
|
2449
1811
|
var coordLen = EC_FIELD_BYTES[curve];
|
|
2450
1812
|
if (!coordLen) throw _err("c509/non-invertible", "unsupported EC subject curve " + (curve || curveOid));
|
|
2451
|
-
var sigCurve = _resolveIssuerSigCurve(c, opts);
|
|
1813
|
+
var sigCurve = _resolveIssuerSigCurve(c, opts);
|
|
2452
1814
|
var spkiNode = asn1.decode(c.subjectPublicKeyInfo.bytes);
|
|
2453
1815
|
return {
|
|
2454
1816
|
certificateType: 3,
|
|
2455
|
-
serialNumber: c.serialNumber,
|
|
1817
|
+
serialNumber: c.serialNumber,
|
|
2456
1818
|
signatureAlgorithm: { name: c.signatureAlgorithm.name, oid: c.signatureAlgorithm.oid },
|
|
2457
|
-
// sec. 3.1.4: "If the 'issuer' field is identical to the 'subject' field, e.g., in case of
|
|
2458
|
-
// self-signed certificates, then the 'issuer' field MUST be encoded as the CBOR simple value
|
|
2459
|
-
// null." Compared on the RAW DER bytes, not on a canonicalized name: the reconstruction rebuilds
|
|
2460
|
-
// a null issuer FROM the subject, so two names that merely compare equal (a PrintableString
|
|
2461
|
-
// against a UTF8String of the same characters) would rebuild different bytes and break the
|
|
2462
|
-
// signature that covers them. Byte-identical is exactly the condition under which that is safe.
|
|
2463
1819
|
issuer: c.issuer.bytes.equals(c.subject.bytes) ? null : _c509NameFromDer(c.issuer.bytes),
|
|
2464
1820
|
validity: { notBefore: c.validity.notBefore, notAfter: guard.time.instantOf(c.validity.notAfter) === _NO_EXPIRY ? null : c.validity.notAfter },
|
|
2465
1821
|
subject: _c509NameFromDer(c.subject.bytes),
|
|
2466
1822
|
subjectPublicKeyAlgorithm: { name: "ecPublicKey", oid: c.subjectPublicKeyInfo.algorithm.oid, curve: curve },
|
|
2467
|
-
subjectPublicKey: _compressEcPoint(asn1.read.bitString(spkiNode.children[1]).bytes, coordLen),
|
|
1823
|
+
subjectPublicKey: _compressEcPoint(asn1.read.bitString(spkiNode.children[1]).bytes, coordLen),
|
|
2468
1824
|
rsaPublicKey: null,
|
|
2469
1825
|
extensions: (c.extensions || []).map(function (e) {
|
|
2470
1826
|
var ext = { name: e.name, oid: e.oid, critical: !!e.critical, value: e.value };
|
|
2471
|
-
if (e.name === "keyUsage") { var bits = _keyUsageBitsFromDer(e.value); if (bits != null) ext.keyUsageBits = bits; }
|
|
1827
|
+
if (e.name === "keyUsage") { var bits = _keyUsageBitsFromDer(e.value); if (bits != null) ext.keyUsageBits = bits; }
|
|
2472
1828
|
return ext;
|
|
2473
1829
|
}),
|
|
2474
1830
|
signatureValue: validator.sig.ecdsaDerToP1363(c.signatureValue.bytes, sigCurve, C509Error, "c509/bad-signature"),
|
|
@@ -2477,16 +1833,11 @@ _derToType3 = function (input, opts) {
|
|
|
2477
1833
|
|
|
2478
1834
|
function encode(input, opts) {
|
|
2479
1835
|
if (input && typeof input === "object" && !Buffer.isBuffer(input) && input.certificateType != null) {
|
|
2480
|
-
return _encodeC509Array(input);
|
|
1836
|
+
return _encodeC509Array(input);
|
|
2481
1837
|
}
|
|
2482
1838
|
if (!Buffer.isBuffer(input) && typeof input !== "string") throw _err("c509/bad-input", "encode input must be a DER/PEM X.509 certificate or a c509.parse result");
|
|
2483
|
-
// Normalize input to DER through the SAME shared coercion x509.parse uses (a PEM string, a PEM-armored
|
|
2484
|
-
// Buffer, or raw DER all collapse to the certificate DER), so the self-verify compares against exactly the
|
|
2485
|
-
// bytes _derToType3 processed -- never a Buffer of PEM text.
|
|
2486
1839
|
var origDer = pkix.coerceToDer(input, { pemLabel: "CERTIFICATE", PemError: frameworkError.PemError, ErrorClass: C509Error, prefix: "c509" });
|
|
2487
1840
|
var encoded = _encodeC509Array(_derToType3(origDer, opts));
|
|
2488
|
-
// The type-3 transform MUST invert back to the original DER byte-for-byte (so the original signature
|
|
2489
|
-
// verifies) -- self-verify it, failing closed on any edge the reconstruction cannot reproduce.
|
|
2490
1841
|
var recon = parse(encoded).reconstructedDer;
|
|
2491
1842
|
if (!recon || Buffer.compare(recon, origDer) !== 0) throw _err("c509/non-invertible", "the type-3 C509 does not reconstruct the source certificate byte-for-byte");
|
|
2492
1843
|
return encoded;
|