119 lines
5.2 KiB
TypeScript
119 lines
5.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { EventEmitter } from "node:events";
|
|
import tls from "node:tls";
|
|
import { certificateDaysRemaining } from "./tls";
|
|
import { collectTls, type TlsConnector } from "./tls";
|
|
|
|
describe("TLS certificate date handling", () => {
|
|
it("returns whole days remaining from a supplied clock", () => {
|
|
const now = Date.parse("2026-01-01T12:00:00.000Z");
|
|
expect(certificateDaysRemaining("Jan 11 12:00:00 2026 GMT", now)).toBe(10);
|
|
expect(certificateDaysRemaining("Dec 31 12:00:00 2025 GMT", now)).toBe(-1);
|
|
});
|
|
|
|
it("does not turn an invalid certificate date into a score deduction", () => {
|
|
expect(certificateDaysRemaining("not-a-certificate-date", Date.now())).toBeNull();
|
|
expect(certificateDaysRemaining(null, Date.now())).toBeNull();
|
|
});
|
|
|
|
it("tries the next validated address after a failed TLS connection", async () => {
|
|
const attempted: string[] = [];
|
|
const connect: TlsConnector = ((options) => {
|
|
const address = String(options.host);
|
|
attempted.push(address);
|
|
const socket = new EventEmitter() as EventEmitter & {
|
|
authorized: boolean;
|
|
authorizationError: null;
|
|
getPeerCertificate: () => Record<string, unknown>;
|
|
getProtocol: () => string;
|
|
destroy: () => void;
|
|
};
|
|
socket.destroy = () => undefined;
|
|
if (address === "1.1.1.1") {
|
|
queueMicrotask(() => socket.emit("error", Object.assign(new Error("refused"), { code: "ECONNREFUSED" })));
|
|
} else {
|
|
socket.authorized = true;
|
|
socket.authorizationError = null;
|
|
socket.getProtocol = () => "TLSv1.3";
|
|
socket.getPeerCertificate = () => ({
|
|
subject: { CN: "example.com" },
|
|
issuer: { CN: "Example CA" },
|
|
subjectaltname: "DNS:example.com",
|
|
valid_from: "Jan 01 00:00:00 2026 GMT",
|
|
valid_to: "Jan 01 00:00:00 2027 GMT",
|
|
fingerprint256: "AA:BB",
|
|
});
|
|
queueMicrotask(() => socket.emit("secureConnect"));
|
|
}
|
|
return socket as unknown as ReturnType<typeof tls.connect>;
|
|
}) as TlsConnector;
|
|
|
|
const result = await collectTls("example.com", ["1.1.1.1", "2.2.2.2"], { connect, now: Date.parse("2026-06-01T00:00:00Z") });
|
|
expect(result).toMatchObject({ address: "2.2.2.2", protocol: "TLSv1.3", hostnameValid: true, authorized: true });
|
|
expect(attempted).toEqual(["1.1.1.1", "2.2.2.2"]);
|
|
expect(result.attempts).toEqual([{ address: "1.1.1.1", error: "TLS probe failed (ECONNREFUSED)." }]);
|
|
});
|
|
|
|
it("reports certificates that are not yet valid", async () => {
|
|
const connect: TlsConnector = (() => {
|
|
const socket = new EventEmitter() as EventEmitter & {
|
|
authorized: boolean;
|
|
authorizationError: null;
|
|
getPeerCertificate: () => Record<string, unknown>;
|
|
getProtocol: () => string;
|
|
destroy: () => void;
|
|
};
|
|
socket.authorized = true;
|
|
socket.authorizationError = null;
|
|
socket.getProtocol = () => "TLSv1.3";
|
|
socket.getPeerCertificate = () => ({
|
|
subjectaltname: "DNS:example.com",
|
|
valid_from: "Jul 01 00:00:00 2026 GMT",
|
|
valid_to: "Jan 01 00:00:00 2027 GMT",
|
|
});
|
|
socket.destroy = () => undefined;
|
|
queueMicrotask(() => socket.emit("secureConnect"));
|
|
return socket as unknown as ReturnType<typeof tls.connect>;
|
|
}) as TlsConnector;
|
|
const result = await collectTls("example.com", ["2.2.2.2"], { connect, now: Date.parse("2026-06-01T00:00:00Z") });
|
|
expect(result.notYetValid).toBe(true);
|
|
expect(result.daysRemaining).toBe(214);
|
|
});
|
|
|
|
it("marks malformed or inverted certificate validity dates", async () => {
|
|
const connect: TlsConnector = (() => {
|
|
const socket = new EventEmitter() as EventEmitter & {
|
|
authorized: boolean;
|
|
authorizationError: null;
|
|
getPeerCertificate: () => Record<string, unknown>;
|
|
getProtocol: () => string;
|
|
destroy: () => void;
|
|
};
|
|
socket.authorized = true;
|
|
socket.authorizationError = null;
|
|
socket.getProtocol = () => "TLSv1.3";
|
|
socket.getPeerCertificate = () => ({
|
|
subjectaltname: "DNS:example.com",
|
|
valid_from: "Jan 02 00:00:00 2026 GMT",
|
|
valid_to: "Jan 01 00:00:00 2026 GMT",
|
|
});
|
|
socket.destroy = () => undefined;
|
|
queueMicrotask(() => socket.emit("secureConnect"));
|
|
return socket as unknown as ReturnType<typeof tls.connect>;
|
|
}) as TlsConnector;
|
|
const result = await collectTls("example.com", ["2.2.2.2"], { connect, now: Date.parse("2026-01-01T00:00:00Z") });
|
|
expect(result).toMatchObject({ validityDatesValid: false, daysRemaining: 0 });
|
|
});
|
|
|
|
it("marks an exhausted address set as a failed collector", async () => {
|
|
const connect: TlsConnector = (() => {
|
|
const socket = new EventEmitter() as EventEmitter & { destroy: () => void };
|
|
socket.destroy = () => undefined;
|
|
queueMicrotask(() => socket.emit("error", Object.assign(new Error("unreachable"), { code: "ETIMEDOUT" })));
|
|
return socket as unknown as ReturnType<typeof tls.connect>;
|
|
}) as TlsConnector;
|
|
const result = await collectTls("example.com", ["2.2.2.2"], { connect, deadline: Date.now() + 1_000 });
|
|
expect(result._meta).toMatchObject({ status: "failed" });
|
|
});
|
|
});
|