core/adapters/site.adapter.ts

core/adapters/site.adapter.ts is a file in Bane's Lab Deploy. 60 lines of code and 17 definitions.

import {
    DANE_END_ENTITY,
    DANE_PUBLIC_KEY,
    DANE_SHA256,
    PROBE_TIMEOUT_MS,
    TLSA_PORT_MARK,
    TLSA_PROTOCOL,
} from "#configuration/constants/deployment.constants";
import type { DaneKeys, ProbeResponse, ServedKey } from "#types/deployment.types";
import { X509Certificate, createHash } from "node:crypto";
import { tlsMissing, tlsTimeout } from "#configuration/strings/deployment.strings";
import { Buffer } from "node:buffer";
import { TLSSocket } from "node:tls";
import { request } from "node:https";
import { resolveTlsa } from "node:dns/promises";

const BODY_METHOD = "GET";
const HEAD_METHOD = "HEAD";
const TYPE_HEADER = "content-type";
const DIGEST = "sha256";
const HEX = "hex";

const keyDigestOf = function keyDigestOf(raw: Buffer): string {
    const spki = new X509Certificate(raw).publicKey.export({ format: "der", type: "spki" });
    return createHash(DIGEST).update(spki).digest(HEX);
};

const servedKey = async function servedKey(site: string): Promise<ServedKey> {
    return new Promise((resolve, reject) => {
        const call = request(site, { method: HEAD_METHOD, timeout: PROBE_TIMEOUT_MS }, (response) => {
            const { socket } = response;
            response.resume();
            if (!(socket instanceof TLSSocket) || socket.remotePort === undefined) {
                reject(new Error(tlsMissing(site)));
                return;
            }
            resolve({ digest: keyDigestOf(socket.getPeerCertificate().raw), port: socket.remotePort });
        });
        call.on("timeout", () => {
            call.destroy(new Error(tlsTimeout(site)));
        });
        call.on("error", reject);
        call.end();
    });
};

export const readDane = async function readDane(site: string): Promise<DaneKeys> {
    const served = await servedKey(site);
    const name = TLSA_PORT_MARK + String(served.port) + TLSA_PROTOCOL + new URL(site).hostname;
    const published = (await resolveTlsa(name))
        .filter(
            (record) =>
                record.certUsage === DANE_END_ENTITY &&
                record.selector === DANE_PUBLIC_KEY &&
                record.match === DANE_SHA256,
        )
        .map((record) => Buffer.from(record.data).toString(HEX));
    return { name, published, served: served.digest };
};

export const fetchProbe = async function fetchProbe(url: string, method: string): Promise<ProbeResponse> {
    const response = await fetch(url, { method, redirect: "manual", signal: AbortSignal.timeout(PROBE_TIMEOUT_MS) });
    const body = method === BODY_METHOD ? await response.text() : "";
    return { body, status: response.status, type: response.headers.get(TYPE_HEADER) ?? "" };
};