const ASSERTION_DOMAIN = "nekonest-cloud/relay-mtls-identity/v1"; const MAX_ASSERTION_SKEW_SECONDS = 30; export type TrustedRelayMtlsIdentity = { nodeId: string; spiffeId: string; certificateFingerprintSha256: string; }; export const AUTHENTICATE_RELAY_NODE_IDENTITY_SQL = ` UPDATE relay_node_credentials SET last_used_at = ?1 WHERE node_id = ?2 AND mtls_spiffe_id = ?3 AND certificate_fingerprint_sha256 = ?4 AND status = 'active' AND issued_at <= ?1 AND revoked_at IS NULL AND (expires_at IS NULL OR expires_at > ?1) AND EXISTS ( SELECT 1 FROM relay_nodes WHERE id = ?2 AND status IN ('active', 'draining') )`; export class RelayMtlsIdentityError extends Error { readonly code: "relay_mtls_unavailable" | "relay_mtls_identity_invalid"; readonly status: 401 | 503; constructor( code: "relay_mtls_unavailable" | "relay_mtls_identity_invalid", message: string, status: 401 | 503, ) { super(message); this.code = code; this.status = status; } } function assertionTranscript(input: { method: string; pathname: string; nodeId: string; spiffeId: string; certificateFingerprintSha256: string; timestampSeconds: number; }): Uint8Array { return new TextEncoder().encode([ ASSERTION_DOMAIN, input.method.toUpperCase(), input.pathname, input.nodeId, input.spiffeId, input.certificateFingerprintSha256, String(input.timestampSeconds), ].join("\0")); } function bytesToHex(bytes: Uint8Array): string { return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join(""); } function equalHex(left: string, right: string): boolean { let difference = left.length ^ right.length; const length = Math.max(left.length, right.length); for (let index = 0; index < length; index += 1) { difference |= (left.charCodeAt(index) || 0) ^ (right.charCodeAt(index) || 0); } return difference === 0; } export async function createTrustedRelayMtlsAssertion(input: { assertionSecret: string; method: string; pathname: string; nodeId: string; spiffeId: string; certificateFingerprintSha256: string; timestampSeconds: number; }): Promise { if (input.assertionSecret.length < 32) throw new TypeError("relay_mtls_assertion_secret_too_short"); const key = await crypto.subtle.importKey( "raw", new TextEncoder().encode(input.assertionSecret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"], ); const signature = await crypto.subtle.sign( "HMAC", key, Uint8Array.from(assertionTranscript(input)).buffer, ); return bytesToHex(new Uint8Array(signature)); } /** * Workers cannot inspect a client certificate directly. The production mTLS * terminator must strip all x-neko-mtls-* headers from untrusted traffic, * verify the certificate, and inject this short-lived HMAC assertion. */ export async function verifyTrustedRelayMtlsIdentity(input: { request: Request; assertionSecret: string; nowMs?: number; }): Promise { if (input.assertionSecret.length < 32) { throw new RelayMtlsIdentityError( "relay_mtls_unavailable", "Relay mTLS ingress assertion is not configured", 503, ); } const headers = input.request.headers; const nodeId = headers.get("x-neko-relay-node-id")?.trim() ?? ""; const spiffeId = headers.get("x-neko-mtls-spiffe-id")?.trim() ?? ""; const certificateFingerprintSha256 = headers.get("x-neko-mtls-cert-sha256")?.trim().toLowerCase() ?? ""; const verified = headers.get("x-neko-mtls-verified")?.trim() ?? ""; const timestampRaw = headers.get("x-neko-mtls-timestamp")?.trim() ?? ""; const assertion = headers.get("x-neko-mtls-assertion")?.trim().toLowerCase() ?? ""; const timestampSeconds = Number(timestampRaw); if ( verified !== "SUCCESS" || !/^node_[A-Za-z0-9._:-]{1,96}$/u.test(nodeId) || !/^spiffe:\/\/[A-Za-z0-9._~!$&'()*+,;=:@%/-]{3,240}$/u.test(spiffeId) || !/^[0-9a-f]{64}$/u.test(certificateFingerprintSha256) || !/^[0-9a-f]{64}$/u.test(assertion) || !Number.isSafeInteger(timestampSeconds) ) { throw new RelayMtlsIdentityError( "relay_mtls_identity_invalid", "Relay mTLS identity is invalid", 401, ); } const nowSeconds = Math.floor((input.nowMs ?? Date.now()) / 1_000); if (Math.abs(nowSeconds - timestampSeconds) > MAX_ASSERTION_SKEW_SECONDS) { throw new RelayMtlsIdentityError( "relay_mtls_identity_invalid", "Relay mTLS assertion has expired", 401, ); } const expected = await createTrustedRelayMtlsAssertion({ assertionSecret: input.assertionSecret, method: input.request.method, pathname: new URL(input.request.url).pathname, nodeId, spiffeId, certificateFingerprintSha256, timestampSeconds, }); if (!equalHex(assertion, expected)) { throw new RelayMtlsIdentityError( "relay_mtls_identity_invalid", "Relay mTLS assertion is invalid", 401, ); } return { nodeId, spiffeId, certificateFingerprintSha256 }; }