all repos — red-anatomia @ 43cc40161c75c7c9bb5bef2c1ce2d551b7f24484

red-anatomia

src/collectors/tls.ts (view raw)

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
import tls from 'node:tls';
import type { Observations, CertificadoInfo } from '../core/types.js';

export async function coletarTls(obs: Observations): Promise<void> {
  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<void>((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;
}