🔱: [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 verifyHttpChallenge
pull/29/head
GitHub Actions Bot 2024-01-22 19:24:37 +00:00
parent 18865f0931
commit 08c1f338d5
11 changed files with 119 additions and 17 deletions

View File

@ -4,9 +4,14 @@ version: 2.1
commands:
pre:
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: npm --version
- run: yarn --version
- checkout
enable-eab:
@ -105,13 +110,13 @@ commands:
test:
steps:
- run: yarn --color
- run: yarn run lint --color
- run: yarn run lint-types
- run: yarn run build-docs
- run: npm i
- run: npm run lint
- run: npm run lint-types
- run: npm run build-docs
- run:
command: yarn run test --color
command: npm run test
environment:
ACME_DOMAIN_NAME: test.example.com
ACME_CHALLTESTSRV_URL: http://127.0.0.1:8055

View File

@ -1,6 +1,4 @@
.vscode/
node_modules/
npm-debug.log
yarn-error.log
yarn.lock
package-lock.json

View File

@ -1,2 +0,0 @@
ignore-engines true
ignore-optional true

View File

@ -1,5 +1,10 @@
# 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)
* `fixed` Upgrade `jsrsasign@11.0.0` - [GHSA-rh63-9qcf-83gf](https://github.com/kjur/jsrsasign/security/advisories/GHSA-rh63-9qcf-83gf)

View File

@ -2,7 +2,7 @@
"name": "acme-client",
"description": "Simple and unopinionated ACME client",
"author": "nmorsman",
"version": "5.1.0",
"version": "5.2.0",
"main": "src/index.js",
"types": "types/index.d.ts",
"license": "MIT",

View File

@ -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;
}
/**

View File

@ -3,6 +3,7 @@
*/
const dns = require('dns').promises;
const https = require('https');
const { log } = require('./logger');
const axios = require('./axios');
const util = require('./util');
@ -24,8 +25,11 @@ async function verifyHttpChallenge(authz, challenge, keyAuthorization, suffix =
const httpPort = axios.defaults.acmeSettings.httpChallengePort || 80;
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}`);
const resp = await axios.get(challengeUrl);
const resp = await axios.get(challengeUrl, { httpsAgent });
const data = (resp.data || '').replace(/\s+$/, '');
log(`Query successful, HTTP status code: ${resp.status}`);

View File

@ -129,7 +129,7 @@ describe('pebble', () => {
});
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);
});

View File

@ -17,6 +17,10 @@ describe('verify', () => {
const testHttp01Challenge = { type: 'http-01', status: 'pending', token: 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 testDns01Challenge = { type: 'dns-01', status: 'pending', token: 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
*/

View File

@ -32,6 +32,7 @@ if (capEabEnabled && process.env.ACME_EAB_KID && process.env.ACME_EAB_HMAC_KEY)
describe('client.auto', () => {
const testDomain = `${uuid()}.${domainName}`;
const testHttpDomain = `${uuid()}.${domainName}`;
const testHttpsDomain = `${uuid()}.${domainName}`;
const testDnsDomain = `${uuid()}.${domainName}`;
const testWildcardDomain = `${uuid()}.${domainName}`;
@ -178,6 +179,38 @@ describe('client.auto', () => {
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
@ -215,6 +248,22 @@ describe('client.auto', () => {
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 () => {
const [, csr] = await acme.crypto.createCsr({
commonName: testDnsDomain

View File

@ -6,6 +6,7 @@ const { assert } = require('chai');
const axios = require('./../src/axios');
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 });
}
async function addHttps01ChallengeResponse(token, content, targetHostname, targetPort = 443) {
async function addHttps01ChallengeResponse(token, content, targetHostname) {
await addHttp01ChallengeResponse(token, content);
return request('add-redirect', {
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);
}
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) {
assert.strictEqual(challenge.type, 'dns-01');
return addDns01ChallengeResponse(`_acme-challenge.${authz.identifier.value}.`, keyAuthorization);
@ -98,5 +104,6 @@ exports.challengeNoopFn = async () => true;
exports.challengeThrowFn = async () => { throw new Error('oops'); };
exports.assertHttpChallengeCreateFn = assertHttpChallengeCreateFn;
exports.assertHttpsChallengeCreateFn = assertHttpsChallengeCreateFn;
exports.assertDnsChallengeCreateFn = assertDnsChallengeCreateFn;
exports.challengeCreateFn = challengeCreateFn;