@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/util.js
CHANGED
|
@@ -1,172 +1,173 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Utility methods
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const Promise = require('bluebird');
|
|
6
|
-
const Backoff = require('backo2');
|
|
7
|
-
const logger = require('./util.log.js');
|
|
8
|
-
|
|
9
|
-
const debug = logger.info;
|
|
10
|
-
const forge = require('./crypto/forge');
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Retry promise
|
|
15
|
-
*
|
|
16
|
-
* @param {function} fn Function returning promise that should be retried
|
|
17
|
-
* @param {number} attempts Maximum number of attempts
|
|
18
|
-
* @param {Backoff} backoff Backoff instance
|
|
19
|
-
* @returns {Promise}
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
async function retryPromise(fn, attempts, backoff) {
|
|
23
|
-
let aborted = false;
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
const data = await fn(() => { aborted = true; });
|
|
27
|
-
return data;
|
|
28
|
-
}
|
|
29
|
-
catch (e) {
|
|
30
|
-
if (aborted || ((backoff.attempts + 1) >= attempts)) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* @param {
|
|
48
|
-
* @param {
|
|
49
|
-
* @param {number} [backoffOpts.
|
|
50
|
-
* @param {number} [backoffOpts.
|
|
51
|
-
* @
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* @
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
.replace(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* @
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* @param {string}
|
|
92
|
-
* @
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* @param {
|
|
113
|
-
* @
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
const
|
|
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
|
-
|
|
172
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Utility methods
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const Promise = require('bluebird');
|
|
6
|
+
const Backoff = require('backo2');
|
|
7
|
+
const logger = require('./util.log.js');
|
|
8
|
+
|
|
9
|
+
const debug = logger.info;
|
|
10
|
+
const forge = require('./crypto/forge');
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Retry promise
|
|
15
|
+
*
|
|
16
|
+
* @param {function} fn Function returning promise that should be retried
|
|
17
|
+
* @param {number} attempts Maximum number of attempts
|
|
18
|
+
* @param {Backoff} backoff Backoff instance
|
|
19
|
+
* @returns {Promise}
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
async function retryPromise(fn, attempts, backoff) {
|
|
23
|
+
let aborted = false;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const data = await fn(() => { aborted = true; });
|
|
27
|
+
return data;
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
if (aborted || ((backoff.attempts + 1) >= attempts)) {
|
|
31
|
+
logger.error(e);
|
|
32
|
+
throw e;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const duration = backoff.duration();
|
|
36
|
+
logger.info(`Promise rejected attempt #${backoff.attempts}, retrying in ${duration}ms: ${e.message}`);
|
|
37
|
+
|
|
38
|
+
await Promise.delay(duration);
|
|
39
|
+
return retryPromise(fn, attempts, backoff);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Retry promise
|
|
46
|
+
*
|
|
47
|
+
* @param {function} fn Function returning promise that should be retried
|
|
48
|
+
* @param {object} [backoffOpts] Backoff options
|
|
49
|
+
* @param {number} [backoffOpts.attempts] Maximum number of attempts, default: `5`
|
|
50
|
+
* @param {number} [backoffOpts.min] Minimum attempt delay in milliseconds, default: `5000`
|
|
51
|
+
* @param {number} [backoffOpts.max] Maximum attempt delay in milliseconds, default: `30000`
|
|
52
|
+
* @returns {Promise}
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
function retry(fn, { attempts = 5, min = 5000, max = 30000 } = {}) {
|
|
56
|
+
const backoff = new Backoff({ min, max });
|
|
57
|
+
return retryPromise(fn, attempts, backoff);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Escape base64 encoded string
|
|
63
|
+
*
|
|
64
|
+
* @param {string} str Base64 encoded string
|
|
65
|
+
* @returns {string} Escaped string
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
function b64escape(str) {
|
|
69
|
+
return str.replace(/\+/g, '-')
|
|
70
|
+
.replace(/\//g, '_')
|
|
71
|
+
.replace(/=/g, '');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Base64 encode and escape buffer or string
|
|
77
|
+
*
|
|
78
|
+
* @param {buffer|string} str Buffer or string to be encoded
|
|
79
|
+
* @returns {string} Escaped base64 encoded string
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
function b64encode(str) {
|
|
83
|
+
const buf = Buffer.isBuffer(str) ? str : Buffer.from(str);
|
|
84
|
+
return b64escape(buf.toString('base64'));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Parse URLs from link header
|
|
90
|
+
*
|
|
91
|
+
* @param {string} header Link header contents
|
|
92
|
+
* @param {string} rel Link relation, default: `alternate`
|
|
93
|
+
* @returns {array} Array of URLs
|
|
94
|
+
*/
|
|
95
|
+
|
|
96
|
+
function parseLinkHeader(header, rel = 'alternate') {
|
|
97
|
+
const relRe = new RegExp(`\\s*rel\\s*=\\s*"?${rel}"?`, 'i');
|
|
98
|
+
|
|
99
|
+
const results = (header || '').split(/,\s*</).map((link) => {
|
|
100
|
+
const [, linkUrl, linkParts] = link.match(/<?([^>]*)>;(.*)/) || [];
|
|
101
|
+
return (linkUrl && linkParts && linkParts.match(relRe)) ? linkUrl : null;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return results.filter((r) => r);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Find certificate chain with preferred issuer
|
|
110
|
+
* If issuer can not be located, the first certificate will be returned
|
|
111
|
+
*
|
|
112
|
+
* @param {array} certificates Array of PEM encoded certificate chains
|
|
113
|
+
* @param {string} issuer Preferred certificate issuer
|
|
114
|
+
* @returns {Promise<string>} PEM encoded certificate chain
|
|
115
|
+
*/
|
|
116
|
+
|
|
117
|
+
async function findCertificateChainForIssuer(chains, issuer) {
|
|
118
|
+
try {
|
|
119
|
+
return await Promise.any(chains.map(async (chain) => {
|
|
120
|
+
/* Look up all issuers */
|
|
121
|
+
const certs = forge.splitPemChain(chain);
|
|
122
|
+
const infoCollection = await Promise.map(certs, forge.readCertificateInfo);
|
|
123
|
+
const issuerCollection = infoCollection.map((i) => i.issuer.commonName);
|
|
124
|
+
|
|
125
|
+
/* Found match, return it */
|
|
126
|
+
if (issuerCollection.includes(issuer)) {
|
|
127
|
+
logger.info(`Found matching certificate for preferred issuer="${issuer}", issuers=${JSON.stringify(issuerCollection)}`);
|
|
128
|
+
return chain;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* No match, throw error */
|
|
132
|
+
logger.info(`Unable to match certificate for preferred issuer="${issuer}", issuers=${JSON.stringify(issuerCollection)}`);
|
|
133
|
+
throw new Error('Certificate issuer mismatch');
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
catch (e) {
|
|
137
|
+
/* No certificates matched, return default */
|
|
138
|
+
logger.info(`Found no match in ${chains.length} certificate chains for preferred issuer="${issuer}", returning default certificate chain`);
|
|
139
|
+
return chains[0];
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Find and format error in response object
|
|
146
|
+
*
|
|
147
|
+
* @param {object} resp HTTP response
|
|
148
|
+
* @returns {string} Error message
|
|
149
|
+
*/
|
|
150
|
+
|
|
151
|
+
function formatResponseError(resp) {
|
|
152
|
+
let result;
|
|
153
|
+
|
|
154
|
+
if (resp.data.error) {
|
|
155
|
+
result = resp.data.error.detail || resp.data.error;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
result = resp.data.detail || JSON.stringify(resp.data);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return result.replace(/\n/g, '');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
/* Export utils */
|
|
166
|
+
module.exports = {
|
|
167
|
+
retry,
|
|
168
|
+
b64escape,
|
|
169
|
+
b64encode,
|
|
170
|
+
parseLinkHeader,
|
|
171
|
+
findCertificateChainForIssuer,
|
|
172
|
+
formatResponseError
|
|
173
|
+
};
|
package/src/util.log.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const log4js = require('log4js');
|
|
2
|
-
|
|
3
|
-
log4js.configure({
|
|
4
|
-
appenders: { std: { type: 'stdout' } },
|
|
5
|
-
categories: { default: { appenders: ['std'], level: 'info' } }
|
|
6
|
-
});
|
|
7
|
-
const logger = log4js.getLogger('certd');
|
|
8
|
-
module.exports = logger;
|
|
1
|
+
const log4js = require('log4js');
|
|
2
|
+
|
|
3
|
+
log4js.configure({
|
|
4
|
+
appenders: { std: { type: 'stdout' } },
|
|
5
|
+
categories: { default: { appenders: ['std'], level: 'info' } }
|
|
6
|
+
});
|
|
7
|
+
const logger = log4js.getLogger('certd');
|
|
8
|
+
module.exports = logger;
|
package/src/verify.js
CHANGED
|
@@ -1,96 +1,96 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ACME challenge verification
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const Promise = require('bluebird');
|
|
6
|
-
const dns = Promise.promisifyAll(require('dns'));
|
|
7
|
-
const logger = require('./util.log.js');
|
|
8
|
-
|
|
9
|
-
const debug = logger.info;
|
|
10
|
-
const axios = require('./axios');
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Verify ACME HTTP challenge
|
|
15
|
-
*
|
|
16
|
-
* https://tools.ietf.org/html/rfc8555#section-8.3
|
|
17
|
-
*
|
|
18
|
-
* @param {object} authz Identifier authorization
|
|
19
|
-
* @param {object} challenge Authorization challenge
|
|
20
|
-
* @param {string} keyAuthorization Challenge key authorization
|
|
21
|
-
* @param {string} [suffix] URL suffix
|
|
22
|
-
* @returns {Promise<boolean>}
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
async function verifyHttpChallenge(authz, challenge, keyAuthorization, suffix = `/.well-known/acme-challenge/${challenge.token}`) {
|
|
26
|
-
const httpPort = axios.defaults.acmeSettings.httpChallengePort || 80;
|
|
27
|
-
const challengeUrl = `http://${authz.identifier.value}:${httpPort}${suffix}`;
|
|
28
|
-
|
|
29
|
-
logger.info(`Sending HTTP query to ${authz.identifier.value}, suffix: ${suffix}, port: ${httpPort}`);
|
|
30
|
-
const resp = await axios.get(challengeUrl);
|
|
31
|
-
const data = (resp.data || '').replace(/\s+$/, '');
|
|
32
|
-
|
|
33
|
-
logger.info(`Query successful, HTTP status code: ${resp.status}`);
|
|
34
|
-
|
|
35
|
-
if (!data || (data !== keyAuthorization)) {
|
|
36
|
-
throw new Error(`Authorization not found in HTTP response from ${authz.identifier.value}`);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
logger.info(`Key authorization match for ${challenge.type}/${authz.identifier.value}, ACME challenge verified`);
|
|
40
|
-
return true;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Verify ACME DNS challenge
|
|
46
|
-
*
|
|
47
|
-
* https://tools.ietf.org/html/rfc8555#section-8.4
|
|
48
|
-
*
|
|
49
|
-
* @param {object} authz Identifier authorization
|
|
50
|
-
* @param {object} challenge Authorization challenge
|
|
51
|
-
* @param {string} keyAuthorization Challenge key authorization
|
|
52
|
-
* @param {string} [prefix] DNS prefix
|
|
53
|
-
* @returns {Promise<boolean>}
|
|
54
|
-
*/
|
|
55
|
-
|
|
56
|
-
async function verifyDnsChallenge(authz, challenge, keyAuthorization, prefix = '_acme-challenge.') {
|
|
57
|
-
logger.info(`Resolving DNS TXT records for ${authz.identifier.value}, prefix: ${prefix}`);
|
|
58
|
-
let challengeRecord = `${prefix}${authz.identifier.value}`;
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
/* Attempt CNAME record first */
|
|
62
|
-
logger.info(`Checking CNAME for record ${challengeRecord}`);
|
|
63
|
-
const cnameRecords = await dns.resolveCnameAsync(challengeRecord);
|
|
64
|
-
|
|
65
|
-
if (cnameRecords.length) {
|
|
66
|
-
logger.info(`CNAME found at ${challengeRecord}, new challenge record: ${cnameRecords[0]}`);
|
|
67
|
-
challengeRecord = cnameRecords[0];
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
catch (e) {
|
|
71
|
-
logger.info(`No CNAME found for record ${challengeRecord}`);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/* Read TXT record */
|
|
75
|
-
const result = await dns.resolveTxtAsync(challengeRecord);
|
|
76
|
-
const records = [].concat(...result);
|
|
77
|
-
|
|
78
|
-
logger.info(`Query successful, found ${records.length} DNS TXT records`);
|
|
79
|
-
|
|
80
|
-
if (records.indexOf(keyAuthorization) === -1) {
|
|
81
|
-
throw new Error(`Authorization not found in DNS TXT records for ${authz.identifier.value}`);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
logger.info(`Key authorization match for ${challenge.type}/${authz.identifier.value}, ACME challenge verified`);
|
|
85
|
-
return true;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Export API
|
|
91
|
-
*/
|
|
92
|
-
|
|
93
|
-
module.exports = {
|
|
94
|
-
'http-01': verifyHttpChallenge,
|
|
95
|
-
'dns-01': verifyDnsChallenge
|
|
96
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* ACME challenge verification
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const Promise = require('bluebird');
|
|
6
|
+
const dns = Promise.promisifyAll(require('dns'));
|
|
7
|
+
const logger = require('./util.log.js');
|
|
8
|
+
|
|
9
|
+
const debug = logger.info;
|
|
10
|
+
const axios = require('./axios');
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Verify ACME HTTP challenge
|
|
15
|
+
*
|
|
16
|
+
* https://tools.ietf.org/html/rfc8555#section-8.3
|
|
17
|
+
*
|
|
18
|
+
* @param {object} authz Identifier authorization
|
|
19
|
+
* @param {object} challenge Authorization challenge
|
|
20
|
+
* @param {string} keyAuthorization Challenge key authorization
|
|
21
|
+
* @param {string} [suffix] URL suffix
|
|
22
|
+
* @returns {Promise<boolean>}
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
async function verifyHttpChallenge(authz, challenge, keyAuthorization, suffix = `/.well-known/acme-challenge/${challenge.token}`) {
|
|
26
|
+
const httpPort = axios.defaults.acmeSettings.httpChallengePort || 80;
|
|
27
|
+
const challengeUrl = `http://${authz.identifier.value}:${httpPort}${suffix}`;
|
|
28
|
+
|
|
29
|
+
logger.info(`Sending HTTP query to ${authz.identifier.value}, suffix: ${suffix}, port: ${httpPort}`);
|
|
30
|
+
const resp = await axios.get(challengeUrl);
|
|
31
|
+
const data = (resp.data || '').replace(/\s+$/, '');
|
|
32
|
+
|
|
33
|
+
logger.info(`Query successful, HTTP status code: ${resp.status}`);
|
|
34
|
+
|
|
35
|
+
if (!data || (data !== keyAuthorization)) {
|
|
36
|
+
throw new Error(`Authorization not found in HTTP response from ${authz.identifier.value}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
logger.info(`Key authorization match for ${challenge.type}/${authz.identifier.value}, ACME challenge verified`);
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Verify ACME DNS challenge
|
|
46
|
+
*
|
|
47
|
+
* https://tools.ietf.org/html/rfc8555#section-8.4
|
|
48
|
+
*
|
|
49
|
+
* @param {object} authz Identifier authorization
|
|
50
|
+
* @param {object} challenge Authorization challenge
|
|
51
|
+
* @param {string} keyAuthorization Challenge key authorization
|
|
52
|
+
* @param {string} [prefix] DNS prefix
|
|
53
|
+
* @returns {Promise<boolean>}
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
async function verifyDnsChallenge(authz, challenge, keyAuthorization, prefix = '_acme-challenge.') {
|
|
57
|
+
logger.info(`Resolving DNS TXT records for ${authz.identifier.value}, prefix: ${prefix}`);
|
|
58
|
+
let challengeRecord = `${prefix}${authz.identifier.value}`;
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
/* Attempt CNAME record first */
|
|
62
|
+
logger.info(`Checking CNAME for record ${challengeRecord}`);
|
|
63
|
+
const cnameRecords = await dns.resolveCnameAsync(challengeRecord);
|
|
64
|
+
|
|
65
|
+
if (cnameRecords.length) {
|
|
66
|
+
logger.info(`CNAME found at ${challengeRecord}, new challenge record: ${cnameRecords[0]}`);
|
|
67
|
+
challengeRecord = cnameRecords[0];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
logger.info(`No CNAME found for record ${challengeRecord}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Read TXT record */
|
|
75
|
+
const result = await dns.resolveTxtAsync(challengeRecord);
|
|
76
|
+
const records = [].concat(...result);
|
|
77
|
+
|
|
78
|
+
logger.info(`Query successful, found ${records.length} DNS TXT records`);
|
|
79
|
+
|
|
80
|
+
if (records.indexOf(keyAuthorization) === -1) {
|
|
81
|
+
throw new Error(`Authorization not found in DNS TXT records for ${authz.identifier.value}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
logger.info(`Key authorization match for ${challenge.type}/${authz.identifier.value}, ACME challenge verified`);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Export API
|
|
91
|
+
*/
|
|
92
|
+
|
|
93
|
+
module.exports = {
|
|
94
|
+
'http-01': verifyHttpChallenge,
|
|
95
|
+
'dns-01': verifyDnsChallenge
|
|
96
|
+
};
|