72 lines
4.1 KiB
JavaScript
72 lines
4.1 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import tls from 'node:tls';
|
|
import { X509Certificate } from 'node:crypto';
|
|
import { pathToFileURL } from 'node:url';
|
|
|
|
const fingerprint = value => {
|
|
assert.equal(typeof value, 'string', 'Fingerprint is required');
|
|
const normalized = value.replaceAll(':', '').toLowerCase();
|
|
assert(/^[a-f0-9]{64}$/.test(normalized), 'Expected SHA-256 fingerprint');
|
|
return normalized;
|
|
};
|
|
|
|
export function compareRenewal(before, after) {
|
|
for (const sample of [before, after]) {
|
|
assert(/^mmt253-[a-f0-9]{8}\.pngs\.top$/.test(sample.fqdn), 'Only the prepared exact test domain is permitted');
|
|
assert.equal(sample.remoteAddress, '104.165.244.7');
|
|
assert.equal(sample.authorized, true);
|
|
assert.equal(sample.hostnameMatched, true);
|
|
assert.equal(sample.nativePrivateKey, true);
|
|
assert.equal(sample.nativeName, sample.certName);
|
|
assert.equal(sample.certName, sample.fqdn.replace('.pngs.top', ''));
|
|
assert.equal(fingerprint(sample.nativeFingerprint256), fingerprint(sample.fingerprint256), 'Native leaf must match served TLS');
|
|
assert(Number.isFinite(Date.parse(sample.validUntil)), 'Expiry is required');
|
|
assert(Number.isFinite(Date.parse(sample.sampledAt)), 'Sample time is required');
|
|
assert(Date.parse(sample.validUntil) > Date.parse(sample.sampledAt), 'Certificate is expired');
|
|
}
|
|
assert.equal(after.fqdn, before.fqdn);
|
|
assert.equal(after.certName, before.certName);
|
|
assert.notEqual(fingerprint(after.fingerprint256), fingerprint(before.fingerprint256), 'Old certificate is still being served');
|
|
assert(Date.parse(after.sampledAt) >= Date.parse(before.sampledAt));
|
|
assert(Date.parse(after.validUntil) > Date.parse(after.sampledAt), 'New certificate is expired');
|
|
// A second issuance on the same day need not extend expiry by a full day.
|
|
return { fingerprintChanged: true, nativeAndTlsMatch: true, trustedHostnameMatch: true };
|
|
}
|
|
|
|
async function sample(fixture) {
|
|
assert(/^mmt253-[a-f0-9]{8}\.pngs\.top$/.test(fixture.fqdn));
|
|
assert.equal(fixture.certName, fixture.fqdn.replace('.pngs.top', ''));
|
|
assert.equal(fixture.nativePrivateKey, true);
|
|
fingerprint(fixture.nativeFingerprint256);
|
|
return await new Promise((resolve, reject) => {
|
|
const socket = tls.connect({ host: '104.165.244.7', port: 443, servername: fixture.fqdn, rejectUnauthorized: true });
|
|
socket.setTimeout(15000, () => socket.destroy(new Error('TLS acceptance timeout')));
|
|
socket.once('error', reject);
|
|
socket.once('secureConnect', () => {
|
|
try {
|
|
assert.equal(socket.authorized, true);
|
|
const cert = new X509Certificate(socket.getPeerCertificate().raw);
|
|
assert(cert.checkHost(fixture.fqdn), 'Hostname mismatch');
|
|
assert(Date.parse(cert.validTo) > Date.now(), 'Certificate is expired');
|
|
assert.equal(fingerprint(cert.fingerprint256), fingerprint(fixture.nativeFingerprint256), 'Native leaf does not match TLS');
|
|
resolve({ sampledAt: new Date().toISOString(), fqdn: fixture.fqdn, certName: fixture.certName,
|
|
remoteAddress: socket.remoteAddress, authorized: socket.authorized, hostnameMatched: true,
|
|
protocol: socket.getProtocol(), fingerprint256: cert.fingerprint256,
|
|
validFrom: cert.validFrom, validUntil: cert.validTo,
|
|
nativeName: fixture.certName, nativePrivateKey: true, nativeFingerprint256: fixture.nativeFingerprint256 });
|
|
} catch (error) { reject(error); } finally { socket.end(); }
|
|
});
|
|
});
|
|
}
|
|
|
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
const [mode, input, output] = process.argv.slice(2);
|
|
assert(input && output && ['sample', 'compare'].includes(mode), 'Usage: sample native-summary.json output.json | compare samples.json output.json');
|
|
assert(!fs.existsSync(output), 'Never overwrite acceptance evidence');
|
|
const data = JSON.parse(fs.readFileSync(input, 'utf8'));
|
|
const result = mode === 'sample' ? await sample(data) : compareRenewal(data.before, data.after);
|
|
fs.writeFileSync(output, JSON.stringify(result, null, 2) + '\n', { flag: 'wx' });
|
|
console.log(mode === 'sample' ? 'Trusted TLS/native fingerprint sample saved' : 'Renewal fingerprint comparison passed');
|
|
}
|