Files
qinglong/packages/ql3-worker-runtime/test/certificateIdentity.test.cjs
T

128 lines
3.8 KiB
JavaScript

'use strict';
const assert = require('node:assert/strict');
const { createHash, X509Certificate } = require('node:crypto');
const test = require('node:test');
const {
generateWorkerCertificateEnrollment,
} = require('../dist/credential/workerCertificateEnrollment');
const {
validateWorkerCertificateIdentity,
} = require('../dist/credential/workerCertificateIdentity');
const {
createCertificateAuthority,
} = require('./helpers/certificateAuthority.cjs');
const HOUR_MS = 60 * 60_000;
async function identityFixture(options = {}) {
const now = options.now ?? Date.now();
const ca = await createCertificateAuthority({ now });
const enrollment = await generateWorkerCertificateEnrollment({
workerId: 'worker-identity-01',
});
const certificateChainPem = await ca.issue(
enrollment.certificateSigningRequestPem,
options.issue,
);
return { now, ca, enrollment, certificateChainPem };
}
test('validates the leaf identity independently of PEM chain formatting', async () => {
const fixture = await identityFixture();
try {
const summary = validateWorkerCertificateIdentity({
privateKeyPem: fixture.enrollment.privateKeyPem,
certificateChainPem: `${fixture.certificateChainPem}\n`,
trustAnchors: [fixture.ca.certificatePem],
now: fixture.now,
minimumRemainingValidityMs: HOUR_MS,
});
const leaf = new X509Certificate(fixture.certificateChainPem);
assert.equal(
summary.certificateSha256,
createHash('sha256').update(leaf.raw).digest('hex'),
);
assert.equal(
summary.publicKeySpkiSha256,
fixture.enrollment.publicKeySpkiSha256,
);
} finally {
fixture.enrollment.dispose();
}
});
test('fails closed for an untrusted issuer', async () => {
const fixture = await identityFixture();
const otherCa = await createCertificateAuthority({ now: fixture.now });
try {
assert.throws(
() =>
validateWorkerCertificateIdentity({
privateKeyPem: fixture.enrollment.privateKeyPem,
certificateChainPem: fixture.certificateChainPem,
trustAnchors: [otherCa.certificatePem],
now: fixture.now,
}),
(error) => error.reason === 'untrusted',
);
} finally {
fixture.enrollment.dispose();
}
});
test('rejects expired, short-lived and non-client-auth leaves', async () => {
const now = Date.now();
const expired = await identityFixture({
now,
issue: { notBeforeMs: now - 2 * HOUR_MS, notAfterMs: now - HOUR_MS },
});
const shortLived = await identityFixture({
now,
issue: { notAfterMs: now + 2 * HOUR_MS },
});
const wrongUsage = await identityFixture({
now,
issue: { clientAuth: false },
});
try {
assert.throws(
() =>
validateWorkerCertificateIdentity({
privateKeyPem: expired.enrollment.privateKeyPem,
certificateChainPem: expired.certificateChainPem,
trustAnchors: [expired.ca.certificatePem],
now,
}),
(error) => error.reason === 'expired',
);
assert.throws(
() =>
validateWorkerCertificateIdentity({
privateKeyPem: shortLived.enrollment.privateKeyPem,
certificateChainPem: shortLived.certificateChainPem,
trustAnchors: [shortLived.ca.certificatePem],
now,
minimumRemainingValidityMs: 3 * HOUR_MS,
}),
(error) => error.reason === 'insufficient_validity',
);
assert.throws(
() =>
validateWorkerCertificateIdentity({
privateKeyPem: wrongUsage.enrollment.privateKeyPem,
certificateChainPem: wrongUsage.certificateChainPem,
trustAnchors: [wrongUsage.ca.certificatePem],
now,
}),
(error) => error.reason === 'not_client_auth',
);
} finally {
expired.enrollment.dispose();
shortLived.enrollment.dispose();
wrongUsage.enrollment.dispose();
}
});