@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/src/auto.js CHANGED
@@ -1,199 +1,203 @@
1
- /**
2
- * ACME auto helper
3
- */
4
-
5
- const Promise = require('bluebird');
6
- const logger = require('./util.log.js');
7
-
8
- const debug = logger.info;
9
- const forge = require('./crypto/forge');
10
-
11
- const defaultOpts = {
12
- csr: null,
13
- email: null,
14
- preferredChain: null,
15
- termsOfServiceAgreed: false,
16
- skipChallengeVerification: false,
17
- challengePriority: ['http-01', 'dns-01'],
18
- challengeCreateFn: async () => { throw new Error('Missing challengeCreateFn()'); },
19
- challengeRemoveFn: async () => { throw new Error('Missing challengeRemoveFn()'); }
20
- };
21
-
22
-
23
- function sleep(time) {
24
- return new Promise((resovle) => {
25
- setTimeout(() => {
26
- resovle();
27
- }, time);
28
- });
29
- }
30
- /**
31
- * ACME client auto mode
32
- *
33
- * @param {AcmeClient} client ACME client
34
- * @param {object} userOpts Options
35
- * @returns {Promise<buffer>} Certificate
36
- */
37
-
38
- module.exports = async function(client, userOpts) {
39
- const opts = Object.assign({}, defaultOpts, userOpts);
40
- const accountPayload = { termsOfServiceAgreed: opts.termsOfServiceAgreed };
41
-
42
- if (!Buffer.isBuffer(opts.csr)) {
43
- opts.csr = Buffer.from(opts.csr);
44
- }
45
-
46
- if (opts.email) {
47
- accountPayload.contact = [`mailto:${opts.email}`];
48
- }
49
-
50
-
51
- /**
52
- * Register account
53
- */
54
-
55
- logger.info('[auto] Checking account');
56
-
57
- try {
58
- client.getAccountUrl();
59
- logger.info('[auto] Account URL already exists, skipping account registration');
60
- }
61
- catch (e) {
62
- logger.info('[auto] Registering account');
63
- await client.createAccount(accountPayload);
64
- }
65
-
66
-
67
- /**
68
- * Parse domains from CSR
69
- */
70
-
71
- logger.info('[auto] Parsing domains from Certificate Signing Request');
72
- const csrDomains = await forge.readCsrDomains(opts.csr);
73
- const domains = [csrDomains.commonName].concat(csrDomains.altNames);
74
-
75
- logger.info(`[auto] Resolved ${domains.length} domains from parsing the Certificate Signing Request`);
76
-
77
-
78
- /**
79
- * Place order
80
- */
81
-
82
- logger.info('[auto] Placing new certificate order with ACME provider');
83
- const orderPayload = { identifiers: domains.map((d) => ({ type: 'dns', value: d })) };
84
- const order = await client.createOrder(orderPayload);
85
- const authorizations = await client.getAuthorizations(order);
86
-
87
- logger.info(`[auto] Placed certificate order successfully, received ${authorizations.length} identity authorizations`);
88
-
89
-
90
- /**
91
- * Resolve and satisfy challenges
92
- */
93
-
94
- logger.info('[auto] Resolving and satisfying authorization challenges');
95
-
96
- // 获取challenge列表
97
- const challengePromises = authorizations.map(async (authz) => {
98
- const d = authz.identifier.value;
99
-
100
- /* Select challenge based on priority */
101
- const challenge = authz.challenges.sort((a, b) => {
102
- const aidx = opts.challengePriority.indexOf(a.type);
103
- const bidx = opts.challengePriority.indexOf(b.type);
104
-
105
- if (aidx === -1) return 1;
106
- if (bidx === -1) return -1;
107
- return aidx - bidx;
108
- }).slice(0, 1)[0];
109
-
110
- if (!challenge) {
111
- throw new Error(`Unable to select challenge for ${d}, no challenge found`);
112
- }
113
-
114
- logger.info(`[auto] [${d}] Found ${authz.challenges.length} challenges, selected type: ${challenge.type}`);
115
-
116
- const keyAuthorization = await client.getChallengeKeyAuthorization(challenge);
117
-
118
- return {
119
- authz, keyAuthorization, domain: d, challenge
120
- };
121
- });
122
-
123
- // 执行获取challenge信息列表
124
- const challengeInfos = await Promise.all(challengePromises);
125
-
126
-
127
- // 创建dns records 的promise
128
- const challengeCreatePromises = challengeInfos.map(async (challengeInfo, index) => {
129
- const { domain, authz, challenge, keyAuthorization } = challengeInfo;
130
- try {
131
- await sleep(index * 2000); // 延迟2秒再请求下一个
132
- logger.info(`[auto] [${domain}] Trigger challengeCreateFn()`);
133
- challengeInfo.record = await opts.challengeCreateFn(authz, challenge, keyAuthorization);
134
- return challengeInfo;
135
- }
136
- catch (e) {
137
- logger.error('challengeCreate error', e);
138
- return e;
139
- }
140
- });
141
-
142
- // 执行创建dns record
143
- const challengeRecordInfos = await Promise.all(challengeCreatePromises);
144
-
145
- try {
146
- // 如果创建record有错误,直接报错
147
- const hasError = challengeRecordInfos.filter((item) => item instanceof Error);
148
- if (hasError.length > 0) {
149
- throw new Error(hasError[0]);
150
- }
151
- // 等待30秒,尽量等dns记录更新到远端
152
- logger.info('[auto] 等待30秒');
153
- await sleep(30000);
154
- // 开始验证
155
- const verifys = challengeRecordInfos.map(async (challengeInfo) => {
156
- const { domain, authz, challenge } = challengeInfo;
157
- const d = domain;
158
- /* Challenge verification */
159
- if (opts.skipChallengeVerification === true) {
160
- logger.info(`[auto] [${d}] Skipping challenge verification since skipChallengeVerification=true`);
161
- }
162
- else {
163
- logger.info(`[auto] [${d}] Running challenge verification`);
164
- await client.verifyChallenge(authz, challenge);
165
- }
166
-
167
- /* Complete challenge and wait for valid status */
168
- logger.info(`[auto] [${d}] Completing challenge with ACME provider and waiting for valid status`);
169
- await client.completeChallenge(challenge);
170
- await client.waitForValidStatus(challenge);
171
- });
172
- logger.info('[auto] Waiting for challenge valid status');
173
- await Promise.all(verifys);
174
- // 验证成功
175
- logger.info('[auto] challenge verify success');
176
- }
177
- finally {
178
- // 删除record
179
- const willRemovePromises = challengeRecordInfos.filter((item) => item && !(item instanceof Error)).map(async (challengeInfo, index) => {
180
- await sleep(index * 2000); // 延迟2秒再请求下一个
181
- const { authz, challenge, keyAuthorization, record, domain } = challengeInfo;
182
- /* Trigger challengeRemoveFn(), suppress errors */
183
- logger.info(`[auto] [${domain}] Trigger challengeRemoveFn()`);
184
-
185
- try {
186
- await opts.challengeRemoveFn(authz, challenge, keyAuthorization, record);
187
- }
188
- catch (e) {
189
- logger.info(`[auto] [${domain}] challengeRemoveFn threw error: ${e.message}`);
190
- }
191
- });
192
-
193
- await Promise.all(willRemovePromises);
194
- }
195
-
196
- logger.info('[auto] Finalizing order and downloading certificate');
197
- await client.finalizeOrder(order, opts.csr);
198
- return client.getCertificate(order, opts.preferredChain);
199
- };
1
+ /**
2
+ * ACME auto helper
3
+ */
4
+
5
+ const Promise = require('bluebird');
6
+ const logger = require('./util.log.js');
7
+
8
+ const debug = logger.info;
9
+ const forge = require('./crypto/forge');
10
+
11
+ const defaultOpts = {
12
+ csr: null,
13
+ email: null,
14
+ preferredChain: null,
15
+ termsOfServiceAgreed: false,
16
+ skipChallengeVerification: false,
17
+ challengePriority: ['http-01', 'dns-01'],
18
+ challengeCreateFn: async () => { throw new Error('Missing challengeCreateFn()'); },
19
+ challengeRemoveFn: async () => { throw new Error('Missing challengeRemoveFn()'); }
20
+ };
21
+
22
+
23
+ function sleep(time) {
24
+ return new Promise((resovle) => {
25
+ setTimeout(() => {
26
+ resovle();
27
+ }, time);
28
+ });
29
+ }
30
+ /**
31
+ * ACME client auto mode
32
+ *
33
+ * @param {AcmeClient} client ACME client
34
+ * @param {object} userOpts Options
35
+ * @returns {Promise<buffer>} Certificate
36
+ */
37
+
38
+ module.exports = async function(client, userOpts) {
39
+ const opts = Object.assign({}, defaultOpts, userOpts);
40
+ const accountPayload = { termsOfServiceAgreed: opts.termsOfServiceAgreed };
41
+
42
+ if (!Buffer.isBuffer(opts.csr)) {
43
+ opts.csr = Buffer.from(opts.csr);
44
+ }
45
+
46
+ if (opts.email) {
47
+ accountPayload.contact = [`mailto:${opts.email}`];
48
+ }
49
+
50
+
51
+ /**
52
+ * Register account
53
+ */
54
+
55
+ logger.info('[auto] Checking account');
56
+
57
+ try {
58
+ client.getAccountUrl();
59
+ logger.info('[auto] Account URL already exists, skipping account registration');
60
+ }
61
+ catch (e) {
62
+ logger.info('[auto] Registering account');
63
+ await client.createAccount(accountPayload);
64
+ }
65
+
66
+
67
+ /**
68
+ * Parse domains from CSR
69
+ */
70
+
71
+ logger.info('[auto] Parsing domains from Certificate Signing Request');
72
+ const csrDomains = await forge.readCsrDomains(opts.csr);
73
+ const domains = [csrDomains.commonName].concat(csrDomains.altNames);
74
+
75
+ logger.info(`[auto] Resolved ${domains.length} domains from parsing the Certificate Signing Request`);
76
+
77
+
78
+ /**
79
+ * Place order
80
+ */
81
+
82
+ logger.info('[auto] Placing new certificate order with ACME provider');
83
+ const orderPayload = { identifiers: domains.map((d) => ({ type: 'dns', value: d })) };
84
+ const order = await client.createOrder(orderPayload);
85
+ const authorizations = await client.getAuthorizations(order);
86
+
87
+ logger.info(`[auto] Placed certificate order successfully, received ${authorizations.length} identity authorizations`);
88
+
89
+
90
+ /**
91
+ * Resolve and satisfy challenges
92
+ */
93
+
94
+ logger.info('[auto] Resolving and satisfying authorization challenges');
95
+
96
+ // 获取challenge列表
97
+ const challengePromises = authorizations.map(async (authz) => {
98
+ const d = authz.identifier.value;
99
+
100
+ /* Select challenge based on priority */
101
+ const challenge = authz.challenges.sort((a, b) => {
102
+ const aidx = opts.challengePriority.indexOf(a.type);
103
+ const bidx = opts.challengePriority.indexOf(b.type);
104
+
105
+ if (aidx === -1) return 1;
106
+ if (bidx === -1) return -1;
107
+ return aidx - bidx;
108
+ }).slice(0, 1)[0];
109
+
110
+ if (!challenge) {
111
+ throw new Error(`Unable to select challenge for ${d}, no challenge found`);
112
+ }
113
+
114
+ logger.info(`[auto] [${d}] Found ${authz.challenges.length} challenges, selected type: ${challenge.type}`);
115
+
116
+ const keyAuthorization = await client.getChallengeKeyAuthorization(challenge);
117
+
118
+ return {
119
+ authz, keyAuthorization, domain: d, challenge
120
+ };
121
+ });
122
+
123
+ // 执行获取challenge信息列表
124
+ const challengeInfos = await Promise.all(challengePromises);
125
+
126
+
127
+ // 创建dns records 的promise
128
+ const challengeCreatePromises = challengeInfos.map(async (challengeInfo, index) => {
129
+ const { domain, authz, challenge, keyAuthorization } = challengeInfo;
130
+ try {
131
+ await sleep(index * 2000); // 延迟2秒再请求下一个
132
+ logger.info(`[auto] [${domain}] Trigger challengeCreateFn()`);
133
+ challengeInfo.record = await opts.challengeCreateFn(authz, challenge, keyAuthorization);
134
+ return challengeInfo;
135
+ }
136
+ catch (e) {
137
+ logger.error('challengeCreate error', e);
138
+ return e;
139
+ }
140
+ });
141
+
142
+ // 执行创建dns record
143
+ const challengeRecordInfos = await Promise.all(challengeCreatePromises);
144
+
145
+ try {
146
+ // 如果创建record有错误,直接报错
147
+ const hasError = challengeRecordInfos.filter((item) => item instanceof Error);
148
+ if (hasError.length > 0) {
149
+ throw new Error(hasError[0]);
150
+ }
151
+ // 等待30秒,尽量等dns记录更新到远端
152
+ logger.info('[auto] 等待30秒');
153
+ await sleep(30000);
154
+ // 开始验证
155
+ const verifys = challengeRecordInfos.map(async (challengeInfo) => {
156
+ const { domain, authz, challenge } = challengeInfo;
157
+ const d = domain;
158
+ /* Challenge verification */
159
+ if (opts.skipChallengeVerification === true) {
160
+ logger.info(`[auto] [${d}] Skipping challenge verification since skipChallengeVerification=true`);
161
+ }
162
+ else {
163
+ logger.info(`[auto] [${d}] Running challenge verification`);
164
+ await client.verifyChallenge(authz, challenge);
165
+ }
166
+
167
+ /* Complete challenge and wait for valid status */
168
+ logger.info(`[auto] [${d}] Completing challenge with ACME provider and waiting for valid status`);
169
+ await client.completeChallenge(challenge);
170
+ await client.waitForValidStatus(challenge);
171
+ });
172
+ logger.info('[auto] Waiting for challenge valid status');
173
+ await Promise.all(verifys);
174
+ // 验证成功
175
+ logger.info('[auto] challenge verify success');
176
+ }
177
+ catch (e) {
178
+ logger.error('申请时出错:', e);
179
+ throw e;
180
+ }
181
+ finally {
182
+ // 删除record
183
+ const willRemovePromises = challengeRecordInfos.filter((item) => item && !(item instanceof Error)).map(async (challengeInfo, index) => {
184
+ await sleep(index * 2000); // 延迟2秒再请求下一个
185
+ const { authz, challenge, keyAuthorization, record, domain } = challengeInfo;
186
+ /* Trigger challengeRemoveFn(), suppress errors */
187
+ logger.info(`[auto] [${domain}] Trigger challengeRemoveFn()`);
188
+
189
+ try {
190
+ await opts.challengeRemoveFn(authz, challenge, keyAuthorization, record);
191
+ }
192
+ catch (e) {
193
+ logger.info(`[auto] [${domain}] challengeRemoveFn threw error: ${e.message}`);
194
+ }
195
+ });
196
+
197
+ await Promise.all(willRemovePromises);
198
+ }
199
+
200
+ logger.info('[auto] Finalizing order and downloading certificate');
201
+ await client.finalizeOrder(order, opts.csr);
202
+ return client.getCertificate(order, opts.preferredChain);
203
+ };
package/src/axios.js CHANGED
@@ -1,40 +1,40 @@
1
- /**
2
- * Axios instance
3
- */
4
-
5
- const axios = require('axios');
6
- const adapter = require('axios/lib/adapters/http');
7
- const pkg = require('./../package.json');
8
-
9
-
10
- /**
11
- * Instance
12
- */
13
-
14
- const instance = axios.create();
15
-
16
- /* Default User-Agent */
17
- instance.defaults.headers.common['User-Agent'] = `node-${pkg.name}/${pkg.version}`;
18
-
19
- /* Default ACME settings */
20
- instance.defaults.acmeSettings = {
21
- httpChallengePort: 80,
22
- bypassCustomDnsResolver: false
23
- };
24
-
25
-
26
- /**
27
- * Explicitly set Node as default HTTP adapter
28
- *
29
- * https://github.com/axios/axios/issues/1180
30
- * https://stackoverflow.com/questions/42677387
31
- */
32
-
33
- instance.defaults.adapter = adapter;
34
-
35
-
36
- /**
37
- * Export instance
38
- */
39
-
40
- module.exports = instance;
1
+ /**
2
+ * Axios instance
3
+ */
4
+
5
+ const axios = require('axios');
6
+ const adapter = require('axios/lib/adapters/http');
7
+ const pkg = require('./../package.json');
8
+
9
+
10
+ /**
11
+ * Instance
12
+ */
13
+
14
+ const instance = axios.create();
15
+
16
+ /* Default User-Agent */
17
+ instance.defaults.headers.common['User-Agent'] = `node-${pkg.name}/${pkg.version}`;
18
+
19
+ /* Default ACME settings */
20
+ instance.defaults.acmeSettings = {
21
+ httpChallengePort: 80,
22
+ bypassCustomDnsResolver: false
23
+ };
24
+
25
+
26
+ /**
27
+ * Explicitly set Node as default HTTP adapter
28
+ *
29
+ * https://github.com/axios/axios/issues/1180
30
+ * https://stackoverflow.com/questions/42677387
31
+ */
32
+
33
+ instance.defaults.adapter = adapter;
34
+
35
+
36
+ /**
37
+ * Export instance
38
+ */
39
+
40
+ module.exports = instance;