@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/http.js
CHANGED
|
@@ -1,241 +1,241 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ACME HTTP client
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const crypto = require('crypto');
|
|
6
|
-
const logger = require('./util.log.js');
|
|
7
|
-
|
|
8
|
-
const debug = logger.info;
|
|
9
|
-
const axios = require('./axios');
|
|
10
|
-
const util = require('./util');
|
|
11
|
-
const forge = require('./crypto/forge');
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* ACME HTTP client
|
|
16
|
-
*
|
|
17
|
-
* @class
|
|
18
|
-
* @param {string} directoryUrl ACME directory URL
|
|
19
|
-
* @param {buffer} accountKey PEM encoded account private key
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
class HttpClient {
|
|
23
|
-
constructor(directoryUrl, accountKey) {
|
|
24
|
-
this.directoryUrl = directoryUrl;
|
|
25
|
-
this.accountKey = accountKey;
|
|
26
|
-
|
|
27
|
-
this.maxBadNonceRetries = 5;
|
|
28
|
-
this.directory = null;
|
|
29
|
-
this.jwk = null;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* HTTP request
|
|
35
|
-
*
|
|
36
|
-
* @param {string} url HTTP URL
|
|
37
|
-
* @param {string} method HTTP method
|
|
38
|
-
* @param {object} [opts] Request options
|
|
39
|
-
* @returns {Promise<object>} HTTP response
|
|
40
|
-
*/
|
|
41
|
-
|
|
42
|
-
async request(url, method, opts = {}) {
|
|
43
|
-
opts.url = url;
|
|
44
|
-
opts.method = method;
|
|
45
|
-
opts.validateStatus = null;
|
|
46
|
-
|
|
47
|
-
/* Headers */
|
|
48
|
-
if (typeof opts.headers === 'undefined') {
|
|
49
|
-
opts.headers = {};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
opts.headers['Content-Type'] = 'application/jose+json';
|
|
53
|
-
|
|
54
|
-
/* Request */
|
|
55
|
-
logger.info(`HTTP request: ${method} ${url}`);
|
|
56
|
-
const resp = await axios.request(opts);
|
|
57
|
-
|
|
58
|
-
logger.info(`RESP ${resp.status} ${method} ${url}`);
|
|
59
|
-
return resp;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Ensure provider directory exists
|
|
65
|
-
*
|
|
66
|
-
* https://tools.ietf.org/html/rfc8555#section-7.1.1
|
|
67
|
-
*
|
|
68
|
-
* @returns {Promise}
|
|
69
|
-
*/
|
|
70
|
-
|
|
71
|
-
async getDirectory() {
|
|
72
|
-
if (!this.directory) {
|
|
73
|
-
const resp = await this.request(this.directoryUrl, 'get');
|
|
74
|
-
this.directory = resp.data;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Get JSON Web Key
|
|
81
|
-
*
|
|
82
|
-
* @returns {Promise<object>} {e, kty, n}
|
|
83
|
-
*/
|
|
84
|
-
|
|
85
|
-
async getJwk() {
|
|
86
|
-
if (this.jwk) {
|
|
87
|
-
return this.jwk;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const exponent = await forge.getPublicExponent(this.accountKey);
|
|
91
|
-
const modulus = await forge.getModulus(this.accountKey);
|
|
92
|
-
|
|
93
|
-
this.jwk = {
|
|
94
|
-
e: util.b64encode(exponent),
|
|
95
|
-
kty: 'RSA',
|
|
96
|
-
n: util.b64encode(modulus)
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
return this.jwk;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Get nonce from directory API endpoint
|
|
105
|
-
*
|
|
106
|
-
* https://tools.ietf.org/html/rfc8555#section-7.2
|
|
107
|
-
*
|
|
108
|
-
* @returns {Promise<string>} nonce
|
|
109
|
-
*/
|
|
110
|
-
|
|
111
|
-
async getNonce() {
|
|
112
|
-
const url = await this.getResourceUrl('newNonce');
|
|
113
|
-
const resp = await this.request(url, 'head');
|
|
114
|
-
|
|
115
|
-
if (!resp.headers['replay-nonce']) {
|
|
116
|
-
throw new Error('Failed to get nonce from ACME provider');
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return resp.headers['replay-nonce'];
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Get URL for a directory resource
|
|
125
|
-
*
|
|
126
|
-
* @param {string} resource API resource name
|
|
127
|
-
* @returns {Promise<string>} URL
|
|
128
|
-
*/
|
|
129
|
-
|
|
130
|
-
async getResourceUrl(resource) {
|
|
131
|
-
await this.getDirectory();
|
|
132
|
-
|
|
133
|
-
if (!this.directory[resource]) {
|
|
134
|
-
throw new Error(`Could not resolve URL for API resource: "${resource}"`);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return this.directory[resource];
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Get directory meta field
|
|
143
|
-
*
|
|
144
|
-
* @param {string} field Meta field name
|
|
145
|
-
* @returns {Promise<string|null>} Meta field value
|
|
146
|
-
*/
|
|
147
|
-
|
|
148
|
-
async getMetaField(field) {
|
|
149
|
-
await this.getDirectory();
|
|
150
|
-
|
|
151
|
-
if (('meta' in this.directory) && (field in this.directory.meta)) {
|
|
152
|
-
return this.directory.meta[field];
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Create signed HTTP request body
|
|
161
|
-
*
|
|
162
|
-
* @param {string} url Request URL
|
|
163
|
-
* @param {object} payload Request payload
|
|
164
|
-
* @param {string} [nonce] Request nonce
|
|
165
|
-
* @param {string} [kid] Request KID
|
|
166
|
-
* @returns {Promise<object>} Signed HTTP request body
|
|
167
|
-
*/
|
|
168
|
-
|
|
169
|
-
async createSignedBody(url, payload = null, nonce = null, kid = null) {
|
|
170
|
-
/* JWS header */
|
|
171
|
-
const header = {
|
|
172
|
-
url,
|
|
173
|
-
alg: 'RS256'
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
if (nonce) {
|
|
177
|
-
logger.info(`Using nonce: ${nonce}`);
|
|
178
|
-
header.nonce = nonce;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/* KID or JWK */
|
|
182
|
-
if (kid) {
|
|
183
|
-
header.kid = kid;
|
|
184
|
-
}
|
|
185
|
-
else {
|
|
186
|
-
header.jwk = await this.getJwk();
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/* Request payload */
|
|
190
|
-
const result = {
|
|
191
|
-
payload: payload ? util.b64encode(JSON.stringify(payload)) : '',
|
|
192
|
-
protected: util.b64encode(JSON.stringify(header))
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
/* Signature */
|
|
196
|
-
const signer = crypto.createSign('RSA-SHA256').update(`${result.protected}.${result.payload}`, 'utf8');
|
|
197
|
-
result.signature = util.b64escape(signer.sign(this.accountKey, 'base64'));
|
|
198
|
-
|
|
199
|
-
return result;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Signed HTTP request
|
|
205
|
-
*
|
|
206
|
-
* https://tools.ietf.org/html/rfc8555#section-6.2
|
|
207
|
-
*
|
|
208
|
-
* @param {string} url Request URL
|
|
209
|
-
* @param {object} payload Request payload
|
|
210
|
-
* @param {string} [kid] Request KID
|
|
211
|
-
* @param {string} [nonce] Request anti-replay nonce
|
|
212
|
-
* @param {number} [attempts] Request attempt counter
|
|
213
|
-
* @returns {Promise<object>} HTTP response
|
|
214
|
-
*/
|
|
215
|
-
|
|
216
|
-
async signedRequest(url, payload, kid = null, nonce = null, attempts = 0) {
|
|
217
|
-
if (!nonce) {
|
|
218
|
-
nonce = await this.getNonce();
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/* Sign body and send request */
|
|
222
|
-
const data = await this.createSignedBody(url, payload, nonce, kid);
|
|
223
|
-
const resp = await this.request(url, 'post', { data });
|
|
224
|
-
|
|
225
|
-
/* Retry on bad nonce - https://tools.ietf.org/html/draft-ietf-acme-acme-10#section-6.4 */
|
|
226
|
-
if (resp.data && resp.data.type && (resp.status === 400) && (resp.data.type === 'urn:ietf:params:acme:error:badNonce') && (attempts < this.maxBadNonceRetries)) {
|
|
227
|
-
const newNonce = resp.headers['replay-nonce'] || null;
|
|
228
|
-
attempts += 1;
|
|
229
|
-
|
|
230
|
-
logger.info(`Caught invalid nonce error, retrying (${attempts}/${this.maxBadNonceRetries}) signed request to: ${url}`);
|
|
231
|
-
return this.signedRequest(url, payload, kid, newNonce, attempts);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/* Return response */
|
|
235
|
-
return resp;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
/* Export client */
|
|
241
|
-
module.exports = HttpClient;
|
|
1
|
+
/**
|
|
2
|
+
* ACME HTTP client
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const crypto = require('crypto');
|
|
6
|
+
const logger = require('./util.log.js');
|
|
7
|
+
|
|
8
|
+
const debug = logger.info;
|
|
9
|
+
const axios = require('./axios');
|
|
10
|
+
const util = require('./util');
|
|
11
|
+
const forge = require('./crypto/forge');
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* ACME HTTP client
|
|
16
|
+
*
|
|
17
|
+
* @class
|
|
18
|
+
* @param {string} directoryUrl ACME directory URL
|
|
19
|
+
* @param {buffer} accountKey PEM encoded account private key
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
class HttpClient {
|
|
23
|
+
constructor(directoryUrl, accountKey) {
|
|
24
|
+
this.directoryUrl = directoryUrl;
|
|
25
|
+
this.accountKey = accountKey;
|
|
26
|
+
|
|
27
|
+
this.maxBadNonceRetries = 5;
|
|
28
|
+
this.directory = null;
|
|
29
|
+
this.jwk = null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* HTTP request
|
|
35
|
+
*
|
|
36
|
+
* @param {string} url HTTP URL
|
|
37
|
+
* @param {string} method HTTP method
|
|
38
|
+
* @param {object} [opts] Request options
|
|
39
|
+
* @returns {Promise<object>} HTTP response
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
async request(url, method, opts = {}) {
|
|
43
|
+
opts.url = url;
|
|
44
|
+
opts.method = method;
|
|
45
|
+
opts.validateStatus = null;
|
|
46
|
+
|
|
47
|
+
/* Headers */
|
|
48
|
+
if (typeof opts.headers === 'undefined') {
|
|
49
|
+
opts.headers = {};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
opts.headers['Content-Type'] = 'application/jose+json';
|
|
53
|
+
|
|
54
|
+
/* Request */
|
|
55
|
+
logger.info(`HTTP request: ${method} ${url}`);
|
|
56
|
+
const resp = await axios.request(opts);
|
|
57
|
+
|
|
58
|
+
logger.info(`RESP ${resp.status} ${method} ${url}`);
|
|
59
|
+
return resp;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Ensure provider directory exists
|
|
65
|
+
*
|
|
66
|
+
* https://tools.ietf.org/html/rfc8555#section-7.1.1
|
|
67
|
+
*
|
|
68
|
+
* @returns {Promise}
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
async getDirectory() {
|
|
72
|
+
if (!this.directory) {
|
|
73
|
+
const resp = await this.request(this.directoryUrl, 'get');
|
|
74
|
+
this.directory = resp.data;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get JSON Web Key
|
|
81
|
+
*
|
|
82
|
+
* @returns {Promise<object>} {e, kty, n}
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
async getJwk() {
|
|
86
|
+
if (this.jwk) {
|
|
87
|
+
return this.jwk;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const exponent = await forge.getPublicExponent(this.accountKey);
|
|
91
|
+
const modulus = await forge.getModulus(this.accountKey);
|
|
92
|
+
|
|
93
|
+
this.jwk = {
|
|
94
|
+
e: util.b64encode(exponent),
|
|
95
|
+
kty: 'RSA',
|
|
96
|
+
n: util.b64encode(modulus)
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return this.jwk;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get nonce from directory API endpoint
|
|
105
|
+
*
|
|
106
|
+
* https://tools.ietf.org/html/rfc8555#section-7.2
|
|
107
|
+
*
|
|
108
|
+
* @returns {Promise<string>} nonce
|
|
109
|
+
*/
|
|
110
|
+
|
|
111
|
+
async getNonce() {
|
|
112
|
+
const url = await this.getResourceUrl('newNonce');
|
|
113
|
+
const resp = await this.request(url, 'head');
|
|
114
|
+
|
|
115
|
+
if (!resp.headers['replay-nonce']) {
|
|
116
|
+
throw new Error('Failed to get nonce from ACME provider');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return resp.headers['replay-nonce'];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get URL for a directory resource
|
|
125
|
+
*
|
|
126
|
+
* @param {string} resource API resource name
|
|
127
|
+
* @returns {Promise<string>} URL
|
|
128
|
+
*/
|
|
129
|
+
|
|
130
|
+
async getResourceUrl(resource) {
|
|
131
|
+
await this.getDirectory();
|
|
132
|
+
|
|
133
|
+
if (!this.directory[resource]) {
|
|
134
|
+
throw new Error(`Could not resolve URL for API resource: "${resource}"`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return this.directory[resource];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get directory meta field
|
|
143
|
+
*
|
|
144
|
+
* @param {string} field Meta field name
|
|
145
|
+
* @returns {Promise<string|null>} Meta field value
|
|
146
|
+
*/
|
|
147
|
+
|
|
148
|
+
async getMetaField(field) {
|
|
149
|
+
await this.getDirectory();
|
|
150
|
+
|
|
151
|
+
if (('meta' in this.directory) && (field in this.directory.meta)) {
|
|
152
|
+
return this.directory.meta[field];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Create signed HTTP request body
|
|
161
|
+
*
|
|
162
|
+
* @param {string} url Request URL
|
|
163
|
+
* @param {object} payload Request payload
|
|
164
|
+
* @param {string} [nonce] Request nonce
|
|
165
|
+
* @param {string} [kid] Request KID
|
|
166
|
+
* @returns {Promise<object>} Signed HTTP request body
|
|
167
|
+
*/
|
|
168
|
+
|
|
169
|
+
async createSignedBody(url, payload = null, nonce = null, kid = null) {
|
|
170
|
+
/* JWS header */
|
|
171
|
+
const header = {
|
|
172
|
+
url,
|
|
173
|
+
alg: 'RS256'
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
if (nonce) {
|
|
177
|
+
logger.info(`Using nonce: ${nonce}`);
|
|
178
|
+
header.nonce = nonce;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* KID or JWK */
|
|
182
|
+
if (kid) {
|
|
183
|
+
header.kid = kid;
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
header.jwk = await this.getJwk();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* Request payload */
|
|
190
|
+
const result = {
|
|
191
|
+
payload: payload ? util.b64encode(JSON.stringify(payload)) : '',
|
|
192
|
+
protected: util.b64encode(JSON.stringify(header))
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
/* Signature */
|
|
196
|
+
const signer = crypto.createSign('RSA-SHA256').update(`${result.protected}.${result.payload}`, 'utf8');
|
|
197
|
+
result.signature = util.b64escape(signer.sign(this.accountKey, 'base64'));
|
|
198
|
+
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Signed HTTP request
|
|
205
|
+
*
|
|
206
|
+
* https://tools.ietf.org/html/rfc8555#section-6.2
|
|
207
|
+
*
|
|
208
|
+
* @param {string} url Request URL
|
|
209
|
+
* @param {object} payload Request payload
|
|
210
|
+
* @param {string} [kid] Request KID
|
|
211
|
+
* @param {string} [nonce] Request anti-replay nonce
|
|
212
|
+
* @param {number} [attempts] Request attempt counter
|
|
213
|
+
* @returns {Promise<object>} HTTP response
|
|
214
|
+
*/
|
|
215
|
+
|
|
216
|
+
async signedRequest(url, payload, kid = null, nonce = null, attempts = 0) {
|
|
217
|
+
if (!nonce) {
|
|
218
|
+
nonce = await this.getNonce();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/* Sign body and send request */
|
|
222
|
+
const data = await this.createSignedBody(url, payload, nonce, kid);
|
|
223
|
+
const resp = await this.request(url, 'post', { data });
|
|
224
|
+
|
|
225
|
+
/* Retry on bad nonce - https://tools.ietf.org/html/draft-ietf-acme-acme-10#section-6.4 */
|
|
226
|
+
if (resp.data && resp.data.type && (resp.status === 400) && (resp.data.type === 'urn:ietf:params:acme:error:badNonce') && (attempts < this.maxBadNonceRetries)) {
|
|
227
|
+
const newNonce = resp.headers['replay-nonce'] || null;
|
|
228
|
+
attempts += 1;
|
|
229
|
+
|
|
230
|
+
logger.info(`Caught invalid nonce error, retrying (${attempts}/${this.maxBadNonceRetries}) signed request to: ${url}`);
|
|
231
|
+
return this.signedRequest(url, payload, kid, newNonce, attempts);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/* Return response */
|
|
235
|
+
return resp;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
/* Export client */
|
|
241
|
+
module.exports = HttpClient;
|
package/src/index.js
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* acme-client
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
exports.Client = require('./client');
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Directory URLs
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
exports.directory = {
|
|
13
|
-
letsencrypt: {
|
|
14
|
-
staging: 'https://acme-staging-v02.api.letsencrypt.org/directory',
|
|
15
|
-
production: 'https://acme-v02.api.letsencrypt.org/directory'
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Crypto
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
exports.forge = require('./crypto/forge');
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Axios
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
exports.axios = require('./axios');
|
|
1
|
+
/**
|
|
2
|
+
* acme-client
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
exports.Client = require('./client');
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Directory URLs
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
exports.directory = {
|
|
13
|
+
letsencrypt: {
|
|
14
|
+
staging: 'https://acme-staging-v02.api.letsencrypt.org/directory',
|
|
15
|
+
production: 'https://acme-v02.api.letsencrypt.org/directory'
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Crypto
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
exports.forge = require('./crypto/forge');
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Axios
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
exports.axios = require('./axios');
|