mirror of https://github.com/certd/certd
385 lines
13 KiB
JavaScript
385 lines
13 KiB
JavaScript
/**
|
|
* ACME client.auto tests
|
|
*/
|
|
|
|
const { assert } = require('chai');
|
|
const { v4: uuid } = require('uuid');
|
|
const cts = require('./challtestsrv');
|
|
const getCertIssuers = require('./get-cert-issuers');
|
|
const spec = require('./spec');
|
|
const acme = require('./../');
|
|
|
|
const domainName = process.env.ACME_DOMAIN_NAME || 'example.com';
|
|
const directoryUrl = process.env.ACME_DIRECTORY_URL || acme.directory.letsencrypt.staging;
|
|
const capEabEnabled = (('ACME_CAP_EAB_ENABLED' in process.env) && (process.env.ACME_CAP_EAB_ENABLED === '1'));
|
|
const capAlternateCertRoots = !(('ACME_CAP_ALTERNATE_CERT_ROOTS' in process.env) && (process.env.ACME_CAP_ALTERNATE_CERT_ROOTS === '0'));
|
|
|
|
const clientOpts = {
|
|
directoryUrl,
|
|
backoffAttempts: 5,
|
|
backoffMin: 1000,
|
|
backoffMax: 5000
|
|
};
|
|
|
|
if (capEabEnabled && process.env.ACME_EAB_KID && process.env.ACME_EAB_HMAC_KEY) {
|
|
clientOpts.externalAccountBinding = {
|
|
kid: process.env.ACME_EAB_KID,
|
|
hmacKey: process.env.ACME_EAB_HMAC_KEY
|
|
};
|
|
}
|
|
|
|
|
|
describe('client.auto', () => {
|
|
const testDomain = `${uuid()}.${domainName}`;
|
|
const testHttpDomain = `${uuid()}.${domainName}`;
|
|
const testDnsDomain = `${uuid()}.${domainName}`;
|
|
const testWildcardDomain = `${uuid()}.${domainName}`;
|
|
|
|
const testSanDomains = [
|
|
`${uuid()}.${domainName}`,
|
|
`${uuid()}.${domainName}`,
|
|
`${uuid()}.${domainName}`
|
|
];
|
|
|
|
|
|
/**
|
|
* Pebble CTS required
|
|
*/
|
|
|
|
before(function() {
|
|
if (!cts.isEnabled()) {
|
|
this.skip();
|
|
}
|
|
});
|
|
|
|
|
|
/**
|
|
* Key types
|
|
*/
|
|
|
|
Object.entries({
|
|
rsa: {
|
|
createKeyFn: () => acme.crypto.createPrivateRsaKey(),
|
|
createKeyAltFns: {
|
|
s1024: () => acme.crypto.createPrivateRsaKey(1024),
|
|
s4096: () => acme.crypto.createPrivateRsaKey(4096)
|
|
}
|
|
},
|
|
ecdsa: {
|
|
createKeyFn: () => acme.crypto.createPrivateEcdsaKey(),
|
|
createKeyAltFns: {
|
|
p384: () => acme.crypto.createPrivateEcdsaKey('P-384'),
|
|
p521: () => acme.crypto.createPrivateEcdsaKey('P-521')
|
|
}
|
|
}
|
|
}).forEach(([name, { createKeyFn, createKeyAltFns }]) => {
|
|
describe(name, () => {
|
|
let testIssuers;
|
|
let testClient;
|
|
let testCertificate;
|
|
let testSanCertificate;
|
|
let testWildcardCertificate;
|
|
|
|
|
|
/**
|
|
* Fixtures
|
|
*/
|
|
|
|
it('should resolve certificate issuers [ACME_CAP_ALTERNATE_CERT_ROOTS]', async function() {
|
|
if (!capAlternateCertRoots) {
|
|
this.skip();
|
|
}
|
|
|
|
testIssuers = await getCertIssuers();
|
|
|
|
assert.isArray(testIssuers);
|
|
assert.isTrue(testIssuers.length > 1);
|
|
|
|
testIssuers.forEach((i) => {
|
|
assert.isString(i);
|
|
assert.strictEqual(1, testIssuers.filter((c) => (c === i)).length);
|
|
});
|
|
});
|
|
|
|
|
|
/**
|
|
* Initialize client
|
|
*/
|
|
|
|
it('should initialize client', async () => {
|
|
testClient = new acme.Client({
|
|
...clientOpts,
|
|
accountKey: await createKeyFn()
|
|
});
|
|
});
|
|
|
|
|
|
/**
|
|
* Invalid challenge response
|
|
*/
|
|
|
|
it('should throw on invalid challenge response', async () => {
|
|
const [, csr] = await acme.crypto.createCsr({
|
|
commonName: `${uuid()}.${domainName}`
|
|
}, await createKeyFn());
|
|
|
|
await assert.isRejected(testClient.auto({
|
|
csr,
|
|
termsOfServiceAgreed: true,
|
|
challengeCreateFn: cts.challengeNoopFn,
|
|
challengeRemoveFn: cts.challengeNoopFn
|
|
}), /^authorization not found/i);
|
|
});
|
|
|
|
it('should throw on invalid challenge response with opts.skipChallengeVerification=true', async () => {
|
|
const [, csr] = await acme.crypto.createCsr({
|
|
commonName: `${uuid()}.${domainName}`
|
|
}, await createKeyFn());
|
|
|
|
await assert.isRejected(testClient.auto({
|
|
csr,
|
|
termsOfServiceAgreed: true,
|
|
skipChallengeVerification: true,
|
|
challengeCreateFn: cts.challengeNoopFn,
|
|
challengeRemoveFn: cts.challengeNoopFn
|
|
}));
|
|
});
|
|
|
|
|
|
/**
|
|
* Challenge function exceptions
|
|
*/
|
|
|
|
it('should throw on challengeCreate exception', async () => {
|
|
const [, csr] = await acme.crypto.createCsr({
|
|
commonName: `${uuid()}.${domainName}`
|
|
}, await createKeyFn());
|
|
|
|
await assert.isRejected(testClient.auto({
|
|
csr,
|
|
termsOfServiceAgreed: true,
|
|
challengeCreateFn: cts.challengeThrowFn,
|
|
challengeRemoveFn: cts.challengeNoopFn
|
|
}), /^oops$/);
|
|
});
|
|
|
|
it('should not throw on challengeRemove exception', async () => {
|
|
const [, csr] = await acme.crypto.createCsr({
|
|
commonName: `${uuid()}.${domainName}`
|
|
}, await createKeyFn());
|
|
|
|
const cert = await testClient.auto({
|
|
csr,
|
|
termsOfServiceAgreed: true,
|
|
challengeCreateFn: cts.challengeCreateFn,
|
|
challengeRemoveFn: cts.challengeThrowFn
|
|
});
|
|
|
|
assert.isString(cert);
|
|
});
|
|
|
|
|
|
/**
|
|
* Order certificates
|
|
*/
|
|
|
|
it('should order certificate', async () => {
|
|
const [, csr] = await acme.crypto.createCsr({
|
|
commonName: testDomain
|
|
}, await createKeyFn());
|
|
|
|
const cert = await testClient.auto({
|
|
csr,
|
|
termsOfServiceAgreed: true,
|
|
challengeCreateFn: cts.challengeCreateFn,
|
|
challengeRemoveFn: cts.challengeRemoveFn
|
|
});
|
|
|
|
assert.isString(cert);
|
|
testCertificate = cert;
|
|
});
|
|
|
|
it('should order certificate using http-01', async () => {
|
|
const [, csr] = await acme.crypto.createCsr({
|
|
commonName: testHttpDomain
|
|
}, await createKeyFn());
|
|
|
|
const cert = await testClient.auto({
|
|
csr,
|
|
termsOfServiceAgreed: true,
|
|
challengeCreateFn: cts.assertHttpChallengeCreateFn,
|
|
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
|
|
}, await createKeyFn());
|
|
|
|
const cert = await testClient.auto({
|
|
csr,
|
|
termsOfServiceAgreed: true,
|
|
challengeCreateFn: cts.assertDnsChallengeCreateFn,
|
|
challengeRemoveFn: cts.challengeRemoveFn,
|
|
challengePriority: ['dns-01']
|
|
});
|
|
|
|
assert.isString(cert);
|
|
});
|
|
|
|
it('should order san certificate', async () => {
|
|
const [, csr] = await acme.crypto.createCsr({
|
|
commonName: testSanDomains[0],
|
|
altNames: testSanDomains
|
|
}, await createKeyFn());
|
|
|
|
const cert = await testClient.auto({
|
|
csr,
|
|
termsOfServiceAgreed: true,
|
|
challengeCreateFn: cts.challengeCreateFn,
|
|
challengeRemoveFn: cts.challengeRemoveFn
|
|
});
|
|
|
|
assert.isString(cert);
|
|
testSanCertificate = cert;
|
|
});
|
|
|
|
it('should order wildcard certificate', async () => {
|
|
const [, csr] = await acme.crypto.createCsr({
|
|
commonName: testWildcardDomain,
|
|
altNames: [`*.${testWildcardDomain}`]
|
|
}, await createKeyFn());
|
|
|
|
const cert = await testClient.auto({
|
|
csr,
|
|
termsOfServiceAgreed: true,
|
|
challengeCreateFn: cts.challengeCreateFn,
|
|
challengeRemoveFn: cts.challengeRemoveFn
|
|
});
|
|
|
|
assert.isString(cert);
|
|
testWildcardCertificate = cert;
|
|
});
|
|
|
|
it('should order certificate with opts.skipChallengeVerification=true', async () => {
|
|
const [, csr] = await acme.crypto.createCsr({
|
|
commonName: `${uuid()}.${domainName}`
|
|
}, await createKeyFn());
|
|
|
|
const cert = await testClient.auto({
|
|
csr,
|
|
termsOfServiceAgreed: true,
|
|
skipChallengeVerification: true,
|
|
challengeCreateFn: cts.challengeCreateFn,
|
|
challengeRemoveFn: cts.challengeRemoveFn
|
|
});
|
|
|
|
assert.isString(cert);
|
|
});
|
|
|
|
it('should order alternate certificate chain [ACME_CAP_ALTERNATE_CERT_ROOTS]', async function() {
|
|
if (!capAlternateCertRoots) {
|
|
this.skip();
|
|
}
|
|
|
|
await Promise.all(testIssuers.map(async (issuer) => {
|
|
const [, csr] = await acme.crypto.createCsr({
|
|
commonName: `${uuid()}.${domainName}`
|
|
}, await createKeyFn());
|
|
|
|
const cert = await testClient.auto({
|
|
csr,
|
|
termsOfServiceAgreed: true,
|
|
preferredChain: issuer,
|
|
challengeCreateFn: cts.challengeCreateFn,
|
|
challengeRemoveFn: cts.challengeRemoveFn
|
|
});
|
|
|
|
const rootCert = acme.crypto.splitPemChain(cert).pop();
|
|
const info = acme.crypto.readCertificateInfo(rootCert);
|
|
|
|
assert.strictEqual(issuer, info.issuer.commonName);
|
|
}));
|
|
});
|
|
|
|
it('should get default chain with invalid preference [ACME_CAP_ALTERNATE_CERT_ROOTS]', async function() {
|
|
if (!capAlternateCertRoots) {
|
|
this.skip();
|
|
}
|
|
|
|
const [, csr] = await acme.crypto.createCsr({
|
|
commonName: `${uuid()}.${domainName}`
|
|
}, await createKeyFn());
|
|
|
|
const cert = await testClient.auto({
|
|
csr,
|
|
termsOfServiceAgreed: true,
|
|
preferredChain: uuid(),
|
|
challengeCreateFn: cts.challengeCreateFn,
|
|
challengeRemoveFn: cts.challengeRemoveFn
|
|
});
|
|
|
|
const rootCert = acme.crypto.splitPemChain(cert).pop();
|
|
const info = acme.crypto.readCertificateInfo(rootCert);
|
|
|
|
assert.strictEqual(testIssuers[0], info.issuer.commonName);
|
|
});
|
|
|
|
|
|
/**
|
|
* Order certificate with alternate key sizes
|
|
*/
|
|
|
|
Object.entries(createKeyAltFns).forEach(([k, altKeyFn]) => {
|
|
it(`should order certificate with key=${k}`, async () => {
|
|
const [, csr] = await acme.crypto.createCsr({
|
|
commonName: testDomain
|
|
}, await altKeyFn());
|
|
|
|
const cert = await testClient.auto({
|
|
csr,
|
|
termsOfServiceAgreed: true,
|
|
challengeCreateFn: cts.challengeCreateFn,
|
|
challengeRemoveFn: cts.challengeRemoveFn
|
|
});
|
|
|
|
assert.isString(cert);
|
|
});
|
|
});
|
|
|
|
|
|
/**
|
|
* Read certificates
|
|
*/
|
|
|
|
it('should read certificate info', () => {
|
|
const info = acme.crypto.readCertificateInfo(testCertificate);
|
|
|
|
spec.crypto.certificateInfo(info);
|
|
assert.strictEqual(info.domains.commonName, testDomain);
|
|
assert.deepStrictEqual(info.domains.altNames, [testDomain]);
|
|
});
|
|
|
|
it('should read san certificate info', () => {
|
|
const info = acme.crypto.readCertificateInfo(testSanCertificate);
|
|
|
|
spec.crypto.certificateInfo(info);
|
|
assert.strictEqual(info.domains.commonName, testSanDomains[0]);
|
|
assert.deepStrictEqual(info.domains.altNames, testSanDomains);
|
|
});
|
|
|
|
it('should read wildcard certificate info', () => {
|
|
const info = acme.crypto.readCertificateInfo(testWildcardCertificate);
|
|
|
|
spec.crypto.certificateInfo(info);
|
|
assert.strictEqual(info.domains.commonName, testWildcardDomain);
|
|
assert.deepStrictEqual(info.domains.altNames, [testWildcardDomain, `*.${testWildcardDomain}`]);
|
|
});
|
|
});
|
|
});
|
|
});
|