🔱: [acme] sync upgrade with 7 commits [trident-sync]

CHANGELOG
Fix tls-alpn-01 pebble test on Node v18+
Return correct tls-alpn-01 key authorization, tests
Support tls-alpn-01 internal challenge verification
Add tls-alpn-01 challenge test server support
Add ALPN crypto utility methods
This commit is contained in:
GitHub Actions Bot
2024-01-30 19:24:20 +00:00
parent 08c1f338d5
commit fc9e71bed2
14 changed files with 389 additions and 42 deletions
@@ -8,10 +8,13 @@ const https = require('https');
const { assert } = require('chai');
const cts = require('./challtestsrv');
const axios = require('./../src/axios');
const { retrieveTlsAlpnCertificate } = require('./../src/util');
const { isAlpnCertificateAuthorizationValid } = require('./../src/crypto');
const domainName = process.env.ACME_DOMAIN_NAME || 'example.com';
const httpPort = axios.defaults.acmeSettings.httpChallengePort || 80;
const httpsPort = axios.defaults.acmeSettings.httpsChallengePort || 443;
const tlsAlpnPort = axios.defaults.acmeSettings.tlsAlpnChallengePort || 443;
describe('pebble', () => {
@@ -33,6 +36,9 @@ describe('pebble', () => {
const testDns01ChallengeHost = `_acme-challenge.${uuid()}.${domainName}.`;
const testDns01ChallengeValue = uuid();
const testTlsAlpn01ChallengeHost = `${uuid()}.${domainName}`;
const testTlsAlpn01ChallengeValue = uuid();
/**
* Pebble CTS required
@@ -181,4 +187,29 @@ describe('pebble', () => {
assert.deepStrictEqual(resp, [[testDns01ChallengeValue]]);
});
});
/**
* TLS-ALPN-01 challenge response
*/
describe('tls-alpn-01', () => {
it('should not locate challenge response', async () => {
await assert.isRejected(retrieveTlsAlpnCertificate(testTlsAlpn01ChallengeHost, tlsAlpnPort), /(failed to retrieve)|(ssl3_read_bytes:tlsv1 alert internal error)/);
});
it('should timeout challenge response', async () => {
await assert.isRejected(retrieveTlsAlpnCertificate('example.org', tlsAlpnPort, 500), /timed out/);
});
it('should add challenge response', async () => {
const resp = await cts.addTlsAlpn01ChallengeResponse(testTlsAlpn01ChallengeHost, testTlsAlpn01ChallengeValue);
assert.isTrue(resp);
});
it('should locate challenge response', async () => {
const resp = await retrieveTlsAlpnCertificate(testTlsAlpn01ChallengeHost, tlsAlpnPort);
assert.isTrue(isAlpnCertificateAuthorizationValid(resp, testTlsAlpn01ChallengeValue));
});
});
});
@@ -26,6 +26,10 @@ describe('verify', () => {
const testDns01Key = uuid();
const testDns01Cname = `${uuid()}.${domainName}`;
const testTlsAlpn01Authz = { identifier: { type: 'dns', value: `${uuid()}.${domainName}` } };
const testTlsAlpn01Challenge = { type: 'dns-01', status: 'pending', token: uuid() };
const testTlsAlpn01Key = uuid();
/**
* Pebble CTS required
@@ -128,4 +132,25 @@ describe('verify', () => {
assert.isTrue(resp);
});
});
/**
* tls-alpn-01
*/
describe('tls-alpn-01', () => {
it('should reject challenge', async () => {
await assert.isRejected(verify['tls-alpn-01'](testTlsAlpn01Authz, testTlsAlpn01Challenge, testTlsAlpn01Key));
});
it('should mock challenge response', async () => {
const resp = await cts.addTlsAlpn01ChallengeResponse(testTlsAlpn01Authz.identifier.value, testTlsAlpn01Key);
assert.isTrue(resp);
});
it('should verify challenge', async () => {
const resp = await verify['tls-alpn-01'](testTlsAlpn01Authz, testTlsAlpn01Challenge, testTlsAlpn01Key);
assert.isTrue(resp);
});
});
});
@@ -95,6 +95,7 @@ describe('crypto', () => {
let testSanCsr;
let testNonCnCsr;
let testNonAsciiCsr;
let testAlpnCertificate;
/**
@@ -215,6 +216,31 @@ describe('crypto', () => {
assert.strictEqual(result.commonName, testCsrDomain);
assert.deepStrictEqual(result.altNames, [testCsrDomain]);
});
/**
* ALPN
*/
it(`${n}/should generate alpn certificate`, async () => {
const authz = { identifier: { value: 'test.example.com' } };
const [key, cert] = await crypto.createAlpnCertificate(authz, 'super-secret.12345', await createFn());
assert.isTrue(Buffer.isBuffer(key));
assert.isTrue(Buffer.isBuffer(cert));
testAlpnCertificate = cert;
});
it(`${n}/should not validate invalid alpn certificate key authorization`, () => {
assert.isFalse(crypto.isAlpnCertificateAuthorizationValid(testAlpnCertificate, 'aaaaaaa'));
assert.isFalse(crypto.isAlpnCertificateAuthorizationValid(testAlpnCertificate, 'bbbbbbb'));
assert.isFalse(crypto.isAlpnCertificateAuthorizationValid(testAlpnCertificate, 'ccccccc'));
});
it(`${n}/should validate valid alpn certificate key authorization`, () => {
assert.isTrue(crypto.isAlpnCertificateAuthorizationValid(testAlpnCertificate, 'super-secret.12345'));
});
});
});
});
@@ -250,7 +276,7 @@ describe('crypto', () => {
* CSR with auto-generated key
*/
it('should generate a csr with auto-generated key', async () => {
it('should generate a csr with default key', async () => {
const [key, csr] = await crypto.createCsr({
commonName: testCsrDomain
});
@@ -281,6 +307,19 @@ describe('crypto', () => {
});
/**
* ALPN
*/
it('should generate alpn certificate with default key', async () => {
const authz = { identifier: { value: 'test.example.com' } };
const [key, cert] = await crypto.createAlpnCertificate(authz, 'abc123');
assert.isTrue(Buffer.isBuffer(key));
assert.isTrue(Buffer.isBuffer(cert));
});
/**
* PEM utils
*/
@@ -33,6 +33,7 @@ if (capEabEnabled && process.env.ACME_EAB_KID && process.env.ACME_EAB_HMAC_KEY)
describe('client', () => {
const testDomain = `${uuid()}.${domainName}`;
const testDomainAlpn = `${uuid()}.${domainName}`;
const testDomainWildcard = `*.${testDomain}`;
const testContact = `mailto:test-${uuid()}@nope.com`;
@@ -78,16 +79,22 @@ describe('client', () => {
let testAccount;
let testAccountUrl;
let testOrder;
let testOrderAlpn;
let testOrderWildcard;
let testAuthz;
let testAuthzAlpn;
let testAuthzWildcard;
let testChallenge;
let testChallengeAlpn;
let testChallengeWildcard;
let testKeyAuthorization;
let testKeyAuthorizationAlpn;
let testKeyAuthorizationWildcard;
let testCsr;
let testCsrAlpn;
let testCsrWildcard;
let testCertificate;
let testCertificateAlpn;
let testCertificateWildcard;
@@ -107,6 +114,7 @@ describe('client', () => {
it('should generate certificate signing request', async () => {
[, testCsr] = await acme.crypto.createCsr({ commonName: testDomain }, await createKeyFn());
[, testCsrAlpn] = await acme.crypto.createCsr({ commonName: testDomainAlpn }, await createKeyFn());
[, testCsrWildcard] = await acme.crypto.createCsr({ commonName: testDomainWildcard }, await createKeyFn());
});
@@ -336,12 +344,14 @@ describe('client', () => {
it('should create new order', async () => {
const data1 = { identifiers: [{ type: 'dns', value: testDomain }] };
const data2 = { identifiers: [{ type: 'dns', value: testDomainWildcard }] };
const data2 = { identifiers: [{ type: 'dns', value: testDomainAlpn }] };
const data3 = { identifiers: [{ type: 'dns', value: testDomainWildcard }] };
testOrder = await testClient.createOrder(data1);
testOrderWildcard = await testClient.createOrder(data2);
testOrderAlpn = await testClient.createOrder(data2);
testOrderWildcard = await testClient.createOrder(data3);
[testOrder, testOrderWildcard].forEach((item) => {
[testOrder, testOrderAlpn, testOrderWildcard].forEach((item) => {
spec.rfc8555.order(item);
assert.strictEqual(item.status, 'pending');
});
@@ -353,7 +363,7 @@ describe('client', () => {
*/
it('should get existing order', async () => {
await Promise.all([testOrder, testOrderWildcard].map(async (existing) => {
await Promise.all([testOrder, testOrderAlpn, testOrderWildcard].map(async (existing) => {
const result = await testClient.getOrder(existing);
spec.rfc8555.order(result);
@@ -368,9 +378,10 @@ describe('client', () => {
it('should get identifier authorization', async () => {
const orderAuthzCollection = await testClient.getAuthorizations(testOrder);
const alpnAuthzCollection = await testClient.getAuthorizations(testOrderAlpn);
const wildcardAuthzCollection = await testClient.getAuthorizations(testOrderWildcard);
[orderAuthzCollection, wildcardAuthzCollection].forEach((collection) => {
[orderAuthzCollection, alpnAuthzCollection, wildcardAuthzCollection].forEach((collection) => {
assert.isArray(collection);
assert.isNotEmpty(collection);
@@ -381,9 +392,10 @@ describe('client', () => {
});
testAuthz = orderAuthzCollection.pop();
testAuthzAlpn = alpnAuthzCollection.pop();
testAuthzWildcard = wildcardAuthzCollection.pop();
testAuthz.challenges.concat(testAuthzWildcard.challenges).forEach((item) => {
testAuthz.challenges.concat(testAuthzAlpn.challenges).concat(testAuthzWildcard.challenges).forEach((item) => {
spec.rfc8555.challenge(item);
assert.strictEqual(item.status, 'pending');
});
@@ -396,12 +408,14 @@ describe('client', () => {
it('should get challenge key authorization', async () => {
testChallenge = testAuthz.challenges.find((c) => (c.type === 'http-01'));
testChallengeAlpn = testAuthzAlpn.challenges.find((c) => (c.type === 'tls-alpn-01'));
testChallengeWildcard = testAuthzWildcard.challenges.find((c) => (c.type === 'dns-01'));
testKeyAuthorization = await testClient.getChallengeKeyAuthorization(testChallenge);
testKeyAuthorizationAlpn = await testClient.getChallengeKeyAuthorization(testChallengeAlpn);
testKeyAuthorizationWildcard = await testClient.getChallengeKeyAuthorization(testChallengeWildcard);
[testKeyAuthorization, testKeyAuthorizationWildcard].forEach((k) => assert.isString(k));
[testKeyAuthorization, testKeyAuthorizationAlpn, testKeyAuthorizationWildcard].forEach((k) => assert.isString(k));
});
@@ -438,9 +452,11 @@ describe('client', () => {
it('should verify challenge', async () => {
await cts.assertHttpChallengeCreateFn(testAuthz, testChallenge, testKeyAuthorization);
await cts.assertTlsAlpnChallengeCreateFn(testAuthzAlpn, testChallengeAlpn, testKeyAuthorizationAlpn);
await cts.assertDnsChallengeCreateFn(testAuthzWildcard, testChallengeWildcard, testKeyAuthorizationWildcard);
await testClient.verifyChallenge(testAuthz, testChallenge);
await testClient.verifyChallenge(testAuthzAlpn, testChallengeAlpn);
await testClient.verifyChallenge(testAuthzWildcard, testChallengeWildcard);
});
@@ -450,7 +466,7 @@ describe('client', () => {
*/
it('should complete challenge', async () => {
await Promise.all([testChallenge, testChallengeWildcard].map(async (challenge) => {
await Promise.all([testChallenge, testChallengeAlpn, testChallengeWildcard].map(async (challenge) => {
const result = await testClient.completeChallenge(challenge);
spec.rfc8555.challenge(result);
@@ -464,7 +480,7 @@ describe('client', () => {
*/
it('should wait for valid challenge status', async () => {
await Promise.all([testChallenge, testChallengeWildcard].map(async (c) => testClient.waitForValidStatus(c)));
await Promise.all([testChallenge, testChallengeAlpn, testChallengeWildcard].map(async (c) => testClient.waitForValidStatus(c)));
});
@@ -474,11 +490,13 @@ describe('client', () => {
it('should finalize order', async () => {
const finalize = await testClient.finalizeOrder(testOrder, testCsr);
const finalizeAlpn = await testClient.finalizeOrder(testOrderAlpn, testCsrAlpn);
const finalizeWildcard = await testClient.finalizeOrder(testOrderWildcard, testCsrWildcard);
[finalize, finalizeWildcard].forEach((f) => spec.rfc8555.order(f));
[finalize, finalizeAlpn, finalizeWildcard].forEach((f) => spec.rfc8555.order(f));
assert.strictEqual(testOrder.url, finalize.url);
assert.strictEqual(testOrderAlpn.url, finalizeAlpn.url);
assert.strictEqual(testOrderWildcard.url, finalizeWildcard.url);
});
@@ -488,7 +506,7 @@ describe('client', () => {
*/
it('should wait for valid order status', async () => {
await Promise.all([testOrder, testOrderWildcard].map(async (o) => testClient.waitForValidStatus(o)));
await Promise.all([testOrder, testOrderAlpn, testOrderWildcard].map(async (o) => testClient.waitForValidStatus(o)));
});
@@ -498,9 +516,10 @@ describe('client', () => {
it('should get certificate', async () => {
testCertificate = await testClient.getCertificate(testOrder);
testCertificateAlpn = await testClient.getCertificate(testOrderAlpn);
testCertificateWildcard = await testClient.getCertificate(testOrderWildcard);
[testCertificate, testCertificateWildcard].forEach((cert) => {
[testCertificate, testCertificateAlpn, testCertificateWildcard].forEach((cert) => {
assert.isString(cert);
acme.crypto.readCertificateInfo(cert);
});
@@ -539,11 +558,13 @@ describe('client', () => {
it('should revoke certificate', async () => {
await testClient.revokeCertificate(testCertificate);
await testClient.revokeCertificate(testCertificateAlpn, { reason: 0 });
await testClient.revokeCertificate(testCertificateWildcard, { reason: 4 });
});
it('should not allow getting revoked certificate', async () => {
await assert.isRejected(testClient.getCertificate(testOrder));
await assert.isRejected(testClient.getCertificate(testOrderAlpn));
await assert.isRejected(testClient.getCertificate(testOrderWildcard));
});
@@ -34,6 +34,7 @@ describe('client.auto', () => {
const testHttpDomain = `${uuid()}.${domainName}`;
const testHttpsDomain = `${uuid()}.${domainName}`;
const testDnsDomain = `${uuid()}.${domainName}`;
const testAlpnDomain = `${uuid()}.${domainName}`;
const testWildcardDomain = `${uuid()}.${domainName}`;
const testSanDomains = [
@@ -280,6 +281,22 @@ describe('client.auto', () => {
assert.isString(cert);
});
it('should order certificate using tls-alpn-01', async () => {
const [, csr] = await acme.crypto.createCsr({
commonName: testAlpnDomain
}, await createKeyFn());
const cert = await testClient.auto({
csr,
termsOfServiceAgreed: true,
challengeCreateFn: cts.assertTlsAlpnChallengeCreateFn,
challengeRemoveFn: cts.challengeRemoveFn,
challengePriority: ['tls-alpn-01']
});
assert.isString(cert);
});
it('should order san certificate', async () => {
const [, csr] = await acme.crypto.createCsr({
commonName: testSanDomains[0],
@@ -63,9 +63,14 @@ async function addDns01ChallengeResponse(host, value) {
return request('set-txt', { host, value });
}
async function addTlsAlpn01ChallengeResponse(host, content) {
return request('add-tlsalpn01', { host, content });
}
exports.addHttp01ChallengeResponse = addHttp01ChallengeResponse;
exports.addHttps01ChallengeResponse = addHttps01ChallengeResponse;
exports.addDns01ChallengeResponse = addDns01ChallengeResponse;
exports.addTlsAlpn01ChallengeResponse = addTlsAlpn01ChallengeResponse;
/**
@@ -87,6 +92,11 @@ async function assertDnsChallengeCreateFn(authz, challenge, keyAuthorization) {
return addDns01ChallengeResponse(`_acme-challenge.${authz.identifier.value}.`, keyAuthorization);
}
async function assertTlsAlpnChallengeCreateFn(authz, challenge, keyAuthorization) {
assert.strictEqual(challenge.type, 'tls-alpn-01');
return addTlsAlpn01ChallengeResponse(authz.identifier.value, keyAuthorization);
}
async function challengeCreateFn(authz, challenge, keyAuthorization) {
if (challenge.type === 'http-01') {
return assertHttpChallengeCreateFn(authz, challenge, keyAuthorization);
@@ -96,6 +106,10 @@ async function challengeCreateFn(authz, challenge, keyAuthorization) {
return assertDnsChallengeCreateFn(authz, challenge, keyAuthorization);
}
if (challenge.type === 'tls-alpn-01') {
return assertTlsAlpnChallengeCreateFn(authz, challenge, keyAuthorization);
}
throw new Error(`Unsupported challenge type ${challenge.type}`);
}
@@ -106,4 +120,5 @@ exports.challengeThrowFn = async () => { throw new Error('oops'); };
exports.assertHttpChallengeCreateFn = assertHttpChallengeCreateFn;
exports.assertHttpsChallengeCreateFn = assertHttpsChallengeCreateFn;
exports.assertDnsChallengeCreateFn = assertDnsChallengeCreateFn;
exports.assertTlsAlpnChallengeCreateFn = assertTlsAlpnChallengeCreateFn;
exports.challengeCreateFn = challengeCreateFn;
+4
View File
@@ -27,6 +27,10 @@ if (process.env.ACME_HTTPS_PORT) {
axios.defaults.acmeSettings.httpsChallengePort = process.env.ACME_HTTPS_PORT;
}
if (process.env.ACME_TLSALPN_PORT) {
axios.defaults.acmeSettings.tlsAlpnChallengePort = process.env.ACME_TLSALPN_PORT;
}
/**
* External account binding