@certd/acme-client 0.2.0 → 0.3.1

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2017-2020 Publish Lab
3
+ Copyright (c) 2017-2022 Publish Lab
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -8,21 +8,33 @@ This module is written to handle communication with a Boulder/Let's Encrypt-styl
8
8
  * Boulder divergences from ACME: [https://github.com/letsencrypt/boulder/blob/master/docs/acme-divergences.md](https://github.com/letsencrypt/boulder/blob/master/docs/acme-divergences.md)
9
9
 
10
10
 
11
+ ## Important upgrade notice
12
+
13
+ On September 15, 2022, Let's Encrypt will stop accepting Certificate Signing Requests signed using the obsolete SHA-1 hash. This change affects all `acme-client` versions lower than `3.3.2` and `4.2.4`. Please upgrade ASAP to ensure that your certificates can still be issued following this date.
14
+
15
+ A more detailed explanation can be found [at the Let's Encrypt forums](https://community.letsencrypt.org/t/rejecting-sha-1-csrs-and-validation-using-tls-1-0-1-1-urls/175144).
16
+
17
+
11
18
  ### Compatibility
12
19
 
13
- | acme-client | API | Style | Node.js |
14
- | ------------- | --------- | --------- | ------- |
15
- | v4.x | ACMEv2 | Promise | >= v10 |
16
- | v3.x | ACMEv2 | Promise | >= v8 |
17
- | v2.x | ACMEv2 | Promise | >= v4 |
18
- | v1.x | ACMEv1 | callback | >= v4 |
20
+ | acme-client | Node.js | |
21
+ | ------------- | --------- | ----------------------------------------- |
22
+ | v5.x | >= v16 | [Upgrade guide](docs/upgrade-v5.md) |
23
+ | v4.x | >= v10 | [Changelog](CHANGELOG.md#v400-2020-05-29) |
24
+ | v3.x | >= v8 | [Changelog](CHANGELOG.md#v300-2019-07-13) |
25
+ | v2.x | >= v4 | [Changelog](CHANGELOG.md#v200-2018-04-02) |
26
+ | v1.x | >= v4 | [Changelog](CHANGELOG.md#v100-2017-10-20) |
19
27
 
20
28
 
21
29
  ### Table of contents
22
30
 
23
31
  * [Installation](#installation)
24
32
  * [Usage](#usage)
33
+ * [Directory URLs](#directory-urls)
34
+ * [External account binding](#external-account-binding)
35
+ * [Specifying the account URL](#specifying-the-account-url)
25
36
  * [Cryptography](#cryptography)
37
+ * [Legacy .forge interface](#legacy-forge-interface)
26
38
  * [Auto mode](#auto-mode)
27
39
  * [Challenge priority](#challenge-priority)
28
40
  * [Internal challenge verification](#internal-challenge-verification)
@@ -56,42 +68,87 @@ const client = new acme.Client({
56
68
  ### Directory URLs
57
69
 
58
70
  ```js
71
+ acme.directory.buypass.staging;
72
+ acme.directory.buypass.production;
73
+
59
74
  acme.directory.letsencrypt.staging;
60
75
  acme.directory.letsencrypt.production;
76
+
77
+ acme.directory.zerossl.production;
61
78
  ```
62
79
 
63
80
 
64
- ## Cryptography
81
+ ### External account binding
82
+
83
+ To enable [external account binding](https://tools.ietf.org/html/rfc8555#section-7.3.4) when creating your ACME account, provide your KID and HMAC key to the client constructor.
84
+
85
+ ```js
86
+ const client = new acme.Client({
87
+ directoryUrl: 'https://acme-provider.example.com/directory-url',
88
+ accountKey: accountPrivateKey,
89
+ externalAccountBinding: {
90
+ kid: 'YOUR-EAB-KID',
91
+ hmacKey: 'YOUR-EAB-HMAC-KEY'
92
+ }
93
+ });
94
+ ```
65
95
 
66
- For key pair generation and Certificate Signing Requests, `acme-client` uses [node-forge](https://www.npmjs.com/package/node-forge), a pure JavaScript implementation of the TLS protocol.
67
96
 
68
- These utility methods are exposed through `.forge`.
97
+ ### Specifying the account URL
69
98
 
70
- __API documentation: [docs/forge.md](docs/forge.md)__
99
+ During the ACME account creation process, the server will check the supplied account key and either create a new account if the key is unused, or return the existing ACME account bound to that key.
71
100
 
101
+ In some cases, for example with some EAB providers, this account creation step may be prohibited and might require you to manually specify the account URL beforehand. This can be done through `accountUrl` in the client constructor.
72
102
 
73
- #### Example
103
+ ```js
104
+ const client = new acme.Client({
105
+ directoryUrl: acme.directory.letsencrypt.staging,
106
+ accountKey: accountPrivateKey,
107
+ accountUrl: 'https://acme-v02.api.letsencrypt.org/acme/acct/12345678'
108
+ });
109
+ ```
110
+
111
+ You can fetch the clients current account URL, either after creating an account or supplying it through the constructor, using `getAccountUrl()`:
74
112
 
75
113
  ```js
76
- const privateKey = await acme.forge.createPrivateKey();
114
+ const myAccountUrl = client.getAccountUrl();
115
+ ```
77
116
 
78
- const [certificateKey, certificateCsr] = await acme.forge.createCsr({
117
+
118
+ ## Cryptography
119
+
120
+ For key pairs `acme-client` utilizes native Node.js cryptography APIs, supporting signing and generation of both RSA and ECDSA keys. The module [jsrsasign](https://www.npmjs.com/package/jsrsasign) is used to generate and parse Certificate Signing Requests.
121
+
122
+ These utility methods are exposed through `.crypto`.
123
+
124
+ * __Documentation: [docs/crypto.md](docs/crypto.md)__
125
+
126
+ ```js
127
+ const privateRsaKey = await acme.crypto.createPrivateRsaKey();
128
+ const privateEcdsaKey = await acme.crypto.createPrivateEcdsaKey();
129
+
130
+ const [certificateKey, certificateCsr] = await acme.crypto.createCsr({
79
131
  commonName: '*.example.com',
80
132
  altNames: ['example.com']
81
133
  });
82
134
  ```
83
135
 
84
136
 
85
- ## Auto mode
137
+ ### Legacy `.forge` interface
86
138
 
87
- For convenience an `auto()` method is included in the client that takes a single config object. This method will handle the entire process of getting a certificate for one or multiple domains.
139
+ The legacy `node-forge` crypto interface is still available for backward compatibility, however this interface is now considered deprecated and will be removed in a future major version of `acme-client`.
88
140
 
89
- A full example can be found at [examples/auto.js](examples/auto.js).
141
+ You should consider migrating to the new `.crypto` API at your earliest convenience. More details can be found in the [acme-client v5 upgrade guide](docs/upgrade-v5.md).
90
142
 
91
- __Documentation: [docs/client.md#AcmeClient+auto](docs/client.md#AcmeClient+auto)__
143
+ * __Documentation: [docs/forge.md](docs/forge.md)__
92
144
 
93
145
 
94
- #### Example
146
+ ## Auto mode
147
+
148
+ For convenience an `auto()` method is included in the client that takes a single config object. This method will handle the entire process of getting a certificate for one or multiple domains.
149
+
150
+ * __Documentation: [docs/client.md#AcmeClient+auto](docs/client.md#AcmeClient+auto)__
151
+ * __Full example: [examples/auto.js](examples/auto.js)__
95
152
 
96
153
  ```js
97
154
  const autoOpts = {
@@ -142,12 +199,8 @@ await client.auto({
142
199
 
143
200
  For more fine-grained control you can interact with the ACME API using the methods documented below.
144
201
 
145
- A full example can be found at [examples/api.js](examples/api.js).
146
-
147
- __API documentation: [docs/client.md](docs/client.md)__
148
-
149
-
150
- #### Example
202
+ * __Documentation: [docs/client.md](docs/client.md)__
203
+ * __Full example: [examples/api.js](examples/api.js)__
151
204
 
152
205
  ```js
153
206
  const account = await client.createAccount({
@@ -187,7 +240,17 @@ A complete list of axios options and documentation can be found at:
187
240
 
188
241
  ## Debugging
189
242
 
190
- `acme-client` uses [debug](https://www.npmjs.com/package/debug) for debugging which can be enabled by running
243
+ To get a better grasp of what `acme-client` is doing behind the scenes, you can either pass it a logger function, or enable debugging through an environment variable.
244
+
245
+ Setting a logger function may for example be useful for passing messages on to another logging system, or just dumping them to the console.
246
+
247
+ ```js
248
+ acme.setLogger((message) => {
249
+ console.log(message);
250
+ });
251
+ ```
252
+
253
+ Debugging to the console can also be enabled through [debug](https://www.npmjs.com/package/debug) by setting an environment variable.
191
254
 
192
255
  ```bash
193
256
  DEBUG=acme-client node index.js
package/package.json CHANGED
@@ -1,50 +1,46 @@
1
1
  {
2
2
  "name": "@certd/acme-client",
3
- "description": "Simple and unopinionated ACME client,base version 4.1.2",
4
- "author": "greper",
5
- "version": "0.2.0",
3
+ "description": "Simple and unopinionated ACME client",
4
+ "author": "nmorsman",
5
+ "version": "0.3.1",
6
6
  "main": "src/index.js",
7
7
  "types": "types",
8
8
  "license": "MIT",
9
9
  "homepage": "https://github.com/publishlab/node-acme-client",
10
10
  "engines": {
11
- "node": ">= 10"
11
+ "node": ">= 16"
12
12
  },
13
13
  "files": [
14
14
  "src",
15
15
  "types"
16
16
  ],
17
17
  "dependencies": {
18
- "axios": "0.21.1",
19
- "backo2": "^1.0.0",
20
- "bluebird": "^3.5.0",
18
+ "axios": "0.27.2",
21
19
  "debug": "^4.1.1",
22
- "log4js": "^6.3.0",
23
- "node-forge": "^0.10.0"
20
+ "jsrsasign": "^10.5.26",
21
+ "node-forge": "^1.3.1"
24
22
  },
25
23
  "devDependencies": {
26
- "@types/node": "^14.0.5",
27
- "chai": "^4.1.0",
28
- "chai-as-promised": "^7.0.0",
29
- "dtslint": "^4.0.0",
30
- "eslint": "^7.1.0",
31
- "eslint-config-airbnb-base": "^14.0.0",
32
- "eslint-plugin-import": "^2.10.0",
33
- "jsdoc-to-markdown": "^6.0.1",
34
- "mocha": "^8.1.3",
35
- "nock": "^13.0.4",
36
- "typescript": "^4.0.2",
37
- "uuid": "^8.1.0",
38
- "webpack-cli": "^4.3.1"
24
+ "@types/node": "^18.6.1",
25
+ "chai": "^4.3.6",
26
+ "chai-as-promised": "^7.1.1",
27
+ "dtslint": "^4.2.1",
28
+ "eslint": "^8.11.0",
29
+ "eslint-config-airbnb-base": "^15.0.0",
30
+ "eslint-plugin-import": "^2.25.4",
31
+ "jsdoc-to-markdown": "^7.1.1",
32
+ "mocha": "^10.0.0",
33
+ "nock": "^13.2.4",
34
+ "typescript": "^4.8.4",
35
+ "uuid": "^8.3.2"
39
36
  },
40
37
  "scripts": {
41
- "build-docs": "jsdoc2md src/client.js > docs/client.md && jsdoc2md src/crypto/forge.js > docs/forge.md",
38
+ "build-docs": "jsdoc2md src/client.js > docs/client.md && jsdoc2md src/crypto/index.js > docs/crypto.md && jsdoc2md src/crypto/forge.js > docs/forge.md",
42
39
  "lint": "eslint .",
43
40
  "lint-types": "dtslint types",
44
- "1prepublishOnly": "npm run build-docs",
45
- "test": "mocha -b -t 60000 \"test/setup.js\" \"test/**/*.spec.js\"",
46
- "test-local": "/bin/bash scripts/run-tests.sh",
47
- "build": "webpack ./src/index.js ./dist/bundle.js"
41
+ "prepublishOnly": "npm run build-docs",
42
+ "test": "mocha -t 60000 \"test/setup.js\" \"test/**/*.spec.js\"",
43
+ "test-local": "/bin/bash scripts/run-tests.sh"
48
44
  },
49
45
  "repository": {
50
46
  "type": "git",
@@ -60,6 +56,5 @@
60
56
  ],
61
57
  "bugs": {
62
58
  "url": "https://github.com/publishlab/node-acme-client/issues"
63
- },
64
- "gitHead": "5fbd7742665c0a949333d805153e9b6af91c0a71"
59
+ }
65
60
  }
package/src/api.js CHANGED
@@ -42,13 +42,15 @@ class AcmeApi {
42
42
  * @param {string} url Request URL
43
43
  * @param {object} [payload] Request payload, default: `null`
44
44
  * @param {array} [validStatusCodes] Array of valid HTTP response status codes, default: `[]`
45
- * @param {boolean} [jwsKid] Use KID in JWS header, default: `true`
45
+ * @param {object} [opts]
46
+ * @param {boolean} [opts.includeJwsKid] Include KID instead of JWK in JWS header, default: `true`
47
+ * @param {boolean} [opts.includeExternalAccountBinding] Include EAB in request, default: `false`
46
48
  * @returns {Promise<object>} HTTP response
47
49
  */
48
50
 
49
- async apiRequest(url, payload = null, validStatusCodes = [], jwsKid = true) {
50
- const kid = jwsKid ? this.getAccountUrl() : null;
51
- const resp = await this.http.signedRequest(url, payload, kid);
51
+ async apiRequest(url, payload = null, validStatusCodes = [], { includeJwsKid = true, includeExternalAccountBinding = false } = {}) {
52
+ const kid = includeJwsKid ? this.getAccountUrl() : null;
53
+ const resp = await this.http.signedRequest(url, payload, { kid, includeExternalAccountBinding });
52
54
 
53
55
  if (validStatusCodes.length && (validStatusCodes.indexOf(resp.status) === -1)) {
54
56
  throw new Error(util.formatResponseError(resp));
@@ -65,13 +67,15 @@ class AcmeApi {
65
67
  * @param {string} resource Request resource name
66
68
  * @param {object} [payload] Request payload, default: `null`
67
69
  * @param {array} [validStatusCodes] Array of valid HTTP response status codes, default: `[]`
68
- * @param {boolean} [jwsKid] Use KID in JWS header, default: `true`
70
+ * @param {object} [opts]
71
+ * @param {boolean} [opts.includeJwsKid] Include KID instead of JWK in JWS header, default: `true`
72
+ * @param {boolean} [opts.includeExternalAccountBinding] Include EAB in request, default: `false`
69
73
  * @returns {Promise<object>} HTTP response
70
74
  */
71
75
 
72
- async apiResourceRequest(resource, payload = null, validStatusCodes = [], jwsKid = true) {
76
+ async apiResourceRequest(resource, payload = null, validStatusCodes = [], { includeJwsKid = true, includeExternalAccountBinding = false } = {}) {
73
77
  const resourceUrl = await this.http.getResourceUrl(resource);
74
- return this.apiRequest(resourceUrl, payload, validStatusCodes, jwsKid);
78
+ return this.apiRequest(resourceUrl, payload, validStatusCodes, { includeJwsKid, includeExternalAccountBinding });
75
79
  }
76
80
 
77
81
 
@@ -98,7 +102,10 @@ class AcmeApi {
98
102
  */
99
103
 
100
104
  async createAccount(data) {
101
- const resp = await this.apiResourceRequest('newAccount', data, [200, 201], false);
105
+ const resp = await this.apiResourceRequest('newAccount', data, [200, 201], {
106
+ includeJwsKid: false,
107
+ includeExternalAccountBinding: (data.onlyReturnExisting !== true)
108
+ });
102
109
 
103
110
  /* Set account URL */
104
111
  if (resp.headers.location) {
package/src/auto.js CHANGED
@@ -2,11 +2,8 @@
2
2
  * ACME auto helper
3
3
  */
4
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');
5
+ const { readCsrDomains } = require('./crypto');
6
+ const { log } = require('./logger');
10
7
 
11
8
  const defaultOpts = {
12
9
  csr: null,
@@ -20,13 +17,6 @@ const defaultOpts = {
20
17
  };
21
18
 
22
19
 
23
- function sleep(time) {
24
- return new Promise((resovle) => {
25
- setTimeout(() => {
26
- resovle();
27
- }, time);
28
- });
29
- }
30
20
  /**
31
21
  * ACME client auto mode
32
22
  *
@@ -52,14 +42,14 @@ module.exports = async function(client, userOpts) {
52
42
  * Register account
53
43
  */
54
44
 
55
- logger.info('[auto] Checking account');
45
+ log('[auto] Checking account');
56
46
 
57
47
  try {
58
48
  client.getAccountUrl();
59
- logger.info('[auto] Account URL already exists, skipping account registration');
49
+ log('[auto] Account URL already exists, skipping account registration');
60
50
  }
61
51
  catch (e) {
62
- logger.info('[auto] Registering account');
52
+ log('[auto] Registering account');
63
53
  await client.createAccount(accountPayload);
64
54
  }
65
55
 
@@ -68,136 +58,136 @@ module.exports = async function(client, userOpts) {
68
58
  * Parse domains from CSR
69
59
  */
70
60
 
71
- logger.info('[auto] Parsing domains from Certificate Signing Request');
72
- const csrDomains = await forge.readCsrDomains(opts.csr);
61
+ log('[auto] Parsing domains from Certificate Signing Request');
62
+ const csrDomains = readCsrDomains(opts.csr);
73
63
  const domains = [csrDomains.commonName].concat(csrDomains.altNames);
64
+ const uniqueDomains = Array.from(new Set(domains));
74
65
 
75
- logger.info(`[auto] Resolved ${domains.length} domains from parsing the Certificate Signing Request`);
66
+ log(`[auto] Resolved ${uniqueDomains.length} unique domains from parsing the Certificate Signing Request`);
76
67
 
77
68
 
78
69
  /**
79
70
  * Place order
80
71
  */
81
72
 
82
- logger.info('[auto] Placing new certificate order with ACME provider');
83
- const orderPayload = { identifiers: domains.map((d) => ({ type: 'dns', value: d })) };
73
+ log('[auto] Placing new certificate order with ACME provider');
74
+ const orderPayload = { identifiers: uniqueDomains.map((d) => ({ type: 'dns', value: d })) };
84
75
  const order = await client.createOrder(orderPayload);
85
76
  const authorizations = await client.getAuthorizations(order);
86
77
 
87
- logger.info(`[auto] Placed certificate order successfully, received ${authorizations.length} identity authorizations`);
78
+ log(`[auto] Placed certificate order successfully, received ${authorizations.length} identity authorizations`);
88
79
 
89
80
 
90
81
  /**
91
82
  * Resolve and satisfy challenges
92
83
  */
93
84
 
94
- logger.info('[auto] Resolving and satisfying authorization challenges');
85
+ log('[auto] Resolving and satisfying authorization challenges');
95
86
 
96
- // 获取challenge列表
97
- const challengePromises = authorizations.map(async (authz) => {
87
+ const challengeFunc = async (authz) => {
98
88
  const d = authz.identifier.value;
89
+ let challengeCompleted = false;
99
90
 
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`);
91
+ /* Skip authz that already has valid status */
92
+ if (authz.status === 'valid') {
93
+ log(`[auto] [${d}] Authorization already has valid status, no need to complete challenges`);
94
+ return;
112
95
  }
113
96
 
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
- });
97
+ try {
98
+ /* Select challenge based on priority */
99
+ const challenge = authz.challenges.sort((a, b) => {
100
+ const aidx = opts.challengePriority.indexOf(a.type);
101
+ const bidx = opts.challengePriority.indexOf(b.type);
102
+
103
+ if (aidx === -1) return 1;
104
+ if (bidx === -1) return -1;
105
+ return aidx - bidx;
106
+ }).slice(0, 1)[0];
107
+
108
+ if (!challenge) {
109
+ throw new Error(`Unable to select challenge for ${d}, no challenge found`);
110
+ }
122
111
 
123
- // 执行获取challenge信息列表
124
- const challengeInfos = await Promise.all(challengePromises);
112
+ log(`[auto] [${d}] Found ${authz.challenges.length} challenges, selected type: ${challenge.type}`);
125
113
 
114
+ /* Trigger challengeCreateFn() */
115
+ log(`[auto] [${d}] Trigger challengeCreateFn()`);
116
+ const keyAuthorization = await client.getChallengeKeyAuthorization(challenge);
126
117
 
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;
118
+ try {
119
+ await opts.challengeCreateFn(authz, challenge, keyAuthorization);
120
+
121
+ /* Challenge verification */
122
+ if (opts.skipChallengeVerification === true) {
123
+ log(`[auto] [${d}] Skipping challenge verification since skipChallengeVerification=true`);
124
+ }
125
+ else {
126
+ log(`[auto] [${d}] Running challenge verification`);
127
+ await client.verifyChallenge(authz, challenge);
128
+ }
129
+
130
+ /* Complete challenge and wait for valid status */
131
+ log(`[auto] [${d}] Completing challenge with ACME provider and waiting for valid status`);
132
+ await client.completeChallenge(challenge);
133
+ challengeCompleted = true;
134
+
135
+ await client.waitForValidStatus(challenge);
136
+ }
137
+ finally {
138
+ /* Trigger challengeRemoveFn(), suppress errors */
139
+ log(`[auto] [${d}] Trigger challengeRemoveFn()`);
140
+
141
+ try {
142
+ await opts.challengeRemoveFn(authz, challenge, keyAuthorization);
143
+ }
144
+ catch (e) {
145
+ log(`[auto] [${d}] challengeRemoveFn threw error: ${e.message}`);
146
+ }
147
+ }
135
148
  }
136
149
  catch (e) {
137
- logger.error('challengeCreate error', e);
138
- return e;
150
+ /* Deactivate pending authz when unable to complete challenge */
151
+ if (!challengeCompleted) {
152
+ log(`[auto] [${d}] Unable to complete challenge: ${e.message}`);
153
+
154
+ try {
155
+ log(`[auto] [${d}] Deactivating failed authorization`);
156
+ await client.deactivateAuthorization(authz);
157
+ }
158
+ catch (f) {
159
+ /* Suppress deactivateAuthorization() errors */
160
+ log(`[auto] [${d}] Authorization deactivation threw error: ${f.message}`);
161
+ }
162
+ }
163
+
164
+ throw e;
139
165
  }
140
- });
166
+ };
141
167
 
142
- // 执行创建dns record
143
- const challengeRecordInfos = await Promise.all(challengeCreatePromises);
168
+ const challengePromises = authorizations.map((authz) => async () => {
169
+ await challengeFunc(authz);
170
+ });
144
171
 
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
- }
172
+ log('[auto] Waiting for challenge valid status');
173
+ // await Promise.all(challengePromises);
166
174
 
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);
175
+ log('开始challenge');
176
+ let promise = Promise.resolve();
177
+ function runPromisesSerially(tasks) {
178
+ tasks.forEach((task) => {
179
+ promise = promise.then(task);
171
180
  });
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;
181
+ return promise;
180
182
  }
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
183
 
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
- }
184
+ await runPromisesSerially(challengePromises);
185
+ log('challenge结束');
186
+ /**
187
+ * Finalize order and download certificate
188
+ */
199
189
 
200
- logger.info('[auto] Finalizing order and downloading certificate');
201
- await client.finalizeOrder(order, opts.csr);
202
- return client.getCertificate(order, opts.preferredChain);
190
+ log('[auto] Finalizing order and downloading certificate');
191
+ const finalized = await client.finalizeOrder(order, opts.csr);
192
+ return client.getCertificate(finalized, opts.preferredChain);
203
193
  };