/** * T9 — relay-node ↔ control-plane trust boundary (the node analog of INV3). `nodeId` is derived * from the VERIFIED relay-node mTLS client-cert subject — NEVER from a body/query/header field. * A request that carries a `nodeId` payload has it IGNORED for identity. Non-mutually-authenticated * connections throw 401. * * OQ5 (open): who ISSUES/ROTATES the node service certs + the SVID format the CP pins * (`nodeMtlsTrustBundlePath`) is a P5/P1 boundary. Here we consume an already-verified peer cert. */ export interface NodeIdentity { readonly nodeId: string } export class NodeAuthError extends Error { readonly status = 401 constructor(message: string) { super(message) } } /** The shape we need from a verified TLS peer certificate (subset of Node's PeerCertificate). */ export interface VerifiedPeerCert { readonly authorized: boolean // TLS stack verified the client cert against the trust bundle readonly subjectCommonName: string | null // SPIFFE-style node id in the cert subject CN / SAN URI } /** Pure derivation — the request wrapper below feeds it the extracted peer cert. */ export function deriveNodeIdentity(cert: VerifiedPeerCert | null): NodeIdentity { if (cert === null || !cert.authorized) { throw new NodeAuthError('relay-node connection is not mutually authenticated') } const cn = cert.subjectCommonName if (cn === null || cn.trim() === '') { throw new NodeAuthError('relay-node client cert has no subject identity') } return { nodeId: cn } } /** Minimal request shape carrying a TLS socket (Fastify/Node). */ export interface RequestWithTls { readonly socket: { authorized?: boolean getPeerCertificate?: () => { subject?: { CN?: string } } | undefined } } /** * Extract the verified node identity from a live request's TLS session. INTEGRATION SEAM: the * exact SAN/URI SVID parsing is finalized with OQ5; here we read authorized + subject CN. */ export function nodeIdentityFromRequest(req: RequestWithTls): NodeIdentity { const authorized = req.socket.authorized === true const peer = req.socket.getPeerCertificate?.() const cn = peer?.subject?.CN ?? null return deriveNodeIdentity({ authorized, subjectCommonName: cn }) }