From 08c1f338d5e085d304e5bdb81294ef9d997ca180 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Mon, 22 Jan 2024 19:24:37 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=B1:=20[acme]=20sync=20upgrade=20with?= =?UTF-8?q?=2010=20commits=20[trident-sync]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../core/acme-client/.circleci/config.yml | 17 ++++--- packages/core/acme-client/.gitignore | 2 - packages/core/acme-client/.yarnrc | 2 - packages/core/acme-client/CHANGELOG.md | 5 ++ packages/core/acme-client/package.json | 2 +- packages/core/acme-client/src/auto.js | 15 +++++- packages/core/acme-client/src/verify.js | 6 ++- .../core/acme-client/test/00-pebble.spec.js | 2 +- .../core/acme-client/test/10-verify.spec.js | 25 ++++++++++ .../core/acme-client/test/70-auto.spec.js | 49 +++++++++++++++++++ .../core/acme-client/test/challtestsrv.js | 11 ++++- 11 files changed, 119 insertions(+), 17 deletions(-) delete mode 100644 packages/core/acme-client/.yarnrc diff --git a/packages/core/acme-client/.circleci/config.yml b/packages/core/acme-client/.circleci/config.yml index 8865b201..589f75c5 100644 --- a/packages/core/acme-client/.circleci/config.yml +++ b/packages/core/acme-client/.circleci/config.yml @@ -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 diff --git a/packages/core/acme-client/.gitignore b/packages/core/acme-client/.gitignore index 61a8d616..a261ab9a 100644 --- a/packages/core/acme-client/.gitignore +++ b/packages/core/acme-client/.gitignore @@ -1,6 +1,4 @@ .vscode/ node_modules/ npm-debug.log -yarn-error.log -yarn.lock package-lock.json diff --git a/packages/core/acme-client/.yarnrc b/packages/core/acme-client/.yarnrc deleted file mode 100644 index 8d6f153a..00000000 --- a/packages/core/acme-client/.yarnrc +++ /dev/null @@ -1,2 +0,0 @@ -ignore-engines true -ignore-optional true diff --git a/packages/core/acme-client/CHANGELOG.md b/packages/core/acme-client/CHANGELOG.md index fad2966c..fcfa31f7 100644 --- a/packages/core/acme-client/CHANGELOG.md +++ b/packages/core/acme-client/CHANGELOG.md @@ -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) diff --git a/packages/core/acme-client/package.json b/packages/core/acme-client/package.json index ecf39dc9..86bd44bc 100644 --- a/packages/core/acme-client/package.json +++ b/packages/core/acme-client/package.json @@ -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", diff --git a/packages/core/acme-client/src/auto.js b/packages/core/acme-client/src/auto.js index 2b009d7f..9e7dc05d 100644 --- a/packages/core/acme-client/src/auto.js +++ b/packages/core/acme-client/src/auto.js @@ -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; + } /** diff --git a/packages/core/acme-client/src/verify.js b/packages/core/acme-client/src/verify.js index fe76cab8..5c9da2df 100644 --- a/packages/core/acme-client/src/verify.js +++ b/packages/core/acme-client/src/verify.js @@ -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}`); diff --git a/packages/core/acme-client/test/00-pebble.spec.js b/packages/core/acme-client/test/00-pebble.spec.js index ce99d7a8..70376c9a 100644 --- a/packages/core/acme-client/test/00-pebble.spec.js +++ b/packages/core/acme-client/test/00-pebble.spec.js @@ -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); }); diff --git a/packages/core/acme-client/test/10-verify.spec.js b/packages/core/acme-client/test/10-verify.spec.js index 5a0b39e3..55e98c54 100644 --- a/packages/core/acme-client/test/10-verify.spec.js +++ b/packages/core/acme-client/test/10-verify.spec.js @@ -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 */ diff --git a/packages/core/acme-client/test/70-auto.spec.js b/packages/core/acme-client/test/70-auto.spec.js index 8e710004..eb80c483 100644 --- a/packages/core/acme-client/test/70-auto.spec.js +++ b/packages/core/acme-client/test/70-auto.spec.js @@ -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 diff --git a/packages/core/acme-client/test/challtestsrv.js b/packages/core/acme-client/test/challtestsrv.js index a823c304..c3013365 100644 --- a/packages/core/acme-client/test/challtestsrv.js +++ b/packages/core/acme-client/test/challtestsrv.js @@ -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;