@blamejs/pki 0.1.5 → 0.1.7
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 +30 -0
- package/README.md +11 -8
- package/bin/pki.js +2 -1
- package/index.js +7 -3
- package/lib/framework-error.js +10 -0
- package/lib/oid.js +7 -2
- package/lib/schema-all.js +126 -0
- package/lib/schema-crl.js +259 -0
- package/lib/schema-engine.js +346 -0
- package/lib/schema-pkix.js +239 -0
- package/lib/schema-x509.js +260 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
- package/lib/x509.js +0 -411
package/lib/x509.js
DELETED
|
@@ -1,411 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
// Copyright (c) blamejs contributors
|
|
3
|
-
"use strict";
|
|
4
|
-
/**
|
|
5
|
-
* @module pki.x509
|
|
6
|
-
* @nav Certificates
|
|
7
|
-
* @title X.509
|
|
8
|
-
* @order 100
|
|
9
|
-
* @featured true
|
|
10
|
-
* @slug x509
|
|
11
|
-
*
|
|
12
|
-
* @intro
|
|
13
|
-
* X.509 certificate handling per RFC 5280. The seed surface is
|
|
14
|
-
* `parse` — turn a DER or PEM certificate into a structured,
|
|
15
|
-
* fully-decoded object: version, serial, signature algorithm, issuer
|
|
16
|
-
* and subject distinguished names, validity window (as real `Date`s),
|
|
17
|
-
* subject public-key info, and the extension list. The parser composes
|
|
18
|
-
* the strict DER codec and the OID registry, so every field is
|
|
19
|
-
* validated on the way in and every algorithm / attribute / extension
|
|
20
|
-
* is named where the registry knows it.
|
|
21
|
-
*
|
|
22
|
-
* The raw `tbsCertificate` bytes are returned alongside the parsed
|
|
23
|
-
* fields so a signature-verification layer can hash exactly the bytes
|
|
24
|
-
* that were signed rather than re-encoding and hoping for round-trip
|
|
25
|
-
* fidelity.
|
|
26
|
-
*
|
|
27
|
-
* @card
|
|
28
|
-
* Parse DER / PEM X.509 certificates into structured, validated fields
|
|
29
|
-
* with named algorithms, extensions, and real-`Date` validity windows.
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
var constants = require("./constants");
|
|
33
|
-
var asn1 = require("./asn1-der");
|
|
34
|
-
var oid = require("./oid");
|
|
35
|
-
var frameworkError = require("./framework-error");
|
|
36
|
-
|
|
37
|
-
var CertificateError = frameworkError.CertificateError;
|
|
38
|
-
var PemError = frameworkError.PemError;
|
|
39
|
-
|
|
40
|
-
var TAGS = asn1.TAGS;
|
|
41
|
-
|
|
42
|
-
// Distinguished-name attribute short labels (RFC 4514 §3 + common use).
|
|
43
|
-
var DN_SHORT = {
|
|
44
|
-
commonName: "CN",
|
|
45
|
-
countryName: "C",
|
|
46
|
-
localityName: "L",
|
|
47
|
-
stateOrProvinceName: "ST",
|
|
48
|
-
streetAddress: "STREET",
|
|
49
|
-
organizationName: "O",
|
|
50
|
-
organizationalUnitName: "OU",
|
|
51
|
-
domainComponent: "DC",
|
|
52
|
-
surname: "SN",
|
|
53
|
-
givenName: "GN",
|
|
54
|
-
serialNumber: "SERIALNUMBER",
|
|
55
|
-
emailAddress: "emailAddress",
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
// ---- PEM -------------------------------------------------------------
|
|
59
|
-
|
|
60
|
-
var PEM_RE = /-----BEGIN ([A-Z0-9 ]+)-----([\s\S]*?)-----END \1-----/;
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* @primitive pki.x509.pemDecode
|
|
64
|
-
* @signature pki.x509.pemDecode(text, label?) -> Buffer
|
|
65
|
-
* @since 0.1.0
|
|
66
|
-
* @status stable
|
|
67
|
-
* @spec RFC 7468, RFC 5280
|
|
68
|
-
* @related pki.x509.pemEncode
|
|
69
|
-
*
|
|
70
|
-
* Extract the DER bytes from a PEM block. With `label` given (e.g.
|
|
71
|
-
* `"CERTIFICATE"`) the block type must match; without it, the first block
|
|
72
|
-
* is taken. Throws `PemError` on a missing / mismatched envelope or a
|
|
73
|
-
* non-base64 body.
|
|
74
|
-
*
|
|
75
|
-
* @example
|
|
76
|
-
* var der = pki.x509.pemDecode(pemText, "CERTIFICATE");
|
|
77
|
-
*/
|
|
78
|
-
function pemDecode(text, label) {
|
|
79
|
-
if (Buffer.isBuffer(text)) text = text.toString("latin1");
|
|
80
|
-
if (typeof text !== "string") throw new PemError("pem/bad-input", "pemDecode expects a string or Buffer");
|
|
81
|
-
if (text.length > constants.LIMITS.PEM_MAX_BYTES) throw new PemError("pem/too-large", "PEM input exceeds size cap");
|
|
82
|
-
var m = PEM_RE.exec(text);
|
|
83
|
-
if (!m) throw new PemError("pem/no-block", "no PEM block found");
|
|
84
|
-
if (label && m[1] !== label) throw new PemError("pem/label-mismatch", "expected " + JSON.stringify(label) + " block, got " + JSON.stringify(m[1]));
|
|
85
|
-
var b64 = m[2].replace(/[\r\n\t ]+/g, "");
|
|
86
|
-
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(b64)) throw new PemError("pem/bad-base64", "PEM body is not valid base64");
|
|
87
|
-
return Buffer.from(b64, "base64");
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* @primitive pki.x509.pemEncode
|
|
92
|
-
* @signature pki.x509.pemEncode(der, label) -> string
|
|
93
|
-
* @since 0.1.0
|
|
94
|
-
* @status stable
|
|
95
|
-
* @spec RFC 7468, RFC 5280
|
|
96
|
-
* @related pki.x509.pemDecode
|
|
97
|
-
*
|
|
98
|
-
* Wrap DER bytes in a PEM envelope with 64-column base64 lines.
|
|
99
|
-
*
|
|
100
|
-
* @example
|
|
101
|
-
* var pem = pki.x509.pemEncode(der, "CERTIFICATE");
|
|
102
|
-
*/
|
|
103
|
-
function pemEncode(der, label) {
|
|
104
|
-
if (typeof label !== "string" || label.length === 0) throw new PemError("pem/bad-label", "pemEncode requires a label");
|
|
105
|
-
var buf = Buffer.isBuffer(der) ? der : Buffer.from(der);
|
|
106
|
-
var b64 = buf.toString("base64").replace(/(.{64})/g, "$1\n").replace(/\n$/, "");
|
|
107
|
-
return "-----BEGIN " + label + "-----\n" + b64 + "\n-----END " + label + "-----\n";
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// ---- helpers ---------------------------------------------------------
|
|
111
|
-
|
|
112
|
-
function _algId(node) {
|
|
113
|
-
if (node.tagClass !== "universal" || node.tagNumber !== TAGS.SEQUENCE || !node.children.length) {
|
|
114
|
-
throw new CertificateError("x509/bad-algorithm-identifier", "AlgorithmIdentifier must be a non-empty SEQUENCE");
|
|
115
|
-
}
|
|
116
|
-
var dotted = asn1.read.oid(node.children[0]);
|
|
117
|
-
return {
|
|
118
|
-
oid: dotted,
|
|
119
|
-
name: oid.name(dotted) || null,
|
|
120
|
-
parameters: node.children.length > 1 ? node.children[1].bytes : null,
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function _attrValueToString(node) {
|
|
125
|
-
try { return asn1.read.string(node); }
|
|
126
|
-
catch (e) {
|
|
127
|
-
// A malformed KNOWN string type (invalid UTF-8, a non-IA5 byte, a
|
|
128
|
-
// PrintableString character outside its set, ...) is a malformed
|
|
129
|
-
// certificate and must fail closed — do NOT hex-encode it away, or the
|
|
130
|
-
// decoder's strict string validation is silently bypassed on the DN path.
|
|
131
|
-
// Those surface as asn1/bad-* content errors; raise a CertificateError so
|
|
132
|
-
// parse fails closed on the documented x509/* contract.
|
|
133
|
-
//
|
|
134
|
-
// A value that simply is not a decodable primitive string is NOT malformed
|
|
135
|
-
// and must stay representable: an ANY-typed non-string tag
|
|
136
|
-
// (asn1/expected-string) or a constructed universal type such as a SEQUENCE
|
|
137
|
-
// (asn1/expected-primitive). Per RFC 4514 §2.4 it is rendered as "#" plus
|
|
138
|
-
// the hex of its FULL DER encoding (tag + length + content — node.bytes),
|
|
139
|
-
// so a constructed value round-trips intact rather than being dropped or
|
|
140
|
-
// rejecting the whole certificate.
|
|
141
|
-
if (!e || (e.code !== "asn1/expected-string" && e.code !== "asn1/expected-primitive")) {
|
|
142
|
-
throw new CertificateError("x509/bad-atv", "malformed string in attribute value: " + ((e && e.message) || String(e)));
|
|
143
|
-
}
|
|
144
|
-
return "#" + node.bytes.toString("hex");
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function _escapeDnValue(v) {
|
|
149
|
-
return v.replace(/([,+"\\<>;])/g, "\\$1");
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function _parseName(node) {
|
|
153
|
-
if (node.tagClass !== "universal" || node.tagNumber !== TAGS.SEQUENCE) {
|
|
154
|
-
throw new CertificateError("x509/bad-name", "Name must be an RDNSequence (SEQUENCE)");
|
|
155
|
-
}
|
|
156
|
-
var rdns = [];
|
|
157
|
-
var parts = [];
|
|
158
|
-
for (var i = 0; i < node.children.length; i++) {
|
|
159
|
-
var rdn = node.children[i];
|
|
160
|
-
if (rdn.tagClass !== "universal" || rdn.tagNumber !== TAGS.SET) {
|
|
161
|
-
throw new CertificateError("x509/bad-rdn", "RelativeDistinguishedName must be a SET");
|
|
162
|
-
}
|
|
163
|
-
var atvs = [];
|
|
164
|
-
var atvParts = [];
|
|
165
|
-
for (var j = 0; j < rdn.children.length; j++) {
|
|
166
|
-
var atv = rdn.children[j];
|
|
167
|
-
if (!atv.children || atv.children.length < 2) {
|
|
168
|
-
throw new CertificateError("x509/bad-atv", "AttributeTypeAndValue must be a SEQUENCE of {type, value}");
|
|
169
|
-
}
|
|
170
|
-
var typeOid = asn1.read.oid(atv.children[0]);
|
|
171
|
-
var typeName = oid.name(typeOid);
|
|
172
|
-
var value = _attrValueToString(atv.children[1]);
|
|
173
|
-
atvs.push({ type: typeOid, name: typeName || null, value: value });
|
|
174
|
-
var label = (typeName && DN_SHORT[typeName]) || typeName || typeOid;
|
|
175
|
-
atvParts.push(label + "=" + _escapeDnValue(value));
|
|
176
|
-
}
|
|
177
|
-
rdns.push(atvs);
|
|
178
|
-
parts.push(atvParts.join("+"));
|
|
179
|
-
}
|
|
180
|
-
return { rdns: rdns, dn: parts.join(", ") };
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function _parseValidityTime(node) {
|
|
184
|
-
if (node.tagClass !== "universal" || (node.tagNumber !== TAGS.UTC_TIME && node.tagNumber !== TAGS.GENERALIZED_TIME)) {
|
|
185
|
-
throw new CertificateError("x509/bad-time", "Validity time must be UTCTime or GeneralizedTime");
|
|
186
|
-
}
|
|
187
|
-
return asn1.read.time(node);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function _parseExtensions(seqNode) {
|
|
191
|
-
// RFC 5280 §4.1.2.9 — Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension.
|
|
192
|
-
// The wrapped value must be a SEQUENCE (not a primitive, which would read
|
|
193
|
-
// seqNode.children as null and throw a raw TypeError) and must carry at
|
|
194
|
-
// least one extension.
|
|
195
|
-
if (seqNode.tagClass !== "universal" || seqNode.tagNumber !== TAGS.SEQUENCE || !seqNode.children) {
|
|
196
|
-
throw new CertificateError("x509/bad-extensions", "extensions [3] must wrap a SEQUENCE");
|
|
197
|
-
}
|
|
198
|
-
if (seqNode.children.length === 0) {
|
|
199
|
-
throw new CertificateError("x509/bad-extensions", "extensions SEQUENCE must contain at least one extension");
|
|
200
|
-
}
|
|
201
|
-
var out = [];
|
|
202
|
-
var seen = new Set();
|
|
203
|
-
for (var i = 0; i < seqNode.children.length; i++) {
|
|
204
|
-
var ext = seqNode.children[i];
|
|
205
|
-
if (!ext.children || ext.children.length < 2) {
|
|
206
|
-
throw new CertificateError("x509/bad-extension", "Extension must be a SEQUENCE");
|
|
207
|
-
}
|
|
208
|
-
var extnID = asn1.read.oid(ext.children[0]);
|
|
209
|
-
// RFC 5280 §4.2 — a certificate MUST NOT include more than one
|
|
210
|
-
// instance of a particular extension.
|
|
211
|
-
if (seen.has(extnID)) {
|
|
212
|
-
throw new CertificateError("x509/duplicate-extension", "certificate repeats extension " + extnID);
|
|
213
|
-
}
|
|
214
|
-
seen.add(extnID);
|
|
215
|
-
var critical = false;
|
|
216
|
-
var valueNode;
|
|
217
|
-
if (ext.children.length === 3) {
|
|
218
|
-
critical = asn1.read.boolean(ext.children[1]);
|
|
219
|
-
valueNode = ext.children[2];
|
|
220
|
-
} else {
|
|
221
|
-
valueNode = ext.children[1];
|
|
222
|
-
}
|
|
223
|
-
out.push({
|
|
224
|
-
oid: extnID,
|
|
225
|
-
name: oid.name(extnID) || null,
|
|
226
|
-
critical: critical,
|
|
227
|
-
value: asn1.read.octetString(valueNode),
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
return out;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// ---- parse -----------------------------------------------------------
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* @primitive pki.x509.parse
|
|
237
|
-
* @signature pki.x509.parse(input) -> certificate
|
|
238
|
-
* @since 0.1.0
|
|
239
|
-
* @status stable
|
|
240
|
-
* @spec RFC 5280, X.509
|
|
241
|
-
* @defends malformed-certificate-parse (CWE-20)
|
|
242
|
-
* @related pki.asn1.decode, pki.oid.name
|
|
243
|
-
*
|
|
244
|
-
* Parse a DER `Buffer` or a PEM string/Buffer into a structured
|
|
245
|
-
* certificate: `{ version, serialNumber, serialNumberHex,
|
|
246
|
-
* signatureAlgorithm, issuer, subject, validity, subjectPublicKeyInfo,
|
|
247
|
-
* extensions, tbsBytes, signatureValue }`. Distinguished names come back
|
|
248
|
-
* both as a rendered `dn` string and as structured `rdns`; the validity
|
|
249
|
-
* window is real `Date`s; `tbsBytes` is the exact signed byte range for a
|
|
250
|
-
* downstream verifier.
|
|
251
|
-
*
|
|
252
|
-
* Throws `CertificateError` when the bytes are not a well-formed
|
|
253
|
-
* certificate and `Asn1Error` when the underlying DER is malformed.
|
|
254
|
-
*
|
|
255
|
-
* @example
|
|
256
|
-
* var cert = pki.x509.parse(pemString);
|
|
257
|
-
* cert.subject.dn; // "CN=example.com, O=Example"
|
|
258
|
-
* cert.validity.notAfter; // Date
|
|
259
|
-
* cert.signatureAlgorithm.name; // "sha256WithRSAEncryption"
|
|
260
|
-
*/
|
|
261
|
-
function parse(input) {
|
|
262
|
-
var der;
|
|
263
|
-
if (typeof input === "string" || (Buffer.isBuffer(input) && input.length >= 5 && input.toString("latin1", 0, 5) === "-----")) {
|
|
264
|
-
der = pemDecode(input, "CERTIFICATE");
|
|
265
|
-
} else if (Buffer.isBuffer(input) || input instanceof Uint8Array) {
|
|
266
|
-
der = Buffer.isBuffer(input) ? input : Buffer.from(input);
|
|
267
|
-
} else {
|
|
268
|
-
throw new CertificateError("x509/bad-input", "parse expects a DER Buffer or a PEM string");
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
var root;
|
|
272
|
-
try {
|
|
273
|
-
root = asn1.decode(der);
|
|
274
|
-
} catch (e) {
|
|
275
|
-
throw new CertificateError("x509/bad-der", "certificate DER did not decode: " + e.message, e);
|
|
276
|
-
}
|
|
277
|
-
if (root.tagClass !== "universal" || root.tagNumber !== TAGS.SEQUENCE || !root.children || root.children.length !== 3) {
|
|
278
|
-
throw new CertificateError("x509/not-a-certificate", "Certificate must be a SEQUENCE of {tbsCertificate, signatureAlgorithm, signature}");
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
var tbs = root.children[0];
|
|
282
|
-
var outerSigAlg = root.children[1];
|
|
283
|
-
var sigValueNode = root.children[2];
|
|
284
|
-
if (!tbs.children || !tbs.children.length) {
|
|
285
|
-
throw new CertificateError("x509/bad-tbs", "tbsCertificate is empty");
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
var idx = 0;
|
|
289
|
-
var version;
|
|
290
|
-
var first = tbs.children[0];
|
|
291
|
-
if (first.tagClass === "context" && first.tagNumber === 0) {
|
|
292
|
-
if (!first.children || !first.children.length) throw new CertificateError("x509/bad-version", "version [0] must wrap an INTEGER");
|
|
293
|
-
// RFC 5280 §4.1.2.1 — version is INTEGER { v1(0), v2(1), v3(2) }; read
|
|
294
|
-
// it as a BigInt so an out-of-range value can't be coerced to a float,
|
|
295
|
-
// and reject an explicitly-encoded v1 (DER forbids encoding the DEFAULT).
|
|
296
|
-
var versionValue = asn1.read.integer(first.children[0]);
|
|
297
|
-
if (versionValue === 0n) {
|
|
298
|
-
throw new CertificateError("x509/bad-version", "DER forbids explicitly encoding the default version v1");
|
|
299
|
-
} else if (versionValue === 1n) {
|
|
300
|
-
version = 2;
|
|
301
|
-
} else if (versionValue === 2n) {
|
|
302
|
-
version = 3;
|
|
303
|
-
} else {
|
|
304
|
-
throw new CertificateError("x509/bad-version", "unsupported certificate version " + versionValue.toString());
|
|
305
|
-
}
|
|
306
|
-
idx = 1;
|
|
307
|
-
} else {
|
|
308
|
-
version = 1;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// Six positional fields follow any [0] version; a shorter tbs would read
|
|
312
|
-
// `undefined` and throw a raw TypeError on the first property access.
|
|
313
|
-
if (tbs.children.length < idx + 6) {
|
|
314
|
-
throw new CertificateError("x509/bad-tbs", "tbsCertificate is too short");
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
var serialNode = tbs.children[idx++];
|
|
318
|
-
var serialNumber = asn1.read.integer(serialNode);
|
|
319
|
-
var innerSigAlg = tbs.children[idx++];
|
|
320
|
-
var issuer = _parseName(tbs.children[idx++]);
|
|
321
|
-
var validityNode = tbs.children[idx++];
|
|
322
|
-
var subject = _parseName(tbs.children[idx++]);
|
|
323
|
-
var spkiNode = tbs.children[idx++];
|
|
324
|
-
|
|
325
|
-
// RFC 5280 §4.1.1.2 — the outer signatureAlgorithm MUST contain the same
|
|
326
|
-
// AlgorithmIdentifier (OID and parameters) as tbsCertificate.signature. A
|
|
327
|
-
// mismatch is a signature-algorithm-substitution vector, so compare the full
|
|
328
|
-
// DER of both fields rather than surfacing two disagreeing algorithms.
|
|
329
|
-
if (!outerSigAlg.bytes.equals(innerSigAlg.bytes)) {
|
|
330
|
-
throw new CertificateError("x509/bad-signature-algorithm", "signatureAlgorithm must match tbsCertificate.signature (RFC 5280 §4.1.1.2)");
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// RFC 5280 §4.1.2.4 — the issuer field MUST contain a non-empty distinguished
|
|
334
|
-
// name. (An empty subject is permitted when a subjectAltName carries the
|
|
335
|
-
// identity — §4.1.2.6 — so only the issuer is required non-empty here.)
|
|
336
|
-
if (!issuer.rdns.length) {
|
|
337
|
-
throw new CertificateError("x509/bad-issuer", "issuer must be a non-empty distinguished name");
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
if (!validityNode.children || validityNode.children.length !== 2) {
|
|
341
|
-
throw new CertificateError("x509/bad-validity", "Validity must be a SEQUENCE of {notBefore, notAfter}");
|
|
342
|
-
}
|
|
343
|
-
var validity = {
|
|
344
|
-
notBefore: _parseValidityTime(validityNode.children[0]),
|
|
345
|
-
notAfter: _parseValidityTime(validityNode.children[1]),
|
|
346
|
-
};
|
|
347
|
-
|
|
348
|
-
if (!spkiNode.children || spkiNode.children.length !== 2) {
|
|
349
|
-
throw new CertificateError("x509/bad-spki", "SubjectPublicKeyInfo must be a SEQUENCE of {algorithm, subjectPublicKey}");
|
|
350
|
-
}
|
|
351
|
-
var spkiBits = asn1.read.bitString(spkiNode.children[1]);
|
|
352
|
-
var subjectPublicKeyInfo = {
|
|
353
|
-
algorithm: _algId(spkiNode.children[0]),
|
|
354
|
-
publicKey: { unusedBits: spkiBits.unusedBits, bytes: spkiBits.bytes },
|
|
355
|
-
bytes: spkiNode.bytes,
|
|
356
|
-
};
|
|
357
|
-
|
|
358
|
-
// Remaining tbs children: optional issuerUniqueID [1], subjectUniqueID
|
|
359
|
-
// [2], and extensions [3] EXPLICIT. Only extensions are surfaced. RFC 5280
|
|
360
|
-
// §4.1 fixes these as the trailing fields, each at most once and in strictly
|
|
361
|
-
// increasing tag order; a repeated or out-of-order tag (or an unknown /
|
|
362
|
-
// non-context field) is malformed. Without the monotonic guard a second [3]
|
|
363
|
-
// silently overwrites the first, hiding its extensions and splitting
|
|
364
|
-
// duplicate extension OIDs across two wrappers past the per-sequence check.
|
|
365
|
-
var extensions = [];
|
|
366
|
-
var hasExtensions = false;
|
|
367
|
-
var lastTrailingTag = 0;
|
|
368
|
-
for (; idx < tbs.children.length; idx++) {
|
|
369
|
-
var t = tbs.children[idx];
|
|
370
|
-
if (t.tagClass !== "context" || t.tagNumber < 1 || t.tagNumber > 3) {
|
|
371
|
-
throw new CertificateError("x509/bad-tbs", "unexpected field after subjectPublicKeyInfo; tbsCertificate allows only issuerUniqueID [1], subjectUniqueID [2], extensions [3]");
|
|
372
|
-
}
|
|
373
|
-
if (t.tagNumber <= lastTrailingTag) {
|
|
374
|
-
throw new CertificateError("x509/bad-tbs", "tbsCertificate trailing field [" + t.tagNumber + "] is repeated or out of order");
|
|
375
|
-
}
|
|
376
|
-
lastTrailingTag = t.tagNumber;
|
|
377
|
-
if (t.tagNumber === 3) {
|
|
378
|
-
hasExtensions = true;
|
|
379
|
-
if (!t.children || !t.children.length) throw new CertificateError("x509/bad-extensions", "extensions [3] must wrap a SEQUENCE");
|
|
380
|
-
extensions = _parseExtensions(t.children[0]);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
// RFC 5280 §4.1.2.9 — the extensions field appears only in a v3 cert.
|
|
385
|
-
if (hasExtensions && version !== 3) {
|
|
386
|
-
throw new CertificateError("x509/bad-version", "extensions are only permitted in a v3 certificate");
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
var sigBits = asn1.read.bitString(sigValueNode);
|
|
390
|
-
|
|
391
|
-
return {
|
|
392
|
-
version: version,
|
|
393
|
-
serialNumber: serialNumber,
|
|
394
|
-
serialNumberHex: serialNode.content.toString("hex"),
|
|
395
|
-
signatureAlgorithm: _algId(outerSigAlg),
|
|
396
|
-
tbsSignatureAlgorithm: _algId(innerSigAlg),
|
|
397
|
-
issuer: issuer,
|
|
398
|
-
subject: subject,
|
|
399
|
-
validity: validity,
|
|
400
|
-
subjectPublicKeyInfo: subjectPublicKeyInfo,
|
|
401
|
-
extensions: extensions,
|
|
402
|
-
tbsBytes: tbs.bytes,
|
|
403
|
-
signatureValue: { unusedBits: sigBits.unusedBits, bytes: sigBits.bytes },
|
|
404
|
-
};
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
module.exports = {
|
|
408
|
-
parse: parse,
|
|
409
|
-
pemDecode: pemDecode,
|
|
410
|
-
pemEncode: pemEncode,
|
|
411
|
-
};
|