@certd/acme-client 1.39.11 → 1.39.13
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/dist/api.d.ts +151 -0
- package/{src → dist}/api.js +2 -38
- package/dist/auto.d.ts +9 -0
- package/{src → dist}/auto.js +45 -79
- package/dist/axios.d.ts +8 -0
- package/{src → dist}/axios.js +3 -31
- package/dist/client.d.ts +396 -0
- package/{src → dist}/client.js +16 -108
- package/dist/crypto/forge.d.ts +174 -0
- package/{src → dist}/crypto/forge.js +7 -63
- package/dist/crypto/index.d.ts +215 -0
- package/{src → dist}/crypto/index.js +2 -87
- package/dist/error.d.ts +3 -0
- package/{src → dist}/error.js +1 -3
- package/dist/http.d.ts +135 -0
- package/{src → dist}/http.js +3 -65
- package/dist/index.d.ts +58 -0
- package/dist/index.js +90 -0
- package/dist/logger.d.ts +15 -0
- package/{src → dist}/logger.js +4 -9
- package/dist/rfc8555.d.ts +106 -0
- package/dist/rfc8555.js +25 -0
- package/dist/types.d.ts +117 -0
- package/dist/types.js +1 -0
- package/dist/util.d.ts +85 -0
- package/{src → dist}/util.js +11 -76
- package/dist/verify.d.ts +14 -0
- package/dist/verify.js +214 -0
- package/dist/wait.d.ts +1 -0
- package/{src → dist}/wait.js +1 -0
- package/package.json +18 -12
- package/types/index.d.ts +15 -4
- package/types/index.test-d.ts +10 -3
- package/src/index.js +0 -106
- package/src/verify.js +0 -231
|
@@ -1,27 +1,22 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
1
2
|
/**
|
|
2
3
|
* Native Node.js crypto interface
|
|
3
4
|
*
|
|
4
5
|
* @namespace crypto
|
|
5
6
|
*/
|
|
6
|
-
import
|
|
7
|
+
import net from 'net';
|
|
7
8
|
import { promisify } from 'util';
|
|
8
9
|
import crypto from 'crypto';
|
|
9
10
|
import asn1js from 'asn1js';
|
|
10
11
|
import x509 from '@peculiar/x509';
|
|
11
|
-
|
|
12
|
-
|
|
13
12
|
const randomInt = promisify(crypto.randomInt);
|
|
14
13
|
const generateKeyPair = promisify(crypto.generateKeyPair);
|
|
15
|
-
|
|
16
14
|
/* Use Node.js Web Crypto API */
|
|
17
15
|
x509.cryptoProvider.set(crypto.webcrypto);
|
|
18
|
-
|
|
19
16
|
/* id-ce-subjectAltName - https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 */
|
|
20
17
|
const subjectAltNameOID = '2.5.29.17';
|
|
21
|
-
|
|
22
18
|
/* id-pe-acmeIdentifier - https://datatracker.ietf.org/doc/html/rfc8737#section-6.1 */
|
|
23
19
|
const alpnAcmeIdentifierOID = '1.3.6.1.5.5.7.1.31';
|
|
24
|
-
|
|
25
20
|
/**
|
|
26
21
|
* Determine key type and info by attempting to derive public key
|
|
27
22
|
*
|
|
@@ -29,14 +24,12 @@ const alpnAcmeIdentifierOID = '1.3.6.1.5.5.7.1.31';
|
|
|
29
24
|
* @param {buffer|string} keyPem PEM encoded private or public key
|
|
30
25
|
* @returns {object}
|
|
31
26
|
*/
|
|
32
|
-
|
|
33
27
|
function getKeyInfo(keyPem) {
|
|
34
28
|
const result = {
|
|
35
29
|
isRSA: false,
|
|
36
30
|
isECDSA: false,
|
|
37
31
|
publicKey: crypto.createPublicKey(keyPem),
|
|
38
32
|
};
|
|
39
|
-
|
|
40
33
|
if (result.publicKey.asymmetricKeyType === 'rsa') {
|
|
41
34
|
result.isRSA = true;
|
|
42
35
|
}
|
|
@@ -46,10 +39,8 @@ function getKeyInfo(keyPem) {
|
|
|
46
39
|
else {
|
|
47
40
|
throw new Error('Unable to parse key information, unknown format');
|
|
48
41
|
}
|
|
49
|
-
|
|
50
42
|
return result;
|
|
51
43
|
}
|
|
52
|
-
|
|
53
44
|
/**
|
|
54
45
|
* Generate a private RSA key
|
|
55
46
|
*
|
|
@@ -66,7 +57,6 @@ function getKeyInfo(keyPem) {
|
|
|
66
57
|
* const privateKey = await acme.crypto.createPrivateRsaKey(4096);
|
|
67
58
|
* ```
|
|
68
59
|
*/
|
|
69
|
-
|
|
70
60
|
export async function createPrivateRsaKey(modulusLength = 2048, encodingType = 'pkcs8') {
|
|
71
61
|
const pair = await generateKeyPair('rsa', {
|
|
72
62
|
modulusLength,
|
|
@@ -75,19 +65,14 @@ export async function createPrivateRsaKey(modulusLength = 2048, encodingType = '
|
|
|
75
65
|
format: 'pem',
|
|
76
66
|
},
|
|
77
67
|
});
|
|
78
|
-
|
|
79
68
|
return Buffer.from(pair.privateKey);
|
|
80
69
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
70
|
/**
|
|
84
71
|
* Alias of `createPrivateRsaKey()`
|
|
85
72
|
*
|
|
86
73
|
* @function
|
|
87
74
|
*/
|
|
88
|
-
|
|
89
75
|
export const createPrivateKey = createPrivateRsaKey;
|
|
90
|
-
|
|
91
76
|
/**
|
|
92
77
|
* Generate a private ECDSA key
|
|
93
78
|
*
|
|
@@ -104,7 +89,6 @@ export const createPrivateKey = createPrivateRsaKey;
|
|
|
104
89
|
* const privateKey = await acme.crypto.createPrivateEcdsaKey('P-384');
|
|
105
90
|
* ```
|
|
106
91
|
*/
|
|
107
|
-
|
|
108
92
|
export const createPrivateEcdsaKey = async (namedCurve = 'P-256', encodingType = 'pkcs8') => {
|
|
109
93
|
const pair = await generateKeyPair('ec', {
|
|
110
94
|
namedCurve,
|
|
@@ -113,10 +97,8 @@ export const createPrivateEcdsaKey = async (namedCurve = 'P-256', encodingType =
|
|
|
113
97
|
format: 'pem',
|
|
114
98
|
},
|
|
115
99
|
});
|
|
116
|
-
|
|
117
100
|
return Buffer.from(pair.privateKey);
|
|
118
101
|
};
|
|
119
|
-
|
|
120
102
|
/**
|
|
121
103
|
* Get a public key derived from a RSA or ECDSA key
|
|
122
104
|
*
|
|
@@ -128,18 +110,14 @@ export const createPrivateEcdsaKey = async (namedCurve = 'P-256', encodingType =
|
|
|
128
110
|
* const publicKey = acme.crypto.getPublicKey(privateKey);
|
|
129
111
|
* ```
|
|
130
112
|
*/
|
|
131
|
-
|
|
132
113
|
export const getPublicKey = (keyPem) => {
|
|
133
114
|
const info = getKeyInfo(keyPem);
|
|
134
|
-
|
|
135
115
|
const publicKey = info.publicKey.export({
|
|
136
116
|
type: info.isECDSA ? 'spki' : 'pkcs1',
|
|
137
117
|
format: 'pem',
|
|
138
118
|
});
|
|
139
|
-
|
|
140
119
|
return Buffer.from(publicKey);
|
|
141
120
|
};
|
|
142
|
-
|
|
143
121
|
/**
|
|
144
122
|
* Get a JSON Web Key derived from a RSA or ECDSA key
|
|
145
123
|
*
|
|
@@ -153,20 +131,16 @@ export const getPublicKey = (keyPem) => {
|
|
|
153
131
|
* const jwk = acme.crypto.getJwk(privateKey);
|
|
154
132
|
* ```
|
|
155
133
|
*/
|
|
156
|
-
|
|
157
134
|
export function getJwk(keyPem) {
|
|
158
135
|
const jwk = crypto.createPublicKey(keyPem).export({
|
|
159
136
|
format: 'jwk',
|
|
160
137
|
});
|
|
161
|
-
|
|
162
138
|
/* Sort keys */
|
|
163
139
|
return Object.keys(jwk).sort().reduce((result, k) => {
|
|
164
140
|
result[k] = jwk[k];
|
|
165
141
|
return result;
|
|
166
142
|
}, {});
|
|
167
143
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
144
|
/**
|
|
171
145
|
* Produce CryptoKeyPair and signing algorithm from a PEM encoded private key
|
|
172
146
|
*
|
|
@@ -174,56 +148,44 @@ export function getJwk(keyPem) {
|
|
|
174
148
|
* @param {buffer|string} keyPem PEM encoded private key
|
|
175
149
|
* @returns {Promise<array>} [keyPair, signingAlgorithm]
|
|
176
150
|
*/
|
|
177
|
-
|
|
178
151
|
async function getWebCryptoKeyPair(keyPem) {
|
|
179
152
|
const info = getKeyInfo(keyPem);
|
|
180
153
|
const jwk = getJwk(keyPem);
|
|
181
|
-
|
|
182
154
|
/* Signing algorithm */
|
|
183
155
|
const sigalg = {
|
|
184
156
|
name: 'RSASSA-PKCS1-v1_5',
|
|
185
157
|
hash: { name: 'SHA-256' },
|
|
186
158
|
};
|
|
187
|
-
|
|
188
159
|
if (info.isECDSA) {
|
|
189
160
|
sigalg.name = 'ECDSA';
|
|
190
161
|
sigalg.namedCurve = jwk.crv;
|
|
191
|
-
|
|
192
162
|
if (jwk.crv === 'P-384') {
|
|
193
163
|
sigalg.hash.name = 'SHA-384';
|
|
194
164
|
}
|
|
195
|
-
|
|
196
165
|
if (jwk.crv === 'P-521') {
|
|
197
166
|
sigalg.hash.name = 'SHA-512';
|
|
198
167
|
}
|
|
199
168
|
}
|
|
200
|
-
|
|
201
169
|
/* Decode PEM and import into CryptoKeyPair */
|
|
202
170
|
const privateKeyDec = x509.PemConverter.decodeFirst(keyPem.toString());
|
|
203
171
|
const privateKey = await crypto.webcrypto.subtle.importKey('pkcs8', privateKeyDec, sigalg, true, ['sign']);
|
|
204
172
|
const publicKey = await crypto.webcrypto.subtle.importKey('jwk', jwk, sigalg, true, ['verify']);
|
|
205
|
-
|
|
206
173
|
return [{ privateKey, publicKey }, sigalg];
|
|
207
174
|
}
|
|
208
|
-
|
|
209
175
|
/**
|
|
210
176
|
* Split chain of PEM encoded objects from string into array
|
|
211
177
|
*
|
|
212
178
|
* @param {buffer|string} chainPem PEM encoded object chain
|
|
213
179
|
* @returns {string[]} Array of PEM objects including headers
|
|
214
180
|
*/
|
|
215
|
-
|
|
216
181
|
export function splitPemChain(chainPem) {
|
|
217
182
|
if (Buffer.isBuffer(chainPem)) {
|
|
218
183
|
chainPem = chainPem.toString();
|
|
219
184
|
}
|
|
220
|
-
|
|
221
185
|
/* Decode into array and re-encode */
|
|
222
186
|
return x509.PemConverter.decodeWithHeaders(chainPem)
|
|
223
187
|
.map((params) => x509.PemConverter.encode([params]));
|
|
224
188
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
189
|
/**
|
|
228
190
|
* Parse body of PEM encoded object and return a Base64URL string
|
|
229
191
|
* If multiple objects are chained, the first body will be returned
|
|
@@ -231,19 +193,15 @@ export function splitPemChain(chainPem) {
|
|
|
231
193
|
* @param {buffer|string} pem PEM encoded chain or object
|
|
232
194
|
* @returns {string} Base64URL-encoded body
|
|
233
195
|
*/
|
|
234
|
-
|
|
235
196
|
export const getPemBodyAsB64u = (pem) => {
|
|
236
197
|
const chain = splitPemChain(pem);
|
|
237
|
-
|
|
238
198
|
if (!chain.length) {
|
|
239
199
|
throw new Error('Unable to parse PEM body from string');
|
|
240
200
|
}
|
|
241
|
-
|
|
242
201
|
/* Select first object, extract body and convert to b64u */
|
|
243
202
|
const dec = x509.PemConverter.decodeFirst(chain[0]);
|
|
244
203
|
return Buffer.from(dec).toString('base64url');
|
|
245
204
|
};
|
|
246
|
-
|
|
247
205
|
/**
|
|
248
206
|
* Parse domains from a certificate or CSR
|
|
249
207
|
*
|
|
@@ -251,23 +209,19 @@ export const getPemBodyAsB64u = (pem) => {
|
|
|
251
209
|
* @param {object} input x509.Certificate or x509.Pkcs10CertificateRequest
|
|
252
210
|
* @returns {object} {commonName, altNames}
|
|
253
211
|
*/
|
|
254
|
-
|
|
255
212
|
function parseDomains(input) {
|
|
256
213
|
const commonName = input.subjectName.getField('CN').pop() || null;
|
|
257
214
|
const altNamesRaw = input.getExtension(subjectAltNameOID);
|
|
258
215
|
let altNames = [];
|
|
259
|
-
|
|
260
216
|
if (altNamesRaw) {
|
|
261
217
|
const altNamesExt = new x509.SubjectAlternativeNameExtension(altNamesRaw.rawData);
|
|
262
218
|
altNames = altNames.concat(altNamesExt.names.items.map((i) => i.value));
|
|
263
219
|
}
|
|
264
|
-
|
|
265
220
|
return {
|
|
266
221
|
commonName,
|
|
267
222
|
altNames,
|
|
268
223
|
};
|
|
269
224
|
}
|
|
270
|
-
|
|
271
225
|
/**
|
|
272
226
|
* Read domains from a Certificate Signing Request
|
|
273
227
|
*
|
|
@@ -282,7 +236,6 @@ function parseDomains(input) {
|
|
|
282
236
|
* console.log(`Alt names: ${altNames.join(', ')}`);
|
|
283
237
|
* ```
|
|
284
238
|
*/
|
|
285
|
-
|
|
286
239
|
export const readCsrDomains = (csrPem) => {
|
|
287
240
|
if (Buffer.isBuffer(csrPem)) {
|
|
288
241
|
csrPem = csrPem.toString();
|
|
@@ -291,7 +244,6 @@ export const readCsrDomains = (csrPem) => {
|
|
|
291
244
|
const csr = new x509.Pkcs10CertificateRequest(dec);
|
|
292
245
|
return parseDomains(csr);
|
|
293
246
|
};
|
|
294
|
-
|
|
295
247
|
/**
|
|
296
248
|
* Read information from a certificate
|
|
297
249
|
* If multiple certificates are chained, the first will be read
|
|
@@ -311,15 +263,12 @@ export const readCsrDomains = (csrPem) => {
|
|
|
311
263
|
* console.log(`Alt names: ${altNames.join(', ')}`);
|
|
312
264
|
* ```
|
|
313
265
|
*/
|
|
314
|
-
|
|
315
266
|
export const readCertificateInfo = (certPem) => {
|
|
316
267
|
if (Buffer.isBuffer(certPem)) {
|
|
317
268
|
certPem = certPem.toString();
|
|
318
269
|
}
|
|
319
|
-
|
|
320
270
|
const dec = x509.PemConverter.decodeFirst(certPem);
|
|
321
271
|
const cert = new x509.X509Certificate(dec);
|
|
322
|
-
|
|
323
272
|
return {
|
|
324
273
|
issuer: {
|
|
325
274
|
commonName: cert.issuerName.getField('CN').pop() || null,
|
|
@@ -329,7 +278,6 @@ export const readCertificateInfo = (certPem) => {
|
|
|
329
278
|
notAfter: cert.notAfter,
|
|
330
279
|
};
|
|
331
280
|
};
|
|
332
|
-
|
|
333
281
|
/**
|
|
334
282
|
* Determine ASN.1 character string type for CSR subject field name
|
|
335
283
|
*
|
|
@@ -340,7 +288,6 @@ export const readCertificateInfo = (certPem) => {
|
|
|
340
288
|
* @param {string} field CSR subject field name
|
|
341
289
|
* @returns {string} ASN.1 character string type
|
|
342
290
|
*/
|
|
343
|
-
|
|
344
291
|
function getCsrAsn1CharStringType(field) {
|
|
345
292
|
switch (field) {
|
|
346
293
|
case 'C':
|
|
@@ -351,7 +298,6 @@ function getCsrAsn1CharStringType(field) {
|
|
|
351
298
|
return 'utf8String';
|
|
352
299
|
}
|
|
353
300
|
}
|
|
354
|
-
|
|
355
301
|
/**
|
|
356
302
|
* Create array of subject fields for a Certificate Signing Request
|
|
357
303
|
*
|
|
@@ -361,18 +307,15 @@ function getCsrAsn1CharStringType(field) {
|
|
|
361
307
|
* @param {object} input Key-value of subject fields
|
|
362
308
|
* @returns {object[]} Certificate Signing Request subject array
|
|
363
309
|
*/
|
|
364
|
-
|
|
365
310
|
function createCsrSubject(input) {
|
|
366
311
|
return Object.entries(input).reduce((result, [type, value]) => {
|
|
367
312
|
if (value) {
|
|
368
313
|
const ds = getCsrAsn1CharStringType(type);
|
|
369
314
|
result.push({ [type]: [{ [ds]: value }] });
|
|
370
315
|
}
|
|
371
|
-
|
|
372
316
|
return result;
|
|
373
317
|
}, []);
|
|
374
318
|
}
|
|
375
|
-
|
|
376
319
|
/**
|
|
377
320
|
* Create x509 subject alternate name extension
|
|
378
321
|
*
|
|
@@ -382,14 +325,12 @@ function createCsrSubject(input) {
|
|
|
382
325
|
* @param {string[]} altNames Array of alt names
|
|
383
326
|
* @returns {x509.SubjectAlternativeNameExtension} Subject alternate name extension
|
|
384
327
|
*/
|
|
385
|
-
|
|
386
328
|
function createSubjectAltNameExtension(altNames) {
|
|
387
329
|
return new x509.SubjectAlternativeNameExtension(altNames.map((value) => {
|
|
388
330
|
const type = net.isIP(value) ? 'ip' : 'dns';
|
|
389
331
|
return { type, value };
|
|
390
332
|
}));
|
|
391
333
|
}
|
|
392
|
-
|
|
393
334
|
/**
|
|
394
335
|
* Create a Certificate Signing Request
|
|
395
336
|
*
|
|
@@ -445,7 +386,6 @@ function createSubjectAltNameExtension(altNames) {
|
|
|
445
386
|
* }, certificateKey);
|
|
446
387
|
* ```
|
|
447
388
|
*/
|
|
448
|
-
|
|
449
389
|
export const createCsr = async (data, keyPem = null) => {
|
|
450
390
|
if (!keyPem) {
|
|
451
391
|
keyPem = await createPrivateRsaKey(data.keySize);
|
|
@@ -453,27 +393,21 @@ export const createCsr = async (data, keyPem = null) => {
|
|
|
453
393
|
else if (!Buffer.isBuffer(keyPem)) {
|
|
454
394
|
keyPem = Buffer.from(keyPem);
|
|
455
395
|
}
|
|
456
|
-
|
|
457
396
|
if (typeof data.altNames === 'undefined') {
|
|
458
397
|
data.altNames = [];
|
|
459
398
|
}
|
|
460
|
-
|
|
461
399
|
/* Ensure subject common name is present in SAN - https://cabforum.org/wp-content/uploads/BRv1.2.3.pdf */
|
|
462
400
|
if (data.commonName && !data.altNames.includes(data.commonName)) {
|
|
463
401
|
data.altNames.unshift(data.commonName);
|
|
464
402
|
}
|
|
465
|
-
|
|
466
403
|
/* CryptoKeyPair and signing algorithm from private key */
|
|
467
404
|
const [keys, signingAlgorithm] = await getWebCryptoKeyPair(keyPem);
|
|
468
|
-
|
|
469
405
|
const extensions = [
|
|
470
406
|
/* https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3 */
|
|
471
407
|
new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.keyEncipherment), // eslint-disable-line no-bitwise
|
|
472
|
-
|
|
473
408
|
/* https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 */
|
|
474
409
|
createSubjectAltNameExtension(data.altNames),
|
|
475
410
|
];
|
|
476
|
-
|
|
477
411
|
/* Create CSR */
|
|
478
412
|
const csr = await x509.Pkcs10CertificateRequestGenerator.create({
|
|
479
413
|
keys,
|
|
@@ -489,12 +423,10 @@ export const createCsr = async (data, keyPem = null) => {
|
|
|
489
423
|
E: data.emailAddress,
|
|
490
424
|
}),
|
|
491
425
|
});
|
|
492
|
-
|
|
493
426
|
/* Done */
|
|
494
427
|
const pem = csr.toString('pem');
|
|
495
428
|
return [keyPem, Buffer.from(pem)];
|
|
496
429
|
};
|
|
497
|
-
|
|
498
430
|
/**
|
|
499
431
|
* Create a self-signed ALPN certificate for TLS-ALPN-01 challenges
|
|
500
432
|
*
|
|
@@ -516,7 +448,6 @@ export const createCsr = async (data, keyPem = null) => {
|
|
|
516
448
|
* const [, alpnCertificate] = await acme.crypto.createAlpnCertificate(authz, keyAuthorization, alpnKey);
|
|
517
449
|
* ```
|
|
518
450
|
*/
|
|
519
|
-
|
|
520
451
|
export const createAlpnCertificate = async (authz, keyAuthorization, keyPem = null) => {
|
|
521
452
|
if (!keyPem) {
|
|
522
453
|
keyPem = await createPrivateRsaKey();
|
|
@@ -524,36 +455,27 @@ export const createAlpnCertificate = async (authz, keyAuthorization, keyPem = nu
|
|
|
524
455
|
else if (!Buffer.isBuffer(keyPem)) {
|
|
525
456
|
keyPem = Buffer.from(keyPem);
|
|
526
457
|
}
|
|
527
|
-
|
|
528
458
|
const now = new Date();
|
|
529
459
|
const commonName = authz.identifier.value;
|
|
530
|
-
|
|
531
460
|
/* Pseudo-random serial - max 20 bytes, 11 for epoch (year 5138), 9 random */
|
|
532
461
|
const random = await randomInt(1, 999999999);
|
|
533
462
|
const serialNumber = `${Math.floor(now.getTime() / 1000)}${random}`;
|
|
534
|
-
|
|
535
463
|
/* CryptoKeyPair and signing algorithm from private key */
|
|
536
464
|
const [keys, signingAlgorithm] = await getWebCryptoKeyPair(keyPem);
|
|
537
|
-
|
|
538
465
|
const extensions = [
|
|
539
466
|
/* https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3 */
|
|
540
467
|
new x509.KeyUsagesExtension(x509.KeyUsageFlags.keyCertSign | x509.KeyUsageFlags.cRLSign, true), // eslint-disable-line no-bitwise
|
|
541
|
-
|
|
542
468
|
/* https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.9 */
|
|
543
469
|
new x509.BasicConstraintsExtension(true, 2, true),
|
|
544
|
-
|
|
545
470
|
/* https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.2 */
|
|
546
471
|
await x509.SubjectKeyIdentifierExtension.create(keys.publicKey),
|
|
547
|
-
|
|
548
472
|
/* https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 */
|
|
549
473
|
createSubjectAltNameExtension([commonName]),
|
|
550
474
|
];
|
|
551
|
-
|
|
552
475
|
/* ALPN extension */
|
|
553
476
|
const payload = crypto.createHash('sha256').update(keyAuthorization).digest('hex');
|
|
554
477
|
const octstr = new asn1js.OctetString({ valueHex: Buffer.from(payload, 'hex') });
|
|
555
478
|
extensions.push(new x509.Extension(alpnAcmeIdentifierOID, true, octstr.toBER()));
|
|
556
|
-
|
|
557
479
|
/* Self-signed ALPN certificate */
|
|
558
480
|
const cert = await x509.X509CertificateGenerator.createSelfSigned({
|
|
559
481
|
keys,
|
|
@@ -566,12 +488,10 @@ export const createAlpnCertificate = async (authz, keyAuthorization, keyPem = nu
|
|
|
566
488
|
CN: commonName,
|
|
567
489
|
}),
|
|
568
490
|
});
|
|
569
|
-
|
|
570
491
|
/* Done */
|
|
571
492
|
const pem = cert.toString('pem');
|
|
572
493
|
return [keyPem, Buffer.from(pem)];
|
|
573
494
|
};
|
|
574
|
-
|
|
575
495
|
/**
|
|
576
496
|
* Validate that a ALPN certificate contains the expected key authorization
|
|
577
497
|
*
|
|
@@ -579,22 +499,17 @@ export const createAlpnCertificate = async (authz, keyAuthorization, keyPem = nu
|
|
|
579
499
|
* @param {string} keyAuthorization Expected challenge key authorization
|
|
580
500
|
* @returns {boolean} True when valid
|
|
581
501
|
*/
|
|
582
|
-
|
|
583
502
|
export const isAlpnCertificateAuthorizationValid = (certPem, keyAuthorization) => {
|
|
584
503
|
const expected = crypto.createHash('sha256').update(keyAuthorization).digest('hex');
|
|
585
|
-
|
|
586
504
|
/* Attempt to locate ALPN extension */
|
|
587
505
|
const cert = new x509.X509Certificate(certPem);
|
|
588
506
|
const ext = cert.getExtension(alpnAcmeIdentifierOID);
|
|
589
|
-
|
|
590
507
|
if (!ext) {
|
|
591
508
|
throw new Error('Unable to locate ALPN extension within parsed certificate');
|
|
592
509
|
}
|
|
593
|
-
|
|
594
510
|
/* Decode extension value */
|
|
595
511
|
const parsed = asn1js.fromBER(ext.value);
|
|
596
512
|
const result = Buffer.from(parsed.result.valueBlock.valueHexView).toString('hex');
|
|
597
|
-
|
|
598
513
|
/* Return true if match */
|
|
599
514
|
return (result === expected);
|
|
600
515
|
};
|
package/dist/error.d.ts
ADDED
package/{src → dist}/error.js
RENAMED
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACME HTTP client
|
|
3
|
+
*
|
|
4
|
+
* @class
|
|
5
|
+
* @param {string} directoryUrl ACME directory URL
|
|
6
|
+
* @param {buffer} accountKey PEM encoded account private key
|
|
7
|
+
* @param {object} [opts.externalAccountBinding]
|
|
8
|
+
* @param {string} [opts.externalAccountBinding.kid] External account binding KID
|
|
9
|
+
* @param {string} [opts.externalAccountBinding.hmacKey] External account binding HMAC key
|
|
10
|
+
*/
|
|
11
|
+
declare class HttpClient {
|
|
12
|
+
constructor(directoryUrl: any, accountKey: any, externalAccountBinding: {}, urlMapping: {}, logger: any, cacheNonce?: boolean);
|
|
13
|
+
pushNonce(nonce: any): void;
|
|
14
|
+
popNonce(): any;
|
|
15
|
+
/**
|
|
16
|
+
* HTTP request
|
|
17
|
+
*
|
|
18
|
+
* @param {string} url HTTP URL
|
|
19
|
+
* @param {string} method HTTP method
|
|
20
|
+
* @param {object} [opts] Request options
|
|
21
|
+
* @returns {Promise<object>} HTTP response
|
|
22
|
+
*/
|
|
23
|
+
request(url: any, method: any, opts?: {}): Promise<import("axios").AxiosResponse<any, any>>;
|
|
24
|
+
/**
|
|
25
|
+
* Get ACME provider directory
|
|
26
|
+
*
|
|
27
|
+
* https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.1
|
|
28
|
+
*
|
|
29
|
+
* @returns {Promise<object>} ACME directory contents
|
|
30
|
+
*/
|
|
31
|
+
getDirectory(): Promise<any>;
|
|
32
|
+
/**
|
|
33
|
+
* Get JSON Web Key
|
|
34
|
+
*
|
|
35
|
+
* @returns {object} JSON Web Key
|
|
36
|
+
*/
|
|
37
|
+
getJwk(): any;
|
|
38
|
+
/**
|
|
39
|
+
* Get nonce from directory API endpoint
|
|
40
|
+
*
|
|
41
|
+
* https://datatracker.ietf.org/doc/html/rfc8555#section-7.2
|
|
42
|
+
*
|
|
43
|
+
* @returns {Promise<string>} Nonce
|
|
44
|
+
*/
|
|
45
|
+
getNonce(): Promise<any>;
|
|
46
|
+
/**
|
|
47
|
+
* Get URL for a directory resource
|
|
48
|
+
*
|
|
49
|
+
* @param {string} resource API resource name
|
|
50
|
+
* @returns {Promise<string>} URL
|
|
51
|
+
*/
|
|
52
|
+
getResourceUrl(resource: any): Promise<any>;
|
|
53
|
+
/**
|
|
54
|
+
* Get directory meta field
|
|
55
|
+
*
|
|
56
|
+
* @param {string} field Meta field name
|
|
57
|
+
* @returns {Promise<string|null>} Meta field value
|
|
58
|
+
*/
|
|
59
|
+
getMetaField(field: any): Promise<any>;
|
|
60
|
+
/**
|
|
61
|
+
* Prepare HTTP request body for signature
|
|
62
|
+
*
|
|
63
|
+
* @param {string} alg JWS algorithm
|
|
64
|
+
* @param {string} url Request URL
|
|
65
|
+
* @param {object} [payload] Request payload
|
|
66
|
+
* @param {object} [opts]
|
|
67
|
+
* @param {string} [opts.nonce] JWS anti-replay nonce
|
|
68
|
+
* @param {string} [opts.kid] JWS KID
|
|
69
|
+
* @returns {object} Signed HTTP request body
|
|
70
|
+
*/
|
|
71
|
+
prepareSignedBody(alg: any, url: any, payload?: any, { nonce, kid }?: {
|
|
72
|
+
nonce?: any;
|
|
73
|
+
kid?: any;
|
|
74
|
+
}): {
|
|
75
|
+
payload: string;
|
|
76
|
+
protected: string;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Create JWS HTTP request body using HMAC
|
|
80
|
+
*
|
|
81
|
+
* @param {string} hmacKey HMAC key
|
|
82
|
+
* @param {string} url Request URL
|
|
83
|
+
* @param {object} [payload] Request payload
|
|
84
|
+
* @param {object} [opts]
|
|
85
|
+
* @param {string} [opts.nonce] JWS anti-replay nonce
|
|
86
|
+
* @param {string} [opts.kid] JWS KID
|
|
87
|
+
* @returns {object} Signed HMAC request body
|
|
88
|
+
*/
|
|
89
|
+
createSignedHmacBody(hmacKey: any, url: any, payload?: any, { nonce, kid }?: {
|
|
90
|
+
nonce?: any;
|
|
91
|
+
kid?: any;
|
|
92
|
+
}): {
|
|
93
|
+
payload: string;
|
|
94
|
+
protected: string;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Create JWS HTTP request body using RSA or ECC
|
|
98
|
+
*
|
|
99
|
+
* https://datatracker.ietf.org/doc/html/rfc7515
|
|
100
|
+
*
|
|
101
|
+
* @param {string} url Request URL
|
|
102
|
+
* @param {object} [payload] Request payload
|
|
103
|
+
* @param {object} [opts]
|
|
104
|
+
* @param {string} [opts.nonce] JWS nonce
|
|
105
|
+
* @param {string} [opts.kid] JWS KID
|
|
106
|
+
* @returns {object} JWS request body
|
|
107
|
+
*/
|
|
108
|
+
createSignedBody(url: any, payload?: any, { nonce, kid }?: {
|
|
109
|
+
nonce?: any;
|
|
110
|
+
kid?: any;
|
|
111
|
+
}): {
|
|
112
|
+
payload: string;
|
|
113
|
+
protected: string;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Signed HTTP request
|
|
117
|
+
*
|
|
118
|
+
* https://datatracker.ietf.org/doc/html/rfc8555#section-6.2
|
|
119
|
+
*
|
|
120
|
+
* @param {string} url Request URL
|
|
121
|
+
* @param {object} payload Request payload
|
|
122
|
+
* @param {object} [opts]
|
|
123
|
+
* @param {string} [opts.kid] JWS KID
|
|
124
|
+
* @param {string} [opts.nonce] JWS anti-replay nonce
|
|
125
|
+
* @param {boolean} [opts.includeExternalAccountBinding] Include EAB in request
|
|
126
|
+
* @param {number} [attempts] Request attempt counter
|
|
127
|
+
* @returns {Promise<object>} HTTP response
|
|
128
|
+
*/
|
|
129
|
+
signedRequest(url: any, payload: any, { kid, nonce, includeExternalAccountBinding }?: {
|
|
130
|
+
kid?: any;
|
|
131
|
+
nonce?: any;
|
|
132
|
+
includeExternalAccountBinding?: boolean;
|
|
133
|
+
}, attempts?: number): any;
|
|
134
|
+
}
|
|
135
|
+
export default HttpClient;
|