@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.
- package/CHANGELOG.md +152 -152
- package/LICENSE +21 -21
- package/README.md +199 -199
- package/package.json +5 -4
- package/src/api.js +243 -243
- package/src/auto.js +203 -199
- package/src/axios.js +40 -40
- package/src/client.js +716 -716
- package/src/crypto/forge.js +454 -445
- package/src/http.js +241 -241
- package/src/index.js +31 -31
- package/src/util.js +173 -172
- package/src/util.log.js +8 -8
- package/src/verify.js +96 -96
- package/types/index.d.ts +141 -141
- package/types/rfc8555.d.ts +127 -127
- package/types/test.ts +70 -70
- package/types/tsconfig.json +11 -11
- package/types/tslint.json +6 -6
package/src/crypto/forge.js
CHANGED
|
@@ -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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
* @
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
*
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
* @param {buffer|string}
|
|
172
|
-
* @returns {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
*
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
*
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
* @param {
|
|
351
|
-
* @
|
|
352
|
-
*
|
|
353
|
-
* @
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
* }
|
|
358
|
-
*
|
|
359
|
-
*
|
|
360
|
-
* @
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
377
|
-
*
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
* ```
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
+
};
|