mirror of https://github.com/certd/certd
🔱: [acme] sync upgrade with 10 commits [trident-sync]
Bump v5.2.0 - package.json Bump v5.2.0 yarn -> npm CHANGELOG and tests for #76 Fix tests Update auto.js: wait for all challenge promises before exit Fixes #75 CHANGELOG and tests for #66 Fix lint errors Allow self-signed or invalid certificate when evaluating verifyHttpChallengepull/29/head
parent
18865f0931
commit
08c1f338d5
|
@ -4,9 +4,14 @@ version: 2.1
|
||||||
commands:
|
commands:
|
||||||
pre:
|
pre:
|
||||||
steps:
|
steps:
|
||||||
|
- run:
|
||||||
|
name: Setup environment
|
||||||
|
command: |
|
||||||
|
echo 'export FORCE_COLOR=1' >> $BASH_ENV
|
||||||
|
echo 'export NPM_CONFIG_COLOR="always"' >> $BASH_ENV
|
||||||
|
|
||||||
- run: node --version
|
- run: node --version
|
||||||
- run: npm --version
|
- run: npm --version
|
||||||
- run: yarn --version
|
|
||||||
- checkout
|
- checkout
|
||||||
|
|
||||||
enable-eab:
|
enable-eab:
|
||||||
|
@ -105,13 +110,13 @@ commands:
|
||||||
|
|
||||||
test:
|
test:
|
||||||
steps:
|
steps:
|
||||||
- run: yarn --color
|
- run: npm i
|
||||||
- run: yarn run lint --color
|
- run: npm run lint
|
||||||
- run: yarn run lint-types
|
- run: npm run lint-types
|
||||||
- run: yarn run build-docs
|
- run: npm run build-docs
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
command: yarn run test --color
|
command: npm run test
|
||||||
environment:
|
environment:
|
||||||
ACME_DOMAIN_NAME: test.example.com
|
ACME_DOMAIN_NAME: test.example.com
|
||||||
ACME_CHALLTESTSRV_URL: http://127.0.0.1:8055
|
ACME_CHALLTESTSRV_URL: http://127.0.0.1:8055
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
.vscode/
|
.vscode/
|
||||||
node_modules/
|
node_modules/
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
yarn-error.log
|
|
||||||
yarn.lock
|
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
ignore-engines true
|
|
||||||
ignore-optional true
|
|
|
@ -1,5 +1,10 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v5.2.0 (2024-01-22)
|
||||||
|
|
||||||
|
* `fixed` Allow self-signed or invalid certs when validating `http-01` challenges that redirect to HTTPS - [#65](https://github.com/publishlab/node-acme-client/issues/65)
|
||||||
|
* `fixed` Wait for all challenge promises to settle before rejecting `client.auto()` - [#75](https://github.com/publishlab/node-acme-client/issues/75)
|
||||||
|
|
||||||
## v5.1.0 (2024-01-20)
|
## v5.1.0 (2024-01-20)
|
||||||
|
|
||||||
* `fixed` Upgrade `jsrsasign@11.0.0` - [GHSA-rh63-9qcf-83gf](https://github.com/kjur/jsrsasign/security/advisories/GHSA-rh63-9qcf-83gf)
|
* `fixed` Upgrade `jsrsasign@11.0.0` - [GHSA-rh63-9qcf-83gf](https://github.com/kjur/jsrsasign/security/advisories/GHSA-rh63-9qcf-83gf)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "acme-client",
|
"name": "acme-client",
|
||||||
"description": "Simple and unopinionated ACME client",
|
"description": "Simple and unopinionated ACME client",
|
||||||
"author": "nmorsman",
|
"author": "nmorsman",
|
||||||
"version": "5.1.0",
|
"version": "5.2.0",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"types": "types/index.d.ts",
|
"types": "types/index.d.ts",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
|
@ -165,8 +165,19 @@ module.exports = async function(client, userOpts) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
log('[auto] Waiting for challenge valid status');
|
|
||||||
await Promise.all(challengePromises);
|
/**
|
||||||
|
* Wait for all challenge promises to settle
|
||||||
|
*/
|
||||||
|
|
||||||
|
try {
|
||||||
|
log('[auto] Waiting for challenge valid status');
|
||||||
|
await Promise.all(challengePromises);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
await Promise.allSettled(challengePromises);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const dns = require('dns').promises;
|
const dns = require('dns').promises;
|
||||||
|
const https = require('https');
|
||||||
const { log } = require('./logger');
|
const { log } = require('./logger');
|
||||||
const axios = require('./axios');
|
const axios = require('./axios');
|
||||||
const util = require('./util');
|
const util = require('./util');
|
||||||
|
@ -24,8 +25,11 @@ async function verifyHttpChallenge(authz, challenge, keyAuthorization, suffix =
|
||||||
const httpPort = axios.defaults.acmeSettings.httpChallengePort || 80;
|
const httpPort = axios.defaults.acmeSettings.httpChallengePort || 80;
|
||||||
const challengeUrl = `http://${authz.identifier.value}:${httpPort}${suffix}`;
|
const challengeUrl = `http://${authz.identifier.value}:${httpPort}${suffix}`;
|
||||||
|
|
||||||
|
/* May redirect to HTTPS with invalid/self-signed cert - https://letsencrypt.org/docs/challenge-types/#http-01-challenge */
|
||||||
|
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
||||||
|
|
||||||
log(`Sending HTTP query to ${authz.identifier.value}, suffix: ${suffix}, port: ${httpPort}`);
|
log(`Sending HTTP query to ${authz.identifier.value}, suffix: ${suffix}, port: ${httpPort}`);
|
||||||
const resp = await axios.get(challengeUrl);
|
const resp = await axios.get(challengeUrl, { httpsAgent });
|
||||||
const data = (resp.data || '').replace(/\s+$/, '');
|
const data = (resp.data || '').replace(/\s+$/, '');
|
||||||
|
|
||||||
log(`Query successful, HTTP status code: ${resp.status}`);
|
log(`Query successful, HTTP status code: ${resp.status}`);
|
||||||
|
|
|
@ -129,7 +129,7 @@ describe('pebble', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add challenge response', async () => {
|
it('should add challenge response', async () => {
|
||||||
const resp = await cts.addHttps01ChallengeResponse(testHttps01ChallengeToken, testHttps01ChallengeContent, testHttps01ChallengeHost, httpsPort);
|
const resp = await cts.addHttps01ChallengeResponse(testHttps01ChallengeToken, testHttps01ChallengeContent, testHttps01ChallengeHost);
|
||||||
assert.isTrue(resp);
|
assert.isTrue(resp);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,10 @@ describe('verify', () => {
|
||||||
const testHttp01Challenge = { type: 'http-01', status: 'pending', token: uuid() };
|
const testHttp01Challenge = { type: 'http-01', status: 'pending', token: uuid() };
|
||||||
const testHttp01Key = uuid();
|
const testHttp01Key = uuid();
|
||||||
|
|
||||||
|
const testHttps01Authz = { identifier: { type: 'dns', value: `${uuid()}.${domainName}` } };
|
||||||
|
const testHttps01Challenge = { type: 'http-01', status: 'pending', token: uuid() };
|
||||||
|
const testHttps01Key = uuid();
|
||||||
|
|
||||||
const testDns01Authz = { identifier: { type: 'dns', value: `${uuid()}.${domainName}` } };
|
const testDns01Authz = { identifier: { type: 'dns', value: `${uuid()}.${domainName}` } };
|
||||||
const testDns01Challenge = { type: 'dns-01', status: 'pending', token: uuid() };
|
const testDns01Challenge = { type: 'dns-01', status: 'pending', token: uuid() };
|
||||||
const testDns01Key = uuid();
|
const testDns01Key = uuid();
|
||||||
|
@ -74,6 +78,27 @@ describe('verify', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https-01
|
||||||
|
*/
|
||||||
|
|
||||||
|
describe('https-01', () => {
|
||||||
|
it('should reject challenge', async () => {
|
||||||
|
await assert.isRejected(verify['http-01'](testHttps01Authz, testHttps01Challenge, testHttps01Key));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should mock challenge response', async () => {
|
||||||
|
const resp = await cts.addHttps01ChallengeResponse(testHttps01Challenge.token, testHttps01Key, testHttps01Authz.identifier.value);
|
||||||
|
assert.isTrue(resp);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should verify challenge', async () => {
|
||||||
|
const resp = await verify['http-01'](testHttps01Authz, testHttps01Challenge, testHttps01Key);
|
||||||
|
assert.isTrue(resp);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* dns-01
|
* dns-01
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -32,6 +32,7 @@ if (capEabEnabled && process.env.ACME_EAB_KID && process.env.ACME_EAB_HMAC_KEY)
|
||||||
describe('client.auto', () => {
|
describe('client.auto', () => {
|
||||||
const testDomain = `${uuid()}.${domainName}`;
|
const testDomain = `${uuid()}.${domainName}`;
|
||||||
const testHttpDomain = `${uuid()}.${domainName}`;
|
const testHttpDomain = `${uuid()}.${domainName}`;
|
||||||
|
const testHttpsDomain = `${uuid()}.${domainName}`;
|
||||||
const testDnsDomain = `${uuid()}.${domainName}`;
|
const testDnsDomain = `${uuid()}.${domainName}`;
|
||||||
const testWildcardDomain = `${uuid()}.${domainName}`;
|
const testWildcardDomain = `${uuid()}.${domainName}`;
|
||||||
|
|
||||||
|
@ -178,6 +179,38 @@ describe('client.auto', () => {
|
||||||
assert.isString(cert);
|
assert.isString(cert);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should settle all challenges before rejecting', async () => {
|
||||||
|
const results = [];
|
||||||
|
const [, csr] = await acme.crypto.createCsr({
|
||||||
|
commonName: `${uuid()}.${domainName}`,
|
||||||
|
altNames: [
|
||||||
|
`${uuid()}.${domainName}`,
|
||||||
|
`${uuid()}.${domainName}`,
|
||||||
|
`${uuid()}.${domainName}`,
|
||||||
|
`${uuid()}.${domainName}`
|
||||||
|
]
|
||||||
|
}, await createKeyFn());
|
||||||
|
|
||||||
|
await assert.isRejected(testClient.auto({
|
||||||
|
csr,
|
||||||
|
termsOfServiceAgreed: true,
|
||||||
|
challengeCreateFn: async (...args) => {
|
||||||
|
if ([0, 1, 2].includes(results.length)) {
|
||||||
|
results.push(false);
|
||||||
|
throw new Error('oops');
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise((resolve) => { setTimeout(resolve, 500); });
|
||||||
|
results.push(true);
|
||||||
|
return cts.challengeCreateFn(...args);
|
||||||
|
},
|
||||||
|
challengeRemoveFn: cts.challengeRemoveFn
|
||||||
|
}));
|
||||||
|
|
||||||
|
assert.strictEqual(results.length, 5);
|
||||||
|
assert.deepStrictEqual(results, [false, false, false, true, true]);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Order certificates
|
* Order certificates
|
||||||
|
@ -215,6 +248,22 @@ describe('client.auto', () => {
|
||||||
assert.isString(cert);
|
assert.isString(cert);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should order certificate using https-01', async () => {
|
||||||
|
const [, csr] = await acme.crypto.createCsr({
|
||||||
|
commonName: testHttpsDomain
|
||||||
|
}, await createKeyFn());
|
||||||
|
|
||||||
|
const cert = await testClient.auto({
|
||||||
|
csr,
|
||||||
|
termsOfServiceAgreed: true,
|
||||||
|
challengeCreateFn: cts.assertHttpsChallengeCreateFn,
|
||||||
|
challengeRemoveFn: cts.challengeRemoveFn,
|
||||||
|
challengePriority: ['http-01']
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.isString(cert);
|
||||||
|
});
|
||||||
|
|
||||||
it('should order certificate using dns-01', async () => {
|
it('should order certificate using dns-01', async () => {
|
||||||
const [, csr] = await acme.crypto.createCsr({
|
const [, csr] = await acme.crypto.createCsr({
|
||||||
commonName: testDnsDomain
|
commonName: testDnsDomain
|
||||||
|
|
|
@ -6,6 +6,7 @@ const { assert } = require('chai');
|
||||||
const axios = require('./../src/axios');
|
const axios = require('./../src/axios');
|
||||||
|
|
||||||
const apiBaseUrl = process.env.ACME_CHALLTESTSRV_URL || null;
|
const apiBaseUrl = process.env.ACME_CHALLTESTSRV_URL || null;
|
||||||
|
const httpsPort = axios.defaults.acmeSettings.httpsChallengePort || 443;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,11 +51,11 @@ async function addHttp01ChallengeResponse(token, content) {
|
||||||
return request('add-http01', { token, content });
|
return request('add-http01', { token, content });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addHttps01ChallengeResponse(token, content, targetHostname, targetPort = 443) {
|
async function addHttps01ChallengeResponse(token, content, targetHostname) {
|
||||||
await addHttp01ChallengeResponse(token, content);
|
await addHttp01ChallengeResponse(token, content);
|
||||||
return request('add-redirect', {
|
return request('add-redirect', {
|
||||||
path: `/.well-known/acme-challenge/${token}`,
|
path: `/.well-known/acme-challenge/${token}`,
|
||||||
targetURL: `https://${targetHostname}:${targetPort}/.well-known/acme-challenge/${token}`
|
targetURL: `https://${targetHostname}:${httpsPort}/.well-known/acme-challenge/${token}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,6 +77,11 @@ async function assertHttpChallengeCreateFn(authz, challenge, keyAuthorization) {
|
||||||
return addHttp01ChallengeResponse(challenge.token, keyAuthorization);
|
return addHttp01ChallengeResponse(challenge.token, keyAuthorization);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function assertHttpsChallengeCreateFn(authz, challenge, keyAuthorization) {
|
||||||
|
assert.strictEqual(challenge.type, 'http-01');
|
||||||
|
return addHttps01ChallengeResponse(challenge.token, keyAuthorization, authz.identifier.value);
|
||||||
|
}
|
||||||
|
|
||||||
async function assertDnsChallengeCreateFn(authz, challenge, keyAuthorization) {
|
async function assertDnsChallengeCreateFn(authz, challenge, keyAuthorization) {
|
||||||
assert.strictEqual(challenge.type, 'dns-01');
|
assert.strictEqual(challenge.type, 'dns-01');
|
||||||
return addDns01ChallengeResponse(`_acme-challenge.${authz.identifier.value}.`, keyAuthorization);
|
return addDns01ChallengeResponse(`_acme-challenge.${authz.identifier.value}.`, keyAuthorization);
|
||||||
|
@ -98,5 +104,6 @@ exports.challengeNoopFn = async () => true;
|
||||||
exports.challengeThrowFn = async () => { throw new Error('oops'); };
|
exports.challengeThrowFn = async () => { throw new Error('oops'); };
|
||||||
|
|
||||||
exports.assertHttpChallengeCreateFn = assertHttpChallengeCreateFn;
|
exports.assertHttpChallengeCreateFn = assertHttpChallengeCreateFn;
|
||||||
|
exports.assertHttpsChallengeCreateFn = assertHttpsChallengeCreateFn;
|
||||||
exports.assertDnsChallengeCreateFn = assertDnsChallengeCreateFn;
|
exports.assertDnsChallengeCreateFn = assertDnsChallengeCreateFn;
|
||||||
exports.challengeCreateFn = challengeCreateFn;
|
exports.challengeCreateFn = challengeCreateFn;
|
||||||
|
|
Loading…
Reference in New Issue