@certd/acme-client 0.1.6 → 0.2.0

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.
@@ -1,445 +1,454 @@
1
- /**
2
- * node-forge crypto engine
3
- *
4
- * @namespace forge
5
- */
6
-
7
- const net = require('net');
8
- const Promise = require('bluebird');
9
- const forge = require('node-forge');
10
-
11
- const generateKeyPair = Promise.promisify(forge.pki.rsa.generateKeyPair);
12
-
13
-
14
- /**
15
- * Attempt to parse forge object from PEM encoded string
16
- *
17
- * @private
18
- * @param {string} input PEM string
19
- * @return {object}
20
- */
21
-
22
- function forgeObjectFromPem(input) {
23
- const msg = forge.pem.decode(input)[0];
24
- let result;
25
-
26
- switch (msg.type) {
27
- case 'PRIVATE KEY':
28
- case 'RSA PRIVATE KEY':
29
- result = forge.pki.privateKeyFromPem(input);
30
- break;
31
-
32
- case 'PUBLIC KEY':
33
- case 'RSA PUBLIC KEY':
34
- result = forge.pki.publicKeyFromPem(input);
35
- break;
36
-
37
- case 'CERTIFICATE':
38
- case 'X509 CERTIFICATE':
39
- case 'TRUSTED CERTIFICATE':
40
- result = forge.pki.certificateFromPem(input).publicKey;
41
- break;
42
-
43
- case 'CERTIFICATE REQUEST':
44
- result = forge.pki.certificationRequestFromPem(input).publicKey;
45
- break;
46
-
47
- default:
48
- throw new Error('Unable to detect forge message type');
49
- }
50
-
51
- return result;
52
- }
53
-
54
-
55
- /**
56
- * Parse domain names from a certificate or CSR
57
- *
58
- * @private
59
- * @param {object} obj Forge certificate or CSR
60
- * @returns {object} {commonName, altNames}
61
- */
62
-
63
- function parseDomains(obj) {
64
- let commonName = null;
65
- let altNames = [];
66
- let altNamesDict = [];
67
-
68
- const commonNameObject = (obj.subject.attributes || []).find((a) => a.name === 'commonName');
69
- const rootAltNames = (obj.extensions || []).find((e) => 'altNames' in e);
70
- const rootExtensions = (obj.attributes || []).find((a) => 'extensions' in a);
71
-
72
- if (rootAltNames && rootAltNames.altNames && rootAltNames.altNames.length) {
73
- altNamesDict = rootAltNames.altNames;
74
- }
75
- else if (rootExtensions && rootExtensions.extensions && rootExtensions.extensions.length) {
76
- const extAltNames = rootExtensions.extensions.find((e) => 'altNames' in e);
77
-
78
- if (extAltNames && extAltNames.altNames && extAltNames.altNames.length) {
79
- altNamesDict = extAltNames.altNames;
80
- }
81
- }
82
-
83
- if (commonNameObject) {
84
- commonName = commonNameObject.value;
85
- }
86
-
87
- if (altNamesDict) {
88
- altNames = altNamesDict.map((a) => a.value);
89
- }
90
-
91
- return {
92
- commonName,
93
- altNames
94
- };
95
- }
96
-
97
-
98
- /**
99
- * Generate a private RSA key
100
- *
101
- * @param {number} [size] Size of the key, default: `2048`
102
- * @returns {Promise<buffer>} PEM encoded private RSA key
103
- *
104
- * @example Generate private RSA key
105
- * ```js
106
- * const privateKey = await acme.forge.createPrivateKey();
107
- * ```
108
- *
109
- * @example Private RSA key with defined size
110
- * ```js
111
- * const privateKey = await acme.forge.createPrivateKey(4096);
112
- * ```
113
- */
114
-
115
- async function createPrivateKey(size = 2048) {
116
- const keyPair = await generateKeyPair({ bits: size });
117
- const pemKey = forge.pki.privateKeyToPem(keyPair.privateKey);
118
- return Buffer.from(pemKey);
119
- }
120
-
121
- exports.createPrivateKey = createPrivateKey;
122
-
123
-
124
- /**
125
- * Create public key from a private RSA key
126
- *
127
- * @param {buffer|string} key PEM encoded private RSA key
128
- * @returns {Promise<buffer>} PEM encoded public RSA key
129
- *
130
- * @example Create public key
131
- * ```js
132
- * const publicKey = await acme.forge.createPublicKey(privateKey);
133
- * ```
134
- */
135
-
136
- exports.createPublicKey = async function(key) {
137
- const privateKey = forge.pki.privateKeyFromPem(key);
138
- const publicKey = forge.pki.rsa.setPublicKey(privateKey.n, privateKey.e);
139
- const pemKey = forge.pki.publicKeyToPem(publicKey);
140
- return Buffer.from(pemKey);
141
- };
142
-
143
-
144
- /**
145
- * Parse body of PEM encoded object form buffer or string
146
- * If multiple objects are chained, the first body will be returned
147
- *
148
- * @param {buffer|string} str PEM encoded buffer or string
149
- * @returns {string} PEM body
150
- */
151
-
152
- exports.getPemBody = (str) => {
153
- const msg = forge.pem.decode(str)[0];
154
- return forge.util.encode64(msg.body);
155
- };
156
-
157
-
158
- /**
159
- * Split chain of PEM encoded objects from buffer or string into array
160
- *
161
- * @param {buffer|string} str PEM encoded buffer or string
162
- * @returns {string[]} Array of PEM bodies
163
- */
164
-
165
- exports.splitPemChain = (str) => forge.pem.decode(str).map(forge.pem.encode);
166
-
167
-
168
- /**
169
- * Get modulus
170
- *
171
- * @param {buffer|string} input PEM encoded private key, certificate or CSR
172
- * @returns {Promise<buffer>} Modulus
173
- *
174
- * @example Get modulus
175
- * ```js
176
- * const m1 = await acme.forge.getModulus(privateKey);
177
- * const m2 = await acme.forge.getModulus(certificate);
178
- * const m3 = await acme.forge.getModulus(certificateRequest);
179
- * ```
180
- */
181
-
182
- exports.getModulus = async function(input) {
183
- if (!Buffer.isBuffer(input)) {
184
- input = Buffer.from(input);
185
- }
186
-
187
- const obj = forgeObjectFromPem(input);
188
- return Buffer.from(forge.util.hexToBytes(obj.n.toString(16)), 'binary');
189
- };
190
-
191
-
192
- /**
193
- * Get public exponent
194
- *
195
- * @param {buffer|string} input PEM encoded private key, certificate or CSR
196
- * @returns {Promise<buffer>} Exponent
197
- *
198
- * @example Get public exponent
199
- * ```js
200
- * const e1 = await acme.forge.getPublicExponent(privateKey);
201
- * const e2 = await acme.forge.getPublicExponent(certificate);
202
- * const e3 = await acme.forge.getPublicExponent(certificateRequest);
203
- * ```
204
- */
205
-
206
- exports.getPublicExponent = async function(input) {
207
- if (!Buffer.isBuffer(input)) {
208
- input = Buffer.from(input);
209
- }
210
-
211
- const obj = forgeObjectFromPem(input);
212
- return Buffer.from(forge.util.hexToBytes(obj.e.toString(16)), 'binary');
213
- };
214
-
215
-
216
- /**
217
- * Read domains from a Certificate Signing Request
218
- *
219
- * @param {buffer|string} csr PEM encoded Certificate Signing Request
220
- * @returns {Promise<object>} {commonName, altNames}
221
- *
222
- * @example Read Certificate Signing Request domains
223
- * ```js
224
- * const { commonName, altNames } = await acme.forge.readCsrDomains(certificateRequest);
225
- *
226
- * console.log(`Common name: ${commonName}`);
227
- * console.log(`Alt names: ${altNames.join(', ')}`);
228
- * ```
229
- */
230
-
231
- exports.readCsrDomains = async function(csr) {
232
- if (!Buffer.isBuffer(csr)) {
233
- csr = Buffer.from(csr);
234
- }
235
-
236
- const obj = forge.pki.certificationRequestFromPem(csr);
237
- return parseDomains(obj);
238
- };
239
-
240
-
241
- /**
242
- * Read information from a certificate
243
- *
244
- * @param {buffer|string} cert PEM encoded certificate
245
- * @returns {Promise<object>} Certificate info
246
- *
247
- * @example Read certificate information
248
- * ```js
249
- * const info = await acme.forge.readCertificateInfo(certificate);
250
- * const { commonName, altNames } = info.domains;
251
- *
252
- * console.log(`Not after: ${info.notAfter}`);
253
- * console.log(`Not before: ${info.notBefore}`);
254
- *
255
- * console.log(`Common name: ${commonName}`);
256
- * console.log(`Alt names: ${altNames.join(', ')}`);
257
- * ```
258
- */
259
-
260
- exports.readCertificateInfo = async function(cert) {
261
- if (!Buffer.isBuffer(cert)) {
262
- cert = Buffer.from(cert);
263
- }
264
-
265
- const obj = forge.pki.certificateFromPem(cert);
266
- const issuerCn = (obj.issuer.attributes || []).find((a) => a.name === 'commonName');
267
-
268
- return {
269
- issuer: {
270
- commonName: issuerCn ? issuerCn.value : null
271
- },
272
- domains: parseDomains(obj),
273
- notAfter: obj.validity.notAfter,
274
- notBefore: obj.validity.notBefore
275
- };
276
- };
277
-
278
-
279
- /**
280
- * Determine ASN.1 type for CSR subject short name
281
- * Note: https://tools.ietf.org/html/rfc5280
282
- *
283
- * @private
284
- * @param {string} shortName CSR subject short name
285
- * @returns {forge.asn1.Type} ASN.1 type
286
- */
287
-
288
- function getCsrValueTagClass(shortName) {
289
- switch (shortName) {
290
- case 'C':
291
- return forge.asn1.Type.PRINTABLESTRING;
292
- case 'E':
293
- return forge.asn1.Type.IA5STRING;
294
- default:
295
- return forge.asn1.Type.UTF8;
296
- }
297
- }
298
-
299
-
300
- /**
301
- * Create array of short names and values for Certificate Signing Request subjects
302
- *
303
- * @private
304
- * @param {object} subjectObj Key-value of short names and values
305
- * @returns {object[]} Certificate Signing Request subject array
306
- */
307
-
308
- function createCsrSubject(subjectObj) {
309
- return Object.entries(subjectObj).reduce((result, [shortName, value]) => {
310
- if (value) {
311
- const valueTagClass = getCsrValueTagClass(shortName);
312
- result.push({ shortName, value, valueTagClass });
313
- }
314
-
315
- return result;
316
- }, []);
317
- }
318
-
319
-
320
- /**
321
- * Create array of alt names for Certificate Signing Requests
322
- * Note: https://github.com/digitalbazaar/forge/blob/dfdde475677a8a25c851e33e8f81dca60d90cfb9/lib/x509.js#L1444-L1454
323
- *
324
- * @private
325
- * @param {string[]} altNames Alt names
326
- * @returns {object[]} Certificate Signing Request alt names array
327
- */
328
-
329
- function formatCsrAltNames(altNames) {
330
- return altNames.map((value) => {
331
- const type = net.isIP(value) ? 7 : 2;
332
- return { type, value };
333
- });
334
- }
335
-
336
-
337
- /**
338
- * Create a Certificate Signing Request
339
- *
340
- * @param {object} data
341
- * @param {number} [data.keySize] Size of newly created private key, default: `2048`
342
- * @param {string} [data.commonName]
343
- * @param {array} [data.altNames] default: `[]`
344
- * @param {string} [data.country]
345
- * @param {string} [data.state]
346
- * @param {string} [data.locality]
347
- * @param {string} [data.organization]
348
- * @param {string} [data.organizationUnit]
349
- * @param {string} [data.emailAddress]
350
- * @param {buffer|string} [key] CSR private key
351
- * @returns {Promise<buffer[]>} [privateKey, certificateSigningRequest]
352
- *
353
- * @example Create a Certificate Signing Request
354
- * ```js
355
- * const [certificateKey, certificateRequest] = await acme.forge.createCsr({
356
- * commonName: 'test.example.com'
357
- * });
358
- * ```
359
- *
360
- * @example Certificate Signing Request with both common and alternative names
361
- * ```js
362
- * const [certificateKey, certificateRequest] = await acme.forge.createCsr({
363
- * keySize: 4096,
364
- * commonName: 'test.example.com',
365
- * altNames: ['foo.example.com', 'bar.example.com']
366
- * });
367
- * ```
368
- *
369
- * @example Certificate Signing Request with additional information
370
- * ```js
371
- * const [certificateKey, certificateRequest] = await acme.forge.createCsr({
372
- * commonName: 'test.example.com',
373
- * country: 'US',
374
- * state: 'California',
375
- * locality: 'Los Angeles',
376
- * organization: 'The Company Inc.',
377
- * organizationUnit: 'IT Department',
378
- * emailAddress: 'contact@example.com'
379
- * });
380
- * ```
381
- *
382
- * @example Certificate Signing Request with predefined private key
383
- * ```js
384
- * const certificateKey = await acme.forge.createPrivateKey();
385
- *
386
- * const [, certificateRequest] = await acme.forge.createCsr({
387
- * commonName: 'test.example.com'
388
- * }, certificateKey);
389
- */
390
-
391
- exports.createCsr = async function(data, key = null) {
392
- if (!key) {
393
- key = await createPrivateKey(data.keySize);
394
- }
395
- else if (!Buffer.isBuffer(key)) {
396
- key = Buffer.from(key);
397
- }
398
-
399
- if (typeof data.altNames === 'undefined') {
400
- data.altNames = [];
401
- }
402
-
403
- const csr = forge.pki.createCertificationRequest();
404
-
405
- /* Public key */
406
- const privateKey = forge.pki.privateKeyFromPem(key);
407
- const publicKey = forge.pki.rsa.setPublicKey(privateKey.n, privateKey.e);
408
- csr.publicKey = publicKey;
409
-
410
- /* Ensure subject common name is present in SAN - https://cabforum.org/wp-content/uploads/BRv1.2.3.pdf */
411
- if (data.commonName && !data.altNames.includes(data.commonName)) {
412
- data.altNames.unshift(data.commonName);
413
- }
414
-
415
- /* Subject */
416
- const subject = createCsrSubject({
417
- CN: data.commonName,
418
- C: data.country,
419
- ST: data.state,
420
- L: data.locality,
421
- O: data.organization,
422
- OU: data.organizationUnit,
423
- E: data.emailAddress
424
- });
425
-
426
- csr.setSubject(subject);
427
-
428
- /* SAN extension */
429
- if (data.altNames.length) {
430
- csr.setAttributes([{
431
- name: 'extensionRequest',
432
- extensions: [{
433
- name: 'subjectAltName',
434
- altNames: formatCsrAltNames(data.altNames)
435
- }]
436
- }]);
437
- }
438
-
439
- /* Sign CSR */
440
- csr.sign(privateKey);
441
-
442
- /* Done */
443
- const pemCsr = forge.pki.certificationRequestToPem(csr);
444
- return [key, Buffer.from(pemCsr)];
445
- };
1
+ /**
2
+ * node-forge crypto engine
3
+ *
4
+ * @namespace forge
5
+ */
6
+
7
+ const net = require('net');
8
+ const Promise = require('bluebird');
9
+ const forge = require('node-forge');
10
+
11
+ const generateKeyPair = Promise.promisify(forge.pki.rsa.generateKeyPair);
12
+
13
+
14
+ /**
15
+ * Attempt to parse forge object from PEM encoded string
16
+ *
17
+ * @private
18
+ * @param {string} input PEM string
19
+ * @return {object}
20
+ */
21
+
22
+ function forgeObjectFromPem(input) {
23
+ const msg = forge.pem.decode(input)[0];
24
+ let result;
25
+
26
+ switch (msg.type) {
27
+ case 'PRIVATE KEY':
28
+ case 'RSA PRIVATE KEY':
29
+ result = forge.pki.privateKeyFromPem(input);
30
+ break;
31
+
32
+ case 'PUBLIC KEY':
33
+ case 'RSA PUBLIC KEY':
34
+ result = forge.pki.publicKeyFromPem(input);
35
+ break;
36
+
37
+ case 'CERTIFICATE':
38
+ case 'X509 CERTIFICATE':
39
+ case 'TRUSTED CERTIFICATE':
40
+ result = forge.pki.certificateFromPem(input).publicKey;
41
+ break;
42
+
43
+ case 'CERTIFICATE REQUEST':
44
+ result = forge.pki.certificationRequestFromPem(input).publicKey;
45
+ break;
46
+
47
+ default:
48
+ throw new Error('Unable to detect forge message type');
49
+ }
50
+
51
+ return result;
52
+ }
53
+
54
+
55
+ /**
56
+ * Parse domain names from a certificate or CSR
57
+ *
58
+ * @private
59
+ * @param {object} obj Forge certificate or CSR
60
+ * @returns {object} {commonName, altNames}
61
+ */
62
+
63
+ function parseDomains(obj) {
64
+ let commonName = null;
65
+ let altNames = [];
66
+ let altNamesDict = [];
67
+
68
+ const commonNameObject = (obj.subject.attributes || []).find((a) => a.name === 'commonName');
69
+ const rootAltNames = (obj.extensions || []).find((e) => 'altNames' in e);
70
+ const rootExtensions = (obj.attributes || []).find((a) => 'extensions' in a);
71
+
72
+ if (rootAltNames && rootAltNames.altNames && rootAltNames.altNames.length) {
73
+ altNamesDict = rootAltNames.altNames;
74
+ } else if (rootExtensions && rootExtensions.extensions && rootExtensions.extensions.length) {
75
+ const extAltNames = rootExtensions.extensions.find((e) => 'altNames' in e);
76
+
77
+ if (extAltNames && extAltNames.altNames && extAltNames.altNames.length) {
78
+ altNamesDict = extAltNames.altNames;
79
+ }
80
+ }
81
+
82
+ if (commonNameObject) {
83
+ commonName = commonNameObject.value;
84
+ }
85
+
86
+ if (altNamesDict) {
87
+ altNames = altNamesDict.map((a) => a.value);
88
+ }
89
+
90
+ return {
91
+ commonName,
92
+ altNames
93
+ };
94
+ }
95
+
96
+
97
+ /**
98
+ * Generate a private RSA key
99
+ *
100
+ * @param {number} [size] Size of the key, default: `2048`
101
+ * @returns {Promise<buffer>} PEM encoded private RSA key
102
+ *
103
+ * @example Generate private RSA key
104
+ * ```js
105
+ * const privateKey = await acme.forge.createPrivateKey();
106
+ * ```
107
+ *
108
+ * @example Private RSA key with defined size
109
+ * ```js
110
+ * const privateKey = await acme.forge.createPrivateKey(4096);
111
+ * ```
112
+ */
113
+
114
+ async function createPrivateKey(size = 2048) {
115
+ const keyPair = await generateKeyPair({bits: size});
116
+ // const privateKey = forge.pki.privateKeyToPem(keyPair.privateKey);
117
+
118
+ // convert a Forge private key to an ASN.1 RSAPrivateKey
119
+ var rsaPrivateKey = forge.pki.privateKeyToAsn1(keyPair.privateKey);
120
+
121
+ // wrap an RSAPrivateKey ASN.1 object in a PKCS#8 ASN.1 PrivateKeyInfo
122
+ var privateKeyInfo = forge.pki.wrapRsaPrivateKey(rsaPrivateKey);
123
+
124
+ // convert a PKCS#8 ASN.1 PrivateKeyInfo to PEM
125
+ var pemKey = forge.pki.privateKeyInfoToPem(privateKeyInfo);
126
+ console.log('privatekey ', pemKey)
127
+ return Buffer.from(pemKey);
128
+ }
129
+
130
+
131
+ exports.createPrivateKey = createPrivateKey;
132
+
133
+
134
+ /**
135
+ * Create public key from a private RSA key
136
+ *
137
+ * @param {buffer|string} key PEM encoded private RSA key
138
+ * @returns {Promise<buffer>} PEM encoded public RSA key
139
+ *
140
+ * @example Create public key
141
+ * ```js
142
+ * const publicKey = await acme.forge.createPublicKey(privateKey);
143
+ * ```
144
+ */
145
+
146
+ exports.createPublicKey = async function (key) {
147
+ const privateKey = forge.pki.privateKeyFromPem(key);
148
+ const publicKey = forge.pki.rsa.setPublicKey(privateKey.n, privateKey.e);
149
+ const pemKey = forge.pki.publicKeyToPem(publicKey);
150
+ return Buffer.from(pemKey);
151
+ };
152
+
153
+
154
+ /**
155
+ * Parse body of PEM encoded object form buffer or string
156
+ * If multiple objects are chained, the first body will be returned
157
+ *
158
+ * @param {buffer|string} str PEM encoded buffer or string
159
+ * @returns {string} PEM body
160
+ */
161
+
162
+ exports.getPemBody = (str) => {
163
+ const msg = forge.pem.decode(str)[0];
164
+ return forge.util.encode64(msg.body);
165
+ };
166
+
167
+
168
+ /**
169
+ * Split chain of PEM encoded objects from buffer or string into array
170
+ *
171
+ * @param {buffer|string} str PEM encoded buffer or string
172
+ * @returns {string[]} Array of PEM bodies
173
+ */
174
+
175
+ exports.splitPemChain = (str) => forge.pem.decode(str).map(forge.pem.encode);
176
+
177
+
178
+ /**
179
+ * Get modulus
180
+ *
181
+ * @param {buffer|string} input PEM encoded private key, certificate or CSR
182
+ * @returns {Promise<buffer>} Modulus
183
+ *
184
+ * @example Get modulus
185
+ * ```js
186
+ * const m1 = await acme.forge.getModulus(privateKey);
187
+ * const m2 = await acme.forge.getModulus(certificate);
188
+ * const m3 = await acme.forge.getModulus(certificateRequest);
189
+ * ```
190
+ */
191
+
192
+ exports.getModulus = async function (input) {
193
+ if (!Buffer.isBuffer(input)) {
194
+ input = Buffer.from(input);
195
+ }
196
+
197
+ const obj = forgeObjectFromPem(input);
198
+ return Buffer.from(forge.util.hexToBytes(obj.n.toString(16)), 'binary');
199
+ };
200
+
201
+
202
+ /**
203
+ * Get public exponent
204
+ *
205
+ * @param {buffer|string} input PEM encoded private key, certificate or CSR
206
+ * @returns {Promise<buffer>} Exponent
207
+ *
208
+ * @example Get public exponent
209
+ * ```js
210
+ * const e1 = await acme.forge.getPublicExponent(privateKey);
211
+ * const e2 = await acme.forge.getPublicExponent(certificate);
212
+ * const e3 = await acme.forge.getPublicExponent(certificateRequest);
213
+ * ```
214
+ */
215
+
216
+ exports.getPublicExponent = async function (input) {
217
+ if (!Buffer.isBuffer(input)) {
218
+ input = Buffer.from(input);
219
+ }
220
+
221
+ const obj = forgeObjectFromPem(input);
222
+ return Buffer.from(forge.util.hexToBytes(obj.e.toString(16)), 'binary');
223
+ };
224
+
225
+
226
+ /**
227
+ * Read domains from a Certificate Signing Request
228
+ *
229
+ * @param {buffer|string} csr PEM encoded Certificate Signing Request
230
+ * @returns {Promise<object>} {commonName, altNames}
231
+ *
232
+ * @example Read Certificate Signing Request domains
233
+ * ```js
234
+ * const { commonName, altNames } = await acme.forge.readCsrDomains(certificateRequest);
235
+ *
236
+ * console.log(`Common name: ${commonName}`);
237
+ * console.log(`Alt names: ${altNames.join(', ')}`);
238
+ * ```
239
+ */
240
+
241
+ exports.readCsrDomains = async function (csr) {
242
+ if (!Buffer.isBuffer(csr)) {
243
+ csr = Buffer.from(csr);
244
+ }
245
+
246
+ const obj = forge.pki.certificationRequestFromPem(csr);
247
+ return parseDomains(obj);
248
+ };
249
+
250
+
251
+ /**
252
+ * Read information from a certificate
253
+ *
254
+ * @param {buffer|string} cert PEM encoded certificate
255
+ * @returns {Promise<object>} Certificate info
256
+ *
257
+ * @example Read certificate information
258
+ * ```js
259
+ * const info = await acme.forge.readCertificateInfo(certificate);
260
+ * const { commonName, altNames } = info.domains;
261
+ *
262
+ * console.log(`Not after: ${info.notAfter}`);
263
+ * console.log(`Not before: ${info.notBefore}`);
264
+ *
265
+ * console.log(`Common name: ${commonName}`);
266
+ * console.log(`Alt names: ${altNames.join(', ')}`);
267
+ * ```
268
+ */
269
+
270
+ exports.readCertificateInfo = async function (cert) {
271
+ if (!Buffer.isBuffer(cert)) {
272
+ cert = Buffer.from(cert);
273
+ }
274
+
275
+ const obj = forge.pki.certificateFromPem(cert);
276
+ const issuerCn = (obj.issuer.attributes || []).find((a) => a.name === 'commonName');
277
+
278
+ return {
279
+ issuer: {
280
+ commonName: issuerCn ? issuerCn.value : null
281
+ },
282
+ domains: parseDomains(obj),
283
+ notAfter: obj.validity.notAfter,
284
+ notBefore: obj.validity.notBefore
285
+ };
286
+ };
287
+
288
+
289
+ /**
290
+ * Determine ASN.1 type for CSR subject short name
291
+ * Note: https://tools.ietf.org/html/rfc5280
292
+ *
293
+ * @private
294
+ * @param {string} shortName CSR subject short name
295
+ * @returns {forge.asn1.Type} ASN.1 type
296
+ */
297
+
298
+ function getCsrValueTagClass(shortName) {
299
+ switch (shortName) {
300
+ case 'C':
301
+ return forge.asn1.Type.PRINTABLESTRING;
302
+ case 'E':
303
+ return forge.asn1.Type.IA5STRING;
304
+ default:
305
+ return forge.asn1.Type.UTF8;
306
+ }
307
+ }
308
+
309
+
310
+ /**
311
+ * Create array of short names and values for Certificate Signing Request subjects
312
+ *
313
+ * @private
314
+ * @param {object} subjectObj Key-value of short names and values
315
+ * @returns {object[]} Certificate Signing Request subject array
316
+ */
317
+
318
+ function createCsrSubject(subjectObj) {
319
+ return Object.entries(subjectObj).reduce((result, [shortName, value]) => {
320
+ if (value) {
321
+ const valueTagClass = getCsrValueTagClass(shortName);
322
+ result.push({shortName, value, valueTagClass});
323
+ }
324
+
325
+ return result;
326
+ }, []);
327
+ }
328
+
329
+
330
+ /**
331
+ * Create array of alt names for Certificate Signing Requests
332
+ * Note: https://github.com/digitalbazaar/forge/blob/dfdde475677a8a25c851e33e8f81dca60d90cfb9/lib/x509.js#L1444-L1454
333
+ *
334
+ * @private
335
+ * @param {string[]} altNames Alt names
336
+ * @returns {object[]} Certificate Signing Request alt names array
337
+ */
338
+
339
+ function formatCsrAltNames(altNames) {
340
+ return altNames.map((value) => {
341
+ const type = net.isIP(value) ? 7 : 2;
342
+ return {type, value};
343
+ });
344
+ }
345
+
346
+
347
+ /**
348
+ * Create a Certificate Signing Request
349
+ *
350
+ * @param {object} data
351
+ * @param {number} [data.keySize] Size of newly created private key, default: `2048`
352
+ * @param {string} [data.commonName]
353
+ * @param {array} [data.altNames] default: `[]`
354
+ * @param {string} [data.country]
355
+ * @param {string} [data.state]
356
+ * @param {string} [data.locality]
357
+ * @param {string} [data.organization]
358
+ * @param {string} [data.organizationUnit]
359
+ * @param {string} [data.emailAddress]
360
+ * @param {buffer|string} [key] CSR private key
361
+ * @returns {Promise<buffer[]>} [privateKey, certificateSigningRequest]
362
+ *
363
+ * @example Create a Certificate Signing Request
364
+ * ```js
365
+ * const [certificateKey, certificateRequest] = await acme.forge.createCsr({
366
+ * commonName: 'test.example.com'
367
+ * });
368
+ * ```
369
+ *
370
+ * @example Certificate Signing Request with both common and alternative names
371
+ * ```js
372
+ * const [certificateKey, certificateRequest] = await acme.forge.createCsr({
373
+ * keySize: 4096,
374
+ * commonName: 'test.example.com',
375
+ * altNames: ['foo.example.com', 'bar.example.com']
376
+ * });
377
+ * ```
378
+ *
379
+ * @example Certificate Signing Request with additional information
380
+ * ```js
381
+ * const [certificateKey, certificateRequest] = await acme.forge.createCsr({
382
+ * commonName: 'test.example.com',
383
+ * country: 'US',
384
+ * state: 'California',
385
+ * locality: 'Los Angeles',
386
+ * organization: 'The Company Inc.',
387
+ * organizationUnit: 'IT Department',
388
+ * emailAddress: 'contact@example.com'
389
+ * });
390
+ * ```
391
+ *
392
+ * @example Certificate Signing Request with predefined private key
393
+ * ```js
394
+ * const certificateKey = await acme.forge.createPrivateKey();
395
+ *
396
+ * const [, certificateRequest] = await acme.forge.createCsr({
397
+ * commonName: 'test.example.com'
398
+ * }, certificateKey);
399
+ */
400
+
401
+ exports.createCsr = async function (data, key = null) {
402
+ if (!key) {
403
+ key = await createPrivateKey(data.keySize);
404
+ } else if (!Buffer.isBuffer(key)) {
405
+ key = Buffer.from(key);
406
+ }
407
+
408
+ if (typeof data.altNames === 'undefined') {
409
+ data.altNames = [];
410
+ }
411
+
412
+ const csr = forge.pki.createCertificationRequest();
413
+
414
+ /* Public key */
415
+ const privateKey = forge.pki.privateKeyFromPem(key);
416
+ const publicKey = forge.pki.rsa.setPublicKey(privateKey.n, privateKey.e);
417
+ csr.publicKey = publicKey;
418
+
419
+ /* Ensure subject common name is present in SAN - https://cabforum.org/wp-content/uploads/BRv1.2.3.pdf */
420
+ if (data.commonName && !data.altNames.includes(data.commonName)) {
421
+ data.altNames.unshift(data.commonName);
422
+ }
423
+
424
+ /* Subject */
425
+ const subject = createCsrSubject({
426
+ CN: data.commonName,
427
+ C: data.country,
428
+ ST: data.state,
429
+ L: data.locality,
430
+ O: data.organization,
431
+ OU: data.organizationUnit,
432
+ E: data.emailAddress
433
+ });
434
+
435
+ csr.setSubject(subject);
436
+
437
+ /* SAN extension */
438
+ if (data.altNames.length) {
439
+ csr.setAttributes([{
440
+ name: 'extensionRequest',
441
+ extensions: [{
442
+ name: 'subjectAltName',
443
+ altNames: formatCsrAltNames(data.altNames)
444
+ }]
445
+ }]);
446
+ }
447
+
448
+ /* Sign CSR */
449
+ csr.sign(privateKey);
450
+
451
+ /* Done */
452
+ const pemCsr = forge.pki.certificationRequestToPem(csr);
453
+ return [key, Buffer.from(pemCsr)];
454
+ };