import tls from 'node:tls'; import type { Observations, CertificadoInfo } from '../core/types.js'; export async function coletarTls(obs: Observations): Promise { let url: URL; try { url = new URL(obs.urlFinal || obs.urlSolicitada); } catch { return; } if (url.protocol !== 'https:') { obs.protocolo = { http2: false, http3Anunciado: false }; return; } const host = url.hostname; const porta = url.port ? Number(url.port) : 443; await new Promise((resolver) => { // lê o certificado apresentado, mesmo inválido, só para relatá-lo; nenhum corpo é consumido aqui const socket = tls.connect( { host, port: porta, servername: host, ALPNProtocols: ['h2', 'http/1.1'], rejectUnauthorized: false, timeout: 8000, }, () => { try { const cert = socket.getPeerCertificate(false); const cn = txt(cert?.subject?.CN); const issuerCn = txt(cert?.issuer?.CN); const issuerO = txt(cert?.issuer?.O); const info: CertificadoInfo = { emitidoPara: cn, emitidoPor: issuerCn ? `${issuerO ? issuerO + ' — ' : ''}${issuerCn}` : issuerO, validoDe: cert?.valid_from, validoAte: cert?.valid_to, san: cert?.subjectaltname ?.split(',') .map((s) => s.trim().replace(/^DNS:/, '')) .filter(Boolean), protocoloTls: socket.getProtocol() ?? undefined, }; obs.certificado = info; obs.protocolo = { http2: socket.alpnProtocol === 'h2', http3Anunciado: obs.protocolo?.http3Anunciado ?? false, altSvc: obs.protocolo?.altSvc, }; } catch (erro) { obs.avisos.push(`Falha ao ler o certificado: ${(erro as Error).message}`); } finally { socket.end(); resolver(); } }, ); socket.on('error', (erro) => { obs.avisos.push(`Falha na conexão TLS: ${erro.message}`); resolver(); }); socket.on('timeout', () => { socket.destroy(); resolver(); }); }); } function txt(v: string | string[] | undefined): string | undefined { if (v == null) return undefined; return Array.isArray(v) ? v.join(', ') : v; }