Files
Haven/test/selfsignedCert.test.js
Amnibro 4a4435afe0 Every install path uses Haven's own certificate generator (#5586)
4.5.0 taught the server to make its certificate itself, but Start Haven.bat, Install Haven.ps1 and the web installer still went looking for openssl.exe first and reported a skipped certificate as done. They now call the same generator through scripts/gen-cert.js, the certificate carries the CA and server-auth flags phones expect when you import it, and the guide and support page stop telling people to install OpenSSL. Reported by MutantRabbit767.
2026-09-08 17:23:38 -04:00

56 lines
3.0 KiB
JavaScript

'use strict';
const assert = require('node:assert/strict');
const test = require('node:test');
const crypto = require('crypto');
const fs = require('fs');
const https = require('https');
const os = require('os');
const path = require('path');
const { generate, ensureCerts, localNames } = require('../src/selfsignedCert');
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'haven-cert-'));
const made = ensureCerts(dir);
const certPem = fs.readFileSync(made.certPath, 'utf8');
const keyPem = fs.readFileSync(made.keyPath, 'utf8');
const x509 = new crypto.X509Certificate(certPem);
test('writes cert.pem and key.pem once and leaves them alone after that', () => {
assert.equal(made.created, true);
assert.match(certPem, /^-----BEGIN CERTIFICATE-----\n[\s\S]+\n-----END CERTIFICATE-----\n$/);
assert.match(keyPem, /^-----BEGIN PRIVATE KEY-----/);
const again = ensureCerts(dir);
assert.equal(again.created, false);
assert.equal(fs.readFileSync(again.certPath, 'utf8'), certPem);
});
test('parses as a v3 self-signed cert that Node itself trusts as its own issuer', () => {
assert.equal(x509.subject, 'CN=Haven');
assert.equal(x509.issuer, 'CN=Haven');
assert.equal(x509.verify(x509.publicKey), true);
assert.equal(x509.checkPrivateKey(crypto.createPrivateKey(keyPem)), true);
assert.ok(x509.ca);
assert.ok(new Date(x509.validTo).getTime() - new Date(x509.validFrom).getTime() > 3649 * 86400000);
assert.deepEqual(x509.keyUsage, ['1.3.6.1.5.5.7.3.1']);
});
test('SAN covers localhost, the loopback IP, the LAN IPv4s and the hostname', () => {
const san = x509.subjectAltName;
assert.match(san, /DNS:localhost/);
assert.match(san, /IP Address:127\.0\.0\.1/);
for (const n of localNames()) assert.ok(san.includes(/^\d/.test(n) ? `IP Address:${n}` : `DNS:${n}`), `${n} missing from ${san}`);
assert.equal(x509.checkHost('localhost'), 'localhost');
assert.equal(x509.checkIP('127.0.0.1'), '127.0.0.1');
});
test('custom names and CN land in the SAN, junk is dropped, and 2050+ expiry still parses', () => {
const r = generate({ cn: 'haven.example.org', names: ['10.0.0.5', 'bad name!', 'box'], days: 365 * 30 });
const c = new crypto.X509Certificate(r.cert);
assert.equal(c.subject, 'CN=haven.example.org');
assert.equal(c.subjectAltName, 'DNS:haven.example.org, IP Address:10.0.0.5, DNS:box');
assert.equal(new Date(c.validTo).getUTCFullYear() >= 2050, true);
assert.equal(c.verify(c.publicKey), true);
});
test('a real TLS handshake succeeds against the generated pair', async () => {
const server = https.createServer({ cert: certPem, key: keyPem }, (req, res) => res.end('ok'));
await new Promise(r => server.listen(0, '127.0.0.1', r));
const port = server.address().port;
const body = await new Promise((resolve, reject) => https.get({ host: '127.0.0.1', port, path: '/', ca: certPem, servername: 'localhost' }, res => { let d = ''; res.on('data', c => d += c); res.on('end', () => resolve(d)); }).on('error', reject));
server.close();
assert.equal(body, 'ok');
});