Files

92 lines
3.6 KiB
TypeScript

import { describe, expect, it } from "@jest/globals";
import { readFileSync } from "node:fs";
import { X509Certificate } from "node:crypto";
import { keyMatchesCertificate, parseCertificates, parsePrivateKey, splitCertificateBundle } from "../../../src/utils/pem.ts";
const fixture = (name: string) => readFileSync(new URL(`../../fixtures/docker-tls/${name}`, import.meta.url), "utf8");
const CA = fixture("ca.pem");
const CLIENT_CERT = fixture("client-cert.pem");
const CLIENT_KEY = fixture("client-key.pem");
const OTHER_KEY = fixture("other-key.pem");
const ENCRYPTED_KEY = fixture("encrypted-key.pem");
describe("splitCertificateBundle", () => {
it("returns one block for a single certificate", () => {
expect(splitCertificateBundle(CA)).toHaveLength(1);
});
it("returns every block of a bundle in order", () => {
const blocks = splitCertificateBundle(`${CA}\n${CLIENT_CERT}`);
expect(blocks).toHaveLength(2);
expect(blocks[0]).toBe(CA.trim());
expect(blocks[1]).toBe(CLIENT_CERT.trim());
});
it("ignores text outside the blocks", () => {
expect(splitCertificateBundle(`subject=CN=x\n${CA}\ntrailing`)).toHaveLength(1);
});
it("returns an empty array when there is no certificate block", () => {
expect(splitCertificateBundle("")).toEqual([]);
expect(splitCertificateBundle(CLIENT_KEY)).toEqual([]);
expect(splitCertificateBundle("-----BEGIN CERTIFICATE-----\nunterminated")).toEqual([]);
});
});
describe("parseCertificates", () => {
it("parses a single certificate", () => {
const [cert] = parseCertificates(CA);
expect(cert).toBeInstanceOf(X509Certificate);
expect(cert?.subject).toContain("CN=Checkmate Test CA");
});
it("parses every certificate in a bundle", () => {
const certs = parseCertificates(`${CA}\n${CLIENT_CERT}`);
expect(certs.map((c) => c.subject)).toEqual([expect.stringContaining("Checkmate Test CA"), expect.stringContaining("checkmate-test-client")]);
});
it("rejects input with no certificate block", () => {
expect(() => parseCertificates("")).toThrow("No certificate found");
expect(() => parseCertificates(CLIENT_KEY)).toThrow("No certificate found");
});
it("rejects a block whose body is not a certificate", () => {
expect(() => parseCertificates("-----BEGIN CERTIFICATE-----\nbm90IGEgY2VydA==\n-----END CERTIFICATE-----")).toThrow(
"Certificate is not valid PEM"
);
});
});
describe("parsePrivateKey", () => {
it("parses an unencrypted PKCS#8 key", () => {
expect(parsePrivateKey(CLIENT_KEY).type).toBe("private");
});
it("rejects an encrypted PKCS#8 key with a clear message", () => {
expect(() => parsePrivateKey(ENCRYPTED_KEY)).toThrow("Encrypted private keys are not supported");
});
it("rejects a legacy encrypted key by its Proc-Type header", () => {
const legacy = "-----BEGIN RSA PRIVATE KEY-----\nProc-Type: 4,ENCRYPTED\nDEK-Info: AES-128-CBC,00\n\nAAAA\n-----END RSA PRIVATE KEY-----";
expect(() => parsePrivateKey(legacy)).toThrow("Encrypted private keys are not supported");
});
it("rejects input that is not a key", () => {
expect(() => parsePrivateKey("")).toThrow("Private key is not valid PEM");
expect(() => parsePrivateKey(CA)).toThrow("Private key is not valid PEM");
});
});
describe("keyMatchesCertificate", () => {
it("is true for the key the certificate was issued to", () => {
const [cert] = parseCertificates(CLIENT_CERT);
expect(keyMatchesCertificate(parsePrivateKey(CLIENT_KEY), cert!)).toBe(true);
});
it("is false for an unrelated key", () => {
const [cert] = parseCertificates(CLIENT_CERT);
expect(keyMatchesCertificate(parsePrivateKey(OTHER_KEY), cert!)).toBe(false);
});
});