Try
This commit is contained in:
parent
9a6ae9f4ad
commit
fca036f7e3
5 changed files with 167 additions and 96 deletions
11
package-lock.json
generated
11
package-lock.json
generated
|
|
@ -8,11 +8,17 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"await-lock": "^2.1.0",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"jose-node-cjs-runtime": "^3.12.2",
|
"jose-node-cjs-runtime": "^3.12.2",
|
||||||
"pem": "^1.14.4"
|
"pem": "^1.14.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/await-lock": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/await-lock/-/await-lock-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-t7Zm5YGgEEc/3eYAicF32m/TNvL+XOeYZy9CvBUeJY/szM7frLolFylhrlZNWV/ohWhcUXygrBGjYmoQdxF4CQ=="
|
||||||
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "0.21.1",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
|
||||||
|
|
@ -127,6 +133,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"await-lock": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/await-lock/-/await-lock-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-t7Zm5YGgEEc/3eYAicF32m/TNvL+XOeYZy9CvBUeJY/szM7frLolFylhrlZNWV/ohWhcUXygrBGjYmoQdxF4CQ=="
|
||||||
|
},
|
||||||
"axios": {
|
"axios": {
|
||||||
"version": "0.21.1",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"await-lock": "^2.1.0",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"jose-node-cjs-runtime": "^3.12.2",
|
"jose-node-cjs-runtime": "^3.12.2",
|
||||||
"pem": "^1.14.4"
|
"pem": "^1.14.4"
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,24 @@
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const http = require('http')
|
const http = require('http')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const { promisify } = require('util')
|
const {
|
||||||
const { fromKeyLike } = require('jose-node-cjs-runtime/jwk/from_key_like')
|
promisify
|
||||||
const { generateKeyPair } = require('jose-node-cjs-runtime/util/generate_key_pair')
|
} = require('util')
|
||||||
const { calculateThumbprint } = require('jose-node-cjs-runtime/jwk/thumbprint')
|
const {
|
||||||
const { SignJWT } = require('jose-node-cjs-runtime/jwt/sign')
|
fromKeyLike
|
||||||
const { CompactSign } = require('jose-node-cjs-runtime/jws/compact/sign')
|
} = require('jose-node-cjs-runtime/jwk/from_key_like')
|
||||||
|
const {
|
||||||
|
generateKeyPair
|
||||||
|
} = require('jose-node-cjs-runtime/util/generate_key_pair')
|
||||||
|
const {
|
||||||
|
calculateThumbprint
|
||||||
|
} = require('jose-node-cjs-runtime/jwk/thumbprint')
|
||||||
|
const {
|
||||||
|
SignJWT
|
||||||
|
} = require('jose-node-cjs-runtime/jwt/sign')
|
||||||
|
const {
|
||||||
|
CompactSign
|
||||||
|
} = require('jose-node-cjs-runtime/jws/compact/sign')
|
||||||
const pem = require('pem')
|
const pem = require('pem')
|
||||||
const common = require('./common')
|
const common = require('./common')
|
||||||
const request = require('./request')
|
const request = require('./request')
|
||||||
|
|
@ -21,13 +33,13 @@ class Client {
|
||||||
/**
|
/**
|
||||||
* @param {String} [directoryUrl]
|
* @param {String} [directoryUrl]
|
||||||
*/
|
*/
|
||||||
constructor (directoryUrl = common.DIRECTORY_URL) {
|
constructor(directoryUrl = common.DIRECTORY_URL) {
|
||||||
this.accountPrivateJwk = null
|
this.accountPrivateJwk = null
|
||||||
this.accountPrivateKey = null
|
this.accountPrivateKey = null
|
||||||
this.accountPublicJwk = null
|
this.accountPublicJwk = null
|
||||||
this.accountPublicKey = null
|
this.accountPublicKey = null
|
||||||
this.directoryUrl = directoryUrl
|
this.directoryUrl = directoryUrl
|
||||||
this.challengeCallbacks = {}
|
this.challengeCallbacks = null
|
||||||
this.hasDirectory = false
|
this.hasDirectory = false
|
||||||
this.myAccountUrl = ''
|
this.myAccountUrl = ''
|
||||||
this.newAccountUrl = ''
|
this.newAccountUrl = ''
|
||||||
|
|
@ -45,7 +57,7 @@ class Client {
|
||||||
*
|
*
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
exportAccountKeyPair (dirname, passphrase) {
|
exportAccountKeyPair(dirname, passphrase) {
|
||||||
const privateKeyFile = path.join(dirname, 'privateKey.pem')
|
const privateKeyFile = path.join(dirname, 'privateKey.pem')
|
||||||
const publicKeyFile = path.join(dirname, 'publicKey.pem')
|
const publicKeyFile = path.join(dirname, 'publicKey.pem')
|
||||||
|
|
||||||
|
|
@ -60,8 +72,11 @@ class Client {
|
||||||
*
|
*
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
async generateAccountKeyPair () {
|
async generateAccountKeyPair() {
|
||||||
const { privateKey, publicKey } = await generateKeyPair(common.ACCOUNT_KEY_ALGORITHM)
|
const {
|
||||||
|
privateKey,
|
||||||
|
publicKey
|
||||||
|
} = await generateKeyPair(common.ACCOUNT_KEY_ALGORITHM)
|
||||||
|
|
||||||
this.accountPrivateKey = privateKey
|
this.accountPrivateKey = privateKey
|
||||||
this.accountPublicKey = publicKey
|
this.accountPublicKey = publicKey
|
||||||
|
|
@ -77,19 +92,34 @@ class Client {
|
||||||
*
|
*
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
async generateCertificate (domain, email) {
|
async generateCertificate(domain, email) {
|
||||||
await this.directory()
|
await this.directory()
|
||||||
await this.newNonce()
|
await this.newNonce()
|
||||||
await this.newAccount(email)
|
await this.newAccount(email)
|
||||||
|
|
||||||
const { authzUrls, finalizeUrl } = await this.newOrder(domain)
|
const {
|
||||||
const { challenge } = await this.authz(authzUrls[0])
|
authzUrls,
|
||||||
|
finalizeUrl
|
||||||
|
} = await this.newOrder(domain)
|
||||||
|
const {
|
||||||
|
challenge
|
||||||
|
} = await this.authz(authzUrls[0])
|
||||||
|
|
||||||
|
console.log('step 1');
|
||||||
await this.completeChallenge(challenge)
|
await this.completeChallenge(challenge)
|
||||||
|
console.log('step 2');
|
||||||
await this.pollAuthz(authzUrls[0])
|
await this.pollAuthz(authzUrls[0])
|
||||||
const { certificate, privateKeyData } = await this.finalizeOrder(finalizeUrl, domain, email)
|
console.log('step 3');
|
||||||
|
const {
|
||||||
|
certificate,
|
||||||
|
privateKeyData
|
||||||
|
} = await this.finalizeOrder(finalizeUrl, domain, email)
|
||||||
|
console.log('step 4');
|
||||||
|
|
||||||
return { certificate, privateKeyData }
|
return {
|
||||||
|
certificate,
|
||||||
|
privateKeyData
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -100,7 +130,7 @@ class Client {
|
||||||
*
|
*
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
async importAccountKeyPair (dirname, passphrase) {
|
async importAccountKeyPair(dirname, passphrase) {
|
||||||
const [privateKeyData, publicKeyData] = await Promise.all([
|
const [privateKeyData, publicKeyData] = await Promise.all([
|
||||||
fs.promises.readFile(path.join(dirname, 'privateKey.pem'), 'utf8'),
|
fs.promises.readFile(path.join(dirname, 'privateKey.pem'), 'utf8'),
|
||||||
fs.promises.readFile(path.join(dirname, 'publicKey.pem'), 'utf8')
|
fs.promises.readFile(path.join(dirname, 'publicKey.pem'), 'utf8')
|
||||||
|
|
@ -112,14 +142,12 @@ class Client {
|
||||||
await this.initAccountJwks()
|
await this.initAccountJwks()
|
||||||
}
|
}
|
||||||
|
|
||||||
async authz (authzUrl) {
|
async authz(authzUrl) {
|
||||||
const data = await this.sign(
|
const data = await this.sign({
|
||||||
{
|
kid: this.myAccountUrl,
|
||||||
kid: this.myAccountUrl,
|
nonce: this.replayNonce,
|
||||||
nonce: this.replayNonce,
|
url: authzUrl
|
||||||
url: authzUrl
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const res = await request(authzUrl, {
|
const res = await request(authzUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
@ -137,8 +165,14 @@ class Client {
|
||||||
throw new Error(`authz() Status Code: ${res.statusCode} Data: ${res.data}`)
|
throw new Error(`authz() Status Code: ${res.statusCode} Data: ${res.data}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { challenges, identifier, ...rest } = res.data
|
const {
|
||||||
const challenge = challenges.find(({ type }) => type === 'http-01')
|
challenges,
|
||||||
|
identifier,
|
||||||
|
...rest
|
||||||
|
} = res.data
|
||||||
|
const challenge = challenges.find(({
|
||||||
|
type
|
||||||
|
}) => type === 'http-01')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
challenge,
|
challenge,
|
||||||
|
|
@ -147,12 +181,12 @@ class Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async completeChallenge (challenge, cb) {
|
async completeChallenge(challenge) {
|
||||||
await this.readyChallenge(challenge)
|
await this.readyChallenge(challenge)
|
||||||
await this.receiveServerRequest(challenge, cb)
|
await this.receiveServerRequest(challenge)
|
||||||
}
|
}
|
||||||
|
|
||||||
async directory () {
|
async directory() {
|
||||||
if (this.hasDirectory) return false
|
if (this.hasDirectory) return false
|
||||||
|
|
||||||
const res = await request(this.directoryUrl)
|
const res = await request(this.directoryUrl)
|
||||||
|
|
@ -169,7 +203,7 @@ class Client {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchCertificate (certificateUrl) {
|
async fetchCertificate(certificateUrl) {
|
||||||
const data = await this.sign({
|
const data = await this.sign({
|
||||||
kid: this.myAccountUrl,
|
kid: this.myAccountUrl,
|
||||||
nonce: this.replayNonce,
|
nonce: this.replayNonce,
|
||||||
|
|
@ -196,10 +230,18 @@ class Client {
|
||||||
return res.data
|
return res.data
|
||||||
}
|
}
|
||||||
|
|
||||||
async finalizeOrder (finalizeUrl, domain, email) {
|
async finalizeOrder(finalizeUrl, domain, email) {
|
||||||
const { privateKey } = await generateKeyPair(common.CERTIFICATE_KEY_ALGORITHM)
|
const {
|
||||||
|
privateKey
|
||||||
|
} = await generateKeyPair(common.CERTIFICATE_KEY_ALGORITHM)
|
||||||
const clientKey = common.exportPrivateKey(privateKey)
|
const clientKey = common.exportPrivateKey(privateKey)
|
||||||
let { csr } = await createCsr({ clientKey, commonName: domain, email })
|
let {
|
||||||
|
csr
|
||||||
|
} = await createCsr({
|
||||||
|
clientKey,
|
||||||
|
commonName: domain,
|
||||||
|
email
|
||||||
|
})
|
||||||
|
|
||||||
// "The CSR is sent in the base64url-encoded version of the DER format.
|
// "The CSR is sent in the base64url-encoded version of the DER format.
|
||||||
// (Note: Because this field uses base64url, and does not include headers,
|
// (Note: Because this field uses base64url, and does not include headers,
|
||||||
|
|
@ -213,16 +255,13 @@ class Client {
|
||||||
.replace(/\//g, '_')
|
.replace(/\//g, '_')
|
||||||
.replace(/=/g, '')
|
.replace(/=/g, '')
|
||||||
|
|
||||||
const data = await this.sign(
|
const data = await this.sign({
|
||||||
{
|
kid: this.myAccountUrl,
|
||||||
kid: this.myAccountUrl,
|
nonce: this.replayNonce,
|
||||||
nonce: this.replayNonce,
|
url: finalizeUrl
|
||||||
url: finalizeUrl
|
}, {
|
||||||
},
|
csr
|
||||||
{
|
})
|
||||||
csr
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const res = await request(finalizeUrl, {
|
const res = await request(finalizeUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
@ -242,10 +281,13 @@ class Client {
|
||||||
|
|
||||||
const certificate = await this.fetchCertificate(res.data.certificate)
|
const certificate = await this.fetchCertificate(res.data.certificate)
|
||||||
|
|
||||||
return { certificate, privateKeyData: clientKey }
|
return {
|
||||||
|
certificate,
|
||||||
|
privateKeyData: clientKey
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async initAccountJwks () {
|
async initAccountJwks() {
|
||||||
const [publicJwk, accountPrivateJwk] = await Promise.all([
|
const [publicJwk, accountPrivateJwk] = await Promise.all([
|
||||||
fromKeyLike(this.accountPublicKey),
|
fromKeyLike(this.accountPublicKey),
|
||||||
fromKeyLike(this.accountPrivateKey)
|
fromKeyLike(this.accountPrivateKey)
|
||||||
|
|
@ -256,18 +298,15 @@ class Client {
|
||||||
this.thumbprint = await calculateThumbprint(publicJwk)
|
this.thumbprint = await calculateThumbprint(publicJwk)
|
||||||
}
|
}
|
||||||
|
|
||||||
async newAccount (...emails) {
|
async newAccount(...emails) {
|
||||||
const data = await this.sign(
|
const data = await this.sign({
|
||||||
{
|
jwk: this.accountPublicJwk,
|
||||||
jwk: this.accountPublicJwk,
|
nonce: this.replayNonce,
|
||||||
nonce: this.replayNonce,
|
url: this.newAccountUrl
|
||||||
url: this.newAccountUrl
|
}, {
|
||||||
},
|
contact: emails.map(email => 'mailto:' + email),
|
||||||
{
|
termsOfServiceAgreed: true
|
||||||
contact: emails.map(email => 'mailto:' + email),
|
})
|
||||||
termsOfServiceAgreed: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const res = await request(this.newAccountUrl, {
|
const res = await request(this.newAccountUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
@ -290,10 +329,12 @@ class Client {
|
||||||
return res.statusCode === 201
|
return res.statusCode === 201
|
||||||
}
|
}
|
||||||
|
|
||||||
async newNonce () {
|
async newNonce() {
|
||||||
if (this.replayNonce) return false
|
if (this.replayNonce) return false
|
||||||
|
|
||||||
const res = await request(this.newNonceUrl, { method: 'HEAD' })
|
const res = await request(this.newNonceUrl, {
|
||||||
|
method: 'HEAD'
|
||||||
|
})
|
||||||
|
|
||||||
if (res.statusCode !== 200) {
|
if (res.statusCode !== 200) {
|
||||||
throw new Error(`newNonce() Status Code: ${res.statusCode} Data: ${res.data}`)
|
throw new Error(`newNonce() Status Code: ${res.statusCode} Data: ${res.data}`)
|
||||||
|
|
@ -304,19 +345,19 @@ class Client {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
async newOrder (...domains) {
|
async newOrder(...domains) {
|
||||||
const identifiers = domains.map(domain => ({ type: 'dns', value: domain }))
|
const identifiers = domains.map(domain => ({
|
||||||
|
type: 'dns',
|
||||||
|
value: domain
|
||||||
|
}))
|
||||||
|
|
||||||
const data = await this.sign(
|
const data = await this.sign({
|
||||||
{
|
kid: this.myAccountUrl,
|
||||||
kid: this.myAccountUrl,
|
nonce: this.replayNonce,
|
||||||
nonce: this.replayNonce,
|
url: this.newOrderUrl
|
||||||
url: this.newOrderUrl
|
}, {
|
||||||
},
|
identifiers
|
||||||
{
|
})
|
||||||
identifiers
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const res = await request(this.newOrderUrl, {
|
const res = await request(this.newOrderUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
@ -335,7 +376,10 @@ class Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
const orderUrl = res.headers.location
|
const orderUrl = res.headers.location
|
||||||
const { authorizations: authzUrls, finalize: finalizeUrl } = res.data
|
const {
|
||||||
|
authorizations: authzUrls,
|
||||||
|
finalize: finalizeUrl
|
||||||
|
} = res.data
|
||||||
|
|
||||||
return {
|
return {
|
||||||
authzUrls,
|
authzUrls,
|
||||||
|
|
@ -345,7 +389,7 @@ class Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async pollAuthz (authzUrl) {
|
async pollAuthz(authzUrl) {
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 10; i++) {
|
||||||
const result = await this.authz(authzUrl)
|
const result = await this.authz(authzUrl)
|
||||||
|
|
||||||
|
|
@ -364,15 +408,12 @@ class Client {
|
||||||
throw new Error('pollAuthz() timed out')
|
throw new Error('pollAuthz() timed out')
|
||||||
}
|
}
|
||||||
|
|
||||||
async readyChallenge (challenge) {
|
async readyChallenge(challenge) {
|
||||||
const data = await this.sign(
|
const data = await this.sign({
|
||||||
{
|
kid: this.myAccountUrl,
|
||||||
kid: this.myAccountUrl,
|
nonce: this.replayNonce,
|
||||||
nonce: this.replayNonce,
|
url: challenge.url
|
||||||
url: challenge.url
|
}, {})
|
||||||
},
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
|
|
||||||
const res = await request(challenge.url, {
|
const res = await request(challenge.url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
@ -391,21 +432,25 @@ class Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
receiveServerRequest (challenge, cb) {
|
receiveServerRequest(challenge) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const time = setTimeout(() => {
|
const time = setTimeout(() => {
|
||||||
reject(new Error('Timed out waiting for server request'))
|
reject(new Error('Timed out waiting for server request'))
|
||||||
}, 10e3);
|
}, 10e3);
|
||||||
this.challengeCallbacks[challenge.token] = function () {
|
let hasResolved = false;
|
||||||
setTimeout(cb, 100);
|
this.challengeCallbacks = function () {
|
||||||
|
if (!hasResolved)
|
||||||
|
setTimeout(resolve, 100);
|
||||||
|
else
|
||||||
|
return challenge.token + '.' + this.thumbprint;
|
||||||
|
hasResolved = true;
|
||||||
clearTimeout(time);
|
clearTimeout(time);
|
||||||
resolve();
|
|
||||||
return challenge.token + '.' + this.thumbprint;
|
return challenge.token + '.' + this.thumbprint;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setReplayNonce (res) {
|
setReplayNonce(res) {
|
||||||
const replayNonce = (res.headers['replay-nonce'] || '').trim()
|
const replayNonce = (res.headers['replay-nonce'] || '').trim()
|
||||||
|
|
||||||
if (!replayNonce) {
|
if (!replayNonce) {
|
||||||
|
|
@ -415,18 +460,24 @@ class Client {
|
||||||
this.replayNonce = replayNonce
|
this.replayNonce = replayNonce
|
||||||
}
|
}
|
||||||
|
|
||||||
async sign (header, payload) {
|
async sign(header, payload) {
|
||||||
let data
|
let data
|
||||||
|
|
||||||
if (payload) {
|
if (payload) {
|
||||||
data = await new SignJWT(payload)
|
data = await new SignJWT(payload)
|
||||||
.setProtectedHeader({ alg: common.ACCOUNT_KEY_ALGORITHM, ...header })
|
.setProtectedHeader({
|
||||||
|
alg: common.ACCOUNT_KEY_ALGORITHM,
|
||||||
|
...header
|
||||||
|
})
|
||||||
.sign(this.accountPrivateKey)
|
.sign(this.accountPrivateKey)
|
||||||
} else {
|
} else {
|
||||||
// SignJWT constructor only accepts object but RFC8555 requires empty payload
|
// SignJWT constructor only accepts object but RFC8555 requires empty payload
|
||||||
// Workaround: manually pass empty Uint8Array to CompactSign constructor
|
// Workaround: manually pass empty Uint8Array to CompactSign constructor
|
||||||
const sig = new CompactSign(new Uint8Array())
|
const sig = new CompactSign(new Uint8Array())
|
||||||
sig.setProtectedHeader({ alg: common.ACCOUNT_KEY_ALGORITHM, ...header })
|
sig.setProtectedHeader({
|
||||||
|
alg: common.ACCOUNT_KEY_ALGORITHM,
|
||||||
|
...header
|
||||||
|
})
|
||||||
data = await sig.sign(this.accountPrivateKey)
|
data = await sig.sign(this.accountPrivateKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,9 +34,11 @@ const acme_prefix = '/.well-known/acme-challenge/';
|
||||||
const listener = async function (/** @type {import('http').IncomingMessage} */ req, /** @type {import('http').ServerResponse} */ res) {
|
const listener = async function (/** @type {import('http').IncomingMessage} */ req, /** @type {import('http').ServerResponse} */ res) {
|
||||||
try {
|
try {
|
||||||
if (req.url.startsWith(acme_prefix)) {
|
if (req.url.startsWith(acme_prefix)) {
|
||||||
const token = req.url.slice(acme_prefix.length);
|
if (client.challengeCallbacks) {
|
||||||
if (client.challengeCallbacks[token]) {
|
res.writeHead(200, {
|
||||||
res.write(client.challengeCallbacks[token]());
|
'content-type': 'application/octet-stream'
|
||||||
|
});
|
||||||
|
res.write(client.challengeCallbacks());
|
||||||
} else {
|
} else {
|
||||||
res.writeHead(404)
|
res.writeHead(404)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ const {
|
||||||
ensureDir,
|
ensureDir,
|
||||||
findTxtRecord
|
findTxtRecord
|
||||||
} = require('./util');
|
} = require('./util');
|
||||||
|
const { default: AwaitLock } = require('await-lock');
|
||||||
const record_email_prefix = 'forward-domain-cert-maintainer=';
|
const record_email_prefix = 'forward-domain-cert-maintainer=';
|
||||||
const client = new certnode.Client();
|
const client = new certnode.Client();
|
||||||
const certsDir = path.join(__dirname, '.certs');
|
const certsDir = path.join(__dirname, '.certs');
|
||||||
|
|
@ -37,7 +38,7 @@ async function buildCache(host) {
|
||||||
await fs.promises.access(extP, fs.constants.R_OK | fs.constants.W_OK);
|
await fs.promises.access(extP, fs.constants.R_OK | fs.constants.W_OK);
|
||||||
const expire = parseInt((await fs.promises.readFile(extP)).toString('utf8'));
|
const expire = parseInt((await fs.promises.readFile(extP)).toString('utf8'));
|
||||||
if (Date.now() > expire)
|
if (Date.now() > expire)
|
||||||
throw null; // expired
|
throw new Error('expired'); // expired
|
||||||
const cert = await fs.promises.readFile(certP, 'utf8');
|
const cert = await fs.promises.readFile(certP, 'utf8');
|
||||||
const key = await fs.promises.readFile(keyP, 'utf8')
|
const key = await fs.promises.readFile(keyP, 'utf8')
|
||||||
return {
|
return {
|
||||||
|
|
@ -76,13 +77,18 @@ async function getKeyCert(servername) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let lock = new AwaitLock();
|
||||||
|
|
||||||
const SniListener = async (servername, ctx) => {
|
const SniListener = async (servername, ctx) => {
|
||||||
// Generate fresh account keys for Let's Encrypt
|
// Generate fresh account keys for Let's Encrypt
|
||||||
|
await lock.acquireAsync();
|
||||||
try {
|
try {
|
||||||
ctx(null, tls.createSecureContext(await getKeyCert(servername)));
|
ctx(null, tls.createSecureContext(await getKeyCert(servername)));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(JSON.stringify(error));
|
||||||
ctx(error, null);
|
ctx(error, null);
|
||||||
|
} finally {
|
||||||
|
lock.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
editor.link_modal.header
Reference in a new issue