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) ?? "" };
};