Skip to content

Commit 32c4495

Browse files
committed
added ability to create multiple DNS TXT records per name
1 parent fd0ac2b commit 32c4495

7 files changed

Lines changed: 79 additions & 62 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Node Lambda ACME (Lets Encrypt-compatible)
1+
# Node Lambda ACME (Let's Encrypt-compatible)
22

33
![](https://sonarcloud.io/api/project_badges/measure?project=node-acme-lambda&metric=alert_status)
44

src/acme/authorize/getChallenges.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const getDNSChallenge = (challenges) => challenges.find((challenge) => challenge
77
const validateChallenges = (domain, accountKeyPair, challengeResponse) => {
88
const dnsChallenge = getDNSChallenge(challengeResponse.challenges)
99
return Promise.all([
10-
updateDNSChallenge(domain, dnsChallenge, accountKeyPair)
10+
updateDNSChallenge(domain, [dnsChallenge], accountKeyPair)
1111
.then(() => sendDNSChallengeValidation(dnsChallenge, accountKeyPair))
1212
])
1313
}

src/acme/authorize/updateDNSChallenge.js

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -12,45 +12,51 @@ const retry = require('../../retry')(config['acme-dns-retry-delay-ms'], config['
1212
const getTokenDigest = (dnsChallenge, acctKeyPair) =>
1313
crypto.createHash('sha256').update(`${dnsChallenge.token}.${RSA.thumbprint(acctKeyPair)}`).digest()
1414

15-
const dnsPreCheck = (domain, expect) => (tryCount) => {
16-
console.log(`Attempt ${tryCount + 1} to resolve TXT record for ${domain}`)
17-
return resolveTxt(`_acme-challenge.${domain}`)
18-
.then((data) => {
15+
const arrayContainsArray = (superset, subset) =>
16+
subset.every(value => superset.indexOf(value) >= 0)
17+
18+
const flatten = input => Array.prototype.concat.apply([], input)
19+
20+
21+
const dnsPreCheck = (domain, expect) => (tryCount) => {
22+
console.log(`Attempt ${tryCount + 1} to resolve TXT record for ${domain}`)
23+
return resolveTxt(`_acme-challenge.${domain}`)
24+
.then(data => {
25+
++tryCount
26+
return {
27+
tryCount,
28+
result: arrayContainsArray(flatten(data), expect)
29+
}
30+
})
31+
.catch(e => {
32+
if (e.code === 'ENODATA' || e.code === 'ENOTFOUND') {
1933
++tryCount
20-
return {
21-
tryCount,
22-
result: (data[0][0] === expect)
23-
}
24-
})
25-
.catch((e) => {
26-
if (e.code === 'ENODATA' || e.code === 'ENOTFOUND') {
27-
++tryCount
28-
return { tryCount, result: false }
29-
} else { throw e }
30-
})
31-
}
32-
33-
const validateDNSChallenge = (domain, dnsChallengeText) => {
34-
return retry(0, dnsPreCheck(domain, dnsChallengeText))
35-
.then((data) => {
36-
if (data.result) {
37-
return data.result
38-
} else {
39-
throw new Error(`Could not pre-validate DNS TXT record. Didn't find ${dnsChallengeText} in _acme-challenge.${domain}`)
40-
}
41-
})
42-
}
43-
44-
const updateDNSChallenge = (domain, dnsChallenge, acctKeyPair) => {
34+
return { tryCount, result: false }
35+
} else { throw e }
36+
})
37+
}
38+
39+
const validateDNSChallenge = (domain, dnsChallengeTexts) => {
40+
return retry(0, dnsPreCheck(domain, dnsChallengeTexts))
41+
.then((data) => {
42+
if (data.result) {
43+
return data.result
44+
} else {
45+
throw new Error(`Could not pre-validate DNS TXT record. Didn't find ${dnsChallengeTexts} in _acme-challenge.${domain}`)
46+
}
47+
})
48+
}
49+
50+
const updateDNSChallenge = (domain, dnsChallenges, acctKeyPair) => {
4551
const domainName = (typeof domain === 'string') ? domain : domain.name
46-
const dnsChallengeText = urlB64(getTokenDigest(dnsChallenge, acctKeyPair))
52+
const dnsChallengeTexts = dnsChallenges.map(dnsChallenge => urlB64(getTokenDigest(dnsChallenge, acctKeyPair)))
4753
return getHostedZoneId(domain)
48-
.then((id) => {
49-
console.log(`Updating DNS TXT Record for ${domainName} to contain ${dnsChallengeText} in Route53 hosted zone ${id}`)
50-
return updateTXTRecord(id, domainName, dnsChallengeText)
54+
.then(id => {
55+
console.log(`Updating DNS TXT Record for ${domainName} to contain ${dnsChallengeTexts} in Route53 hosted zone ${id}`)
56+
return updateTXTRecord(id, domainName, dnsChallengeTexts)
5157
})
52-
.then((updated) => validateDNSChallenge(domainName, dnsChallengeText))
53-
.catch((e) => {
58+
.then(updated => validateDNSChallenge(domainName, dnsChallengeTexts))
59+
.catch(e => {
5460
console.error(`Couldn't write token digest to DNS record.`, e)
5561
throw e
5662
})

src/acme/generateCertificate.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const v2ACME = (urls, certInfo) =>
1919
getAccount(newAccount(urls['newAccount'], urls['newNonce']))
2020
.then(account =>
2121
getV2Order(certInfo.domains, account.key, urls['newOrder'], urls['newNonce'], account.url)
22-
.then(performAuthorizations(certInfo.domains, account.key, urls['newNonce'], account.url))
22+
.then(performAuthorizations(account.key, urls['newNonce'], account.url))
2323
.then(createV2Certificate(certInfo, account.key, urls['newNonce'], account.url))
2424
)
2525

src/acme/v2/performAuthorizations.js

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,32 @@ const sendV2DNSChallengeValidation = require('./sendV2DNSChallengeValidation')
55

66
const getDNSChallenge = (challenges) => challenges.find((challenge) => challenge.type === 'dns-01')
77

8-
const validateChallenges = (domain, accountKeyPair, challengeResponse, nonceUrl, url) => {
9-
const dnsChallenge = getDNSChallenge(challengeResponse.challenges)
10-
return Promise.all([
11-
updateDNSChallenge(challengeResponse.identifier.value, dnsChallenge, accountKeyPair)
12-
.then(() => sendV2DNSChallengeValidation(dnsChallenge, accountKeyPair, nonceUrl, url))
13-
])
14-
}
8+
const consolidateChallenges = authBodies =>
9+
authBodies.reduce((acc, curr) => {
10+
const dnsChallenge = getDNSChallenge(curr.challenges)
11+
if (acc[curr.identifier.value]) {
12+
acc[curr.identifier.value].push(dnsChallenge)
13+
} else {
14+
acc[curr.identifier.value] = [dnsChallenge]
15+
}
16+
return acc
17+
}, {})
1518

16-
module.exports = (domains, keypair, nonceUrl, url) => orderInfoUrl =>
19+
const validateChallenges = (accountKeyPair, nonceUrl, url) => challenges =>
20+
Promise.all(Object.keys(challenges).map(txtName =>
21+
updateDNSChallenge(txtName, challenges[txtName], accountKeyPair)
22+
.then(() => sendV2DNSChallengeValidation(challenges[txtName], accountKeyPair, nonceUrl, url))
23+
))
24+
25+
module.exports = (keypair, nonceUrl, url) => orderInfoUrl =>
1726
agent.get(orderInfoUrl)
1827
.then(({body}) =>
19-
Promise.all(domains.map((domain, idx) =>
20-
agent.get(body.authorizations[idx])
21-
.then(({body: authBody}) => validateChallenges(domain, keypair, authBody, nonceUrl, url))
28+
Promise.all(body.authorizations.map(authUrl =>
29+
agent.get(authUrl)
30+
.then(({body: authBody}) => authBody)
2231
))
32+
.then(consolidateChallenges)
33+
.then(validateChallenges(keypair, nonceUrl, url))
2334
.then(() => body.finalize)
2435
)
2536
.catch((err) => {
Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
const RSA = require('rsa-compat').RSA
22
const sendSignedRequestV2 = require('./sendSignedRequestV2')
33

4-
const sendDNSChallengeValidation = (dnsChallenge, acctKeyPair, nonceUrl, url) => {
5-
console.log(`Sending DNS challenge validation`)
6-
return sendSignedRequestV2({
7-
keyAuthorization: `${dnsChallenge.token}.${RSA.thumbprint(acctKeyPair)}`
8-
}, acctKeyPair, dnsChallenge.url, nonceUrl, url)
9-
.then(({body}) => body)
10-
.catch((e) => {
11-
console.error(`Couldn't send DNS challenge verification.`, e)
12-
throw e
13-
})
14-
}
4+
const sendDNSChallengeValidation = (dnsChallenges, acctKeyPair, nonceUrl, url) =>
5+
Promise.all(dnsChallenges.map(dnsChallenge =>
6+
sendSignedRequestV2({
7+
keyAuthorization: `${dnsChallenge.token}.${RSA.thumbprint(acctKeyPair)}`
8+
}, acctKeyPair, dnsChallenge.url, nonceUrl, url)
9+
.then(({body}) => body)
10+
.catch((e) => {
11+
console.error(`Couldn't send DNS challenge verification.`, e)
12+
throw e
13+
})
14+
))
1515

1616
module.exports = sendDNSChallengeValidation

src/aws/route53/updateTXTRecord.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const getRoute53 = require('../sdk/getRoute53')
22

3-
const updateTXTRecord = (hostedZoneId, domain, digest) => {
3+
const updateTXTRecord = (hostedZoneId, domain, digests) => {
44
const toSend = {
55
ChangeBatch: {
66
Changes: [
@@ -9,7 +9,7 @@ const updateTXTRecord = (hostedZoneId, domain, digest) => {
99
ResourceRecordSet: {
1010
Name: `_acme-challenge.${domain}`,
1111
Type: 'TXT',
12-
ResourceRecords: [ { Value: JSON.stringify(digest) } ],
12+
ResourceRecords: digests.map(digest => ({Value: JSON.stringify(digest)})),
1313
TTL: 1
1414
}
1515
}

0 commit comments

Comments
 (0)