Initial curated CertScope source from fe2bbe3ca5c98bf64be26a09c6c58ee805a7f87e
This commit is contained in:
commit
7348faf6c0
65 files changed
+12509
No files matched your search
@@ -0,0 +1,72 @@
|
||||
import ipaddr from "ipaddr.js";
|
||||
import { domainToASCII } from "node:url";
|
||||
import { DNS_TIMEOUT_MS, resolveDnsFamily, type DnsFamilyResult } from "./dns";
|
||||
import { createDeadline } from "./deadline";
|
||||
|
||||
const HOST_RE = /^(?=.{1,253}$)(?!-)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;
|
||||
export { DNS_TIMEOUT_MS } from "./dns";
|
||||
|
||||
export class InspectionInputError extends Error {
|
||||
readonly kind = "input" as const;
|
||||
}
|
||||
|
||||
export class InspectionPolicyError extends Error {
|
||||
readonly kind = "policy" as const;
|
||||
}
|
||||
|
||||
export class InspectionResolutionError extends Error {
|
||||
readonly kind = "resolution" as const;
|
||||
}
|
||||
|
||||
export function normalizeHost(input: string) {
|
||||
if (typeof input !== "string") throw new InspectionInputError("Enter a public DNS hostname, not a URL or IP address.");
|
||||
const trimmed = input.trim();
|
||||
if (!trimmed || trimmed.length > 253) throw new InspectionInputError("Enter a public DNS hostname, not a URL or IP address.");
|
||||
const host = domainToASCII(trimmed.toLowerCase().replace(/\.$/, ""));
|
||||
if (!HOST_RE.test(host) || ipaddr.isValid(host)) {
|
||||
throw new InspectionInputError("Enter a public DNS hostname, not a URL or IP address.");
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
export function assertPublicAddress(address: string) {
|
||||
try {
|
||||
const range = ipaddr.parse(address).range();
|
||||
if (range !== "unicast") throw new InspectionPolicyError("Target resolved to non-public address space (" + range + ").");
|
||||
} catch (error) {
|
||||
if (error instanceof InspectionPolicyError) throw error;
|
||||
throw new InspectionPolicyError("Target resolved to an invalid address.");
|
||||
}
|
||||
}
|
||||
|
||||
export function validateResolvedAddresses(addresses: string[]) {
|
||||
if (!addresses.length) throw new InspectionResolutionError("Hostname did not resolve.");
|
||||
const normalized = [...new Set(addresses)];
|
||||
normalized.forEach(assertPublicAddress);
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export interface PublicResolution {
|
||||
addresses: string[];
|
||||
families: { ipv4: DnsFamilyResult; ipv6: DnsFamilyResult };
|
||||
_meta: { status: "ok" | "partial"; partial: boolean; errors: Array<{ probe: string; message: string }> };
|
||||
}
|
||||
|
||||
export async function resolvePublicHostDetailed(host: string, deadline = createDeadline(DNS_TIMEOUT_MS)): Promise<PublicResolution> {
|
||||
const normalized = normalizeHost(host);
|
||||
const [ipv4, ipv6] = await Promise.all([
|
||||
resolveDnsFamily(normalized, 4, deadline),
|
||||
resolveDnsFamily(normalized, 6, deadline),
|
||||
]);
|
||||
const addresses = validateResolvedAddresses([...ipv4.addresses, ...ipv6.addresses]);
|
||||
const errors = [ipv4.error, ipv6.error].filter((error): error is { probe: string; message: string } => Boolean(error));
|
||||
return {
|
||||
addresses,
|
||||
families: { ipv4, ipv6 },
|
||||
_meta: { status: errors.length ? "partial" : "ok", partial: errors.length > 0, errors },
|
||||
};
|
||||
}
|
||||
|
||||
export async function resolvePublicHost(host: string) {
|
||||
return (await resolvePublicHostDetailed(host)).addresses;
|
||||
}
|
||||
Reference in new issue
Block a user