Files
bun.sh/test/js/node/tls/node-tls-cert.test.ts
2024-09-03 21:32:52 -07:00

442 lines
11 KiB
TypeScript

import { expect, it } from "bun:test";
import { readFileSync } from "fs";
import type { AddressInfo } from "node:net";
import type { Server, TLSSocket } from "node:tls";
import { join } from "path";
import tls from "tls";
const client = {
key: readFileSync(join(import.meta.dir, "fixtures", "ec10-key.pem"), "utf8"),
cert: readFileSync(join(import.meta.dir, "fixtures", "ec10-cert.pem"), "utf8"),
ca: readFileSync(join(import.meta.dir, "fixtures", "ca5-cert.pem"), "utf8"),
};
const server = {
key: readFileSync(join(import.meta.dir, "fixtures", "agent10-key.pem"), "utf8"),
cert: readFileSync(join(import.meta.dir, "fixtures", "agent10-cert.pem"), "utf8"),
ca: readFileSync(join(import.meta.dir, "fixtures", "ca2-cert.pem"), "utf8"),
};
function split(file: any, into: any) {
const certs = /([^]*END CERTIFICATE-----\r?\n)(-----BEGIN[^]*)/.exec(file) as RegExpExecArray;
into.single = certs[1];
into.subca = certs[2];
}
// Split out the single end-entity cert and the subordinate CA for later use.
split(client.cert, client);
split(server.cert, server);
// The certificates aren't for "localhost", so override the identity check.
function checkServerIdentity(hostname: string, cert: any) {
expect(hostname).toBe("localhost");
expect(cert.subject.CN).toBe("agent10.example.com");
}
function connect(options: any) {
let { promise, resolve, reject } = Promise.withResolvers();
const server: any = {};
const client: any = {};
const pair = { server, client };
function cleanup() {
if (server.conn) server.conn.end();
if (server.server) server.server.close();
if (client.conn) client.conn.end();
}
let resolved = false;
function resolveOrReject() {
if (resolved) return;
resolved = true;
cleanup();
const err = pair.client.err || pair.server.err;
if (server.conn && client.conn) {
if (err) {
reject(err);
}
resolve(pair);
} else {
reject(err || new Error("Unable to secure connect"));
}
}
try {
server.server = tls
.createServer(options.server, function (conn) {
server.conn = conn;
conn.pipe(conn);
if (client.conn) {
resolveOrReject();
}
})
.on("tlsClientError", (err: any) => {
server.err = err;
resolveOrReject();
})
.on("error", err => {
server.err = err;
resolveOrReject();
})
.listen(0, function () {
const optClient = { ...options.client, port: server.server.address().port, host: "127.0.0.1" };
try {
const conn = tls
.connect(optClient, () => {
client.conn = conn;
if (server.conn) {
resolveOrReject();
}
})
.on("error", function (err) {
client.err = err;
resolveOrReject();
})
.on("close", function () {
resolveOrReject();
});
} catch (err) {
client.err = err;
// The server won't get a connection, we are done.
resolveOrReject();
}
});
} catch (err) {
// Invalid options can throw, report the error.
server.err = err;
resolveOrReject();
}
return promise;
}
it("complete cert chains sent to peer.", async () => {
await connect({
client: {
key: client.key,
cert: client.cert,
ca: server.ca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca,
requestCert: true,
},
});
});
it("complete cert chains sent to peer, but without requesting client's cert.", async () => {
await connect({
client: {
ca: server.ca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca,
},
});
});
// TODO: this requires maxVersion/minVersion
it.todo("Request cert from TLS1.2 client that doesn't have one.", async () => {
try {
await connect({
client: {
maxVersion: "TLSv1.2",
ca: server.ca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca,
requestCert: true,
},
});
expect.unreachable();
} catch (err: any) {
expect(err.code).toBe("ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE");
}
});
it("Typical configuration error, incomplete cert chains sent, we have to know the peer's subordinate CAs in order to verify the peer.", async () => {
await connect({
client: {
key: client.key,
cert: client.single,
ca: [server.ca, server.subca],
checkServerIdentity,
},
server: {
key: server.key,
cert: server.single,
ca: [client.ca, client.subca],
requestCert: true,
},
});
});
it("Typical configuration error, incomplete cert chains sent, we have to know the peer's subordinate CAs in order to verify the peer. But using multi-PEM", async () => {
await connect({
client: {
key: client.key,
cert: client.single,
ca: server.ca + "\n" + server.subca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.single,
ca: client.ca + "\n" + client.subca,
requestCert: true,
},
});
});
it("Typical configuration error, incomplete cert chains sent, we have to know the peer's subordinate CAs in order to verify the peer. But using multi-PEM in an array", async () => {
await connect({
client: {
key: client.key,
cert: client.single,
ca: [server.ca + "\n" + server.subca],
checkServerIdentity,
},
server: {
key: server.key,
cert: server.single,
ca: [client.ca + "\n" + client.subca],
requestCert: true,
},
});
});
it("Fail to complete server's chain", async () => {
try {
await connect({
client: {
ca: server.ca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.single,
},
});
expect.unreachable();
} catch (err: any) {
expect(err.code).toBe("UNABLE_TO_VERIFY_LEAF_SIGNATURE");
}
});
it("Fail to complete client's chain.", async () => {
try {
await connect({
client: {
key: client.key,
cert: client.single,
ca: server.ca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca,
requestCert: true,
},
});
expect.unreachable();
} catch (err: any) {
expect(err.code).toBe("UNABLE_TO_VERIFY_LEAF_SIGNATURE");
}
});
it("Fail to find CA for server.", async () => {
try {
await connect({
client: {
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
},
});
expect.unreachable();
} catch (err: any) {
expect(err.code).toBe("UNABLE_TO_GET_ISSUER_CERT_LOCALLY");
}
});
it("Server sent their CA, but CA cannot be trusted if it is not locally known.", async () => {
try {
await connect({
client: {
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert + "\n" + server.ca,
},
});
expect.unreachable();
} catch (err: any) {
expect(err.code).toBe("SELF_SIGNED_CERT_IN_CHAIN");
}
});
it("Server sent their CA, wrongly, but its OK since we know the CA locally.", async () => {
await connect({
client: {
checkServerIdentity,
ca: server.ca,
},
server: {
key: server.key,
cert: server.cert + "\n" + server.ca,
},
});
});
it.todo('Confirm client support for "BEGIN TRUSTED CERTIFICATE".', async () => {
await connect({
client: {
key: client.key,
cert: client.cert,
ca: server.ca.replace(/CERTIFICATE/g, "TRUSTED CERTIFICATE"),
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca,
requestCert: true,
},
});
});
it.todo('Confirm server support for "BEGIN TRUSTED CERTIFICATE".', async () => {
await connect({
client: {
key: client.key,
cert: client.cert,
ca: server.ca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca.replace(/CERTIFICATE/g, "TRUSTED CERTIFICATE"),
requestCert: true,
},
});
});
it('Confirm client support for "BEGIN X509 CERTIFICATE".', async () => {
await connect({
client: {
key: client.key,
cert: client.cert,
ca: server.ca.replace(/CERTIFICATE/g, "X509 CERTIFICATE"),
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca,
requestCert: true,
},
});
});
it('Confirm server support for "BEGIN X509 CERTIFICATE".', async () => {
await connect({
client: {
key: client.key,
cert: client.cert,
ca: server.ca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca.replace(/CERTIFICATE/g, "X509 CERTIFICATE"),
requestCert: true,
},
});
});
it("Check getPeerCertificate can properly handle '\\0' for fix CVE-2009-2408.", async () => {
let server: Server | null = null;
let socket: TLSSocket | null = null;
try {
const { promise, resolve, reject } = Promise.withResolvers();
server = tls
.createServer({
key: readFileSync(join(import.meta.dir, "fixtures", "0-dns-key.pem")),
cert: readFileSync(join(import.meta.dir, "fixtures", "0-dns-cert.pem")),
})
.on("error", reject)
.listen(0, () => {
const address = server?.address() as AddressInfo;
socket = tls
.connect(
{
host: address.address,
port: address.port,
rejectUnauthorized: false,
},
() => {
const cert = socket?.getPeerCertificate();
resolve(cert?.subjectaltname);
},
)
.on("error", reject);
});
const subjectaltname = await promise;
expect(subjectaltname).toBe(
'DNS:"good.example.org\\u0000.evil.example.com", DNS:just-another.example.com, IP Address:8.8.8.8, IP Address:8.8.4.4, DNS:last.example.com',
);
} finally {
//@ts-ignore
socket?.end();
server?.close();
}
});
it("should not accept untrusted certificates", async () => {
const { promise, resolve, reject } = Promise.withResolvers();
let server: Server | null = null;
let socket: TLSSocket | null = null;
try {
server = tls
.createServer({
key: readFileSync(join(import.meta.dir, "..", "http", "fixtures", "openssl.key")),
cert: readFileSync(join(import.meta.dir, "..", "http", "fixtures", "openssl.crt")),
passphrase: "123123123",
})
.on("error", reject)
.listen(0, () => {
const address = server?.address() as AddressInfo;
const options = {
port: address.port,
rejectUnauthorized: true,
};
socket = tls
.connect(options, () => {
reject(new Error("should not connect"));
})
.on("error", resolve);
});
const err = await promise;
expect(err.code).toBe("UNABLE_TO_VERIFY_LEAF_SIGNATURE");
expect(err.message).toBe("unable to verify the first certificate");
} finally {
//@ts-ignore
socket?.end();
server?.close();
}
});