{"id":70,"date":"2026-06-12T16:59:40","date_gmt":"2026-06-12T16:59:40","guid":{"rendered":"https:\/\/shattered.io\/pt\/2026\/06\/12\/mtls-node-js-tls-13\/"},"modified":"2026-06-12T17:01:02","modified_gmt":"2026-06-12T17:01:02","slug":"mtls-node-js-tls-13","status":"publish","type":"post","link":"https:\/\/shattered.io\/pt\/2026\/06\/12\/mtls-node-js-tls-13\/","title":{"rendered":"mTLS em Node.js: TLS 1.3 em 12 Passos [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">O modelo de seguran\u00e7a &#8220;confia na rede interna&#8221; morreu. Em 2026, qualquer servi\u00e7o que aceite liga\u00e7\u00f5es sem provar quem est\u00e1 do outro lado \u00e9 um alvo. O <strong>mTLS em Node.js<\/strong> resolve isto ao exigir que cliente e servidor apresentem certificados v\u00e1lidos antes de trocar um \u00fanico byte de dados aplicacionais. N\u00e3o \u00e9 um cadeado decorativo, \u00e9 autentica\u00e7\u00e3o criptogr\u00e1fica nas duas dire\u00e7\u00f5es.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Este tutorial leva-te do zero a um sistema mTLS completo e funcional, com TLS 1.3, em 12 passos. Vais criar a tua pr\u00f3pria Autoridade Certificadora, emitir certificados de servidor e de cliente, construir um servidor Node.js que recusa qualquer cliente n\u00e3o autenticado, escrever um cliente que apresenta o seu certificado, e mapear a identidade do certificado para permiss\u00f5es reais da aplica\u00e7\u00e3o. No fim tens um projeto que podes adaptar para APIs internas, comunica\u00e7\u00e3o entre microsservi\u00e7os ou acesso a sistemas sens\u00edveis.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Tempo estimado: 45 a 60 minutos. Todo o c\u00f3digo usa apenas m\u00f3dulos nativos do Node.js (<code>node:tls<\/code>, <code>node:https<\/code>, <code>node:crypto<\/code>) e a ferramenta <code>openssl<\/code>. Sem depend\u00eancias externas pesadas, sem frameworks criptogr\u00e1ficos opacos.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"mtls-em-node-js-porque-e-o-padrao-zero-trust-em-2026\">mTLS em Node.js: porque \u00e9 o padr\u00e3o zero-trust em 2026<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">O TLS normal (o que protege o HTTPS que usas todos os dias) autentica apenas o servidor. O teu navegador verifica que <code>banco.pt<\/code> \u00e9 mesmo o banco, mas o banco n\u00e3o tem como saber, ao n\u00edvel do TLS, quem \u00e9s tu. A autentica\u00e7\u00e3o do utilizador acontece depois, com palavra-passe ou token, j\u00e1 dentro da sess\u00e3o cifrada.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">O TLS m\u00fatuo, ou mTLS, fecha esse buraco. Ambos os lados apresentam um certificado e ambos verificam o certificado do outro contra uma Autoridade Certificadora (CA) de confian\u00e7a. Se o cliente n\u00e3o tiver um certificado v\u00e1lido emitido pela CA esperada, a liga\u00e7\u00e3o cai durante o handshake. Nenhum pedido chega sequer ao c\u00f3digo da aplica\u00e7\u00e3o. Isto torna o mTLS a base pr\u00e1tica da arquitetura zero-trust, onde nada \u00e9 confi\u00e1vel s\u00f3 por estar &#8220;dentro&#8221; da rede.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Em 2026, tr\u00eas for\u00e7as empurram o mTLS para a corrente principal. Primeiro, a velocidade dos ataques: quando um intruso entra na rede, mover-se lateralmente entre servi\u00e7os que confiam cegamente uns nos outros \u00e9 trivial, e o mTLS bloqueia esse movimento. Segundo, as malhas de servi\u00e7o (service meshes) como o Istio e o Linkerd usam mTLS por omiss\u00e3o, normalizando o padr\u00e3o. Terceiro, regula\u00e7\u00e3o como a NIS2 pressiona organiza\u00e7\u00f5es em Portugal e na UE a demonstrar controlo de acesso forte entre sistemas. O mTLS d\u00e1 uma resposta audit\u00e1vel.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">O Node.js \u00e9 uma escolha natural para implementar mTLS porque o suporte est\u00e1 embutido. O m\u00f3dulo <code>node:tls<\/code> exp\u00f5e diretamente as op\u00e7\u00f5es que precisas: <code>requestCert<\/code>, <code>rejectUnauthorized<\/code>, <code>ca<\/code>, <code>minVersion<\/code>. N\u00e3o precisas de um proxy reverso a fazer o trabalho, embora possas combinar os dois. Segundo a <a href=\"https:\/\/nodejs.org\/api\/tls.html\" target=\"_blank\" rel=\"noopener\">documenta\u00e7\u00e3o oficial do m\u00f3dulo TLS<\/a>, estas primitivas dependem do OpenSSL inclu\u00eddo na build do Node, por isso o comportamento exato dos algoritmos segue as capacidades do OpenSSL da tua plataforma.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"o-que-vais-construir-arquitetura-do-projeto-mtls\">O que vais construir: arquitetura do projeto mTLS<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Antes de escrever c\u00f3digo, conv\u00e9m ver o mapa. O projeto tem quatro pe\u00e7as que trabalham juntas. A CA raiz \u00e9 a \u00e2ncora de confian\u00e7a: assina os outros certificados e ambos os lados confiam nela. O certificado do servidor identifica o servidor. O certificado do cliente identifica cada cliente autorizado. O servidor e o cliente Node.js trocam e verificam tudo isto durante o handshake TLS 1.3.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Componente<\/th><th>Ficheiro<\/th><th>Fun\u00e7\u00e3o<\/th><th>Quem confia nele<\/th><\/tr><\/thead><tbody><tr><td>CA raiz<\/td><td><code>ca-cert.pem<\/code><\/td><td>Assina e valida todos os certificados<\/td><td>Servidor e cliente<\/td><\/tr><tr><td>Chave da CA<\/td><td><code>ca-key.pem<\/code><\/td><td>Assina novos certificados (segredo m\u00e1ximo)<\/td><td>Apenas o operador<\/td><\/tr><tr><td>Certificado do servidor<\/td><td><code>server-cert.pem<\/code><\/td><td>Prova a identidade do servidor<\/td><td>Cliente<\/td><\/tr><tr><td>Certificado do cliente<\/td><td><code>client-cert.pem<\/code><\/td><td>Prova a identidade do cliente<\/td><td>Servidor<\/td><\/tr><tr><td>Servidor Node<\/td><td><code>server.js<\/code><\/td><td>Exige e valida o certificado do cliente<\/td><td>(servi\u00e7o)<\/td><\/tr><tr><td>Cliente Node<\/td><td><code>client.js<\/code><\/td><td>Apresenta o seu certificado ao servidor<\/td><td>(servi\u00e7o)<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">O fluxo \u00e9 direto. O cliente liga-se ao servidor e envia o seu certificado. O servidor verifica esse certificado contra a CA raiz. Em paralelo, o cliente verifica o certificado do servidor contra a mesma CA. Se ambas as verifica\u00e7\u00f5es passarem, o t\u00fanel TLS 1.3 abre e a aplica\u00e7\u00e3o corre. Se qualquer uma falhar, a liga\u00e7\u00e3o termina antes de qualquer pedido HTTP. \u00c9 exatamente este comportamento &#8220;falha fechada&#8221; que torna o mTLS em Node.js t\u00e3o robusto.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"pre-requisitos-e-versoes-necessarias\">Pr\u00e9-requisitos e vers\u00f5es necess\u00e1rias<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Este tutorial assume uma base de runtime atual e suportada. As linhas LTS do Node.js receberam corre\u00e7\u00f5es de seguran\u00e7a relevantes para TLS na ronda de janeiro de 2026, incluindo melhorias no parsing de certificados, por isso usa uma vers\u00e3o LTS atualizada e n\u00e3o uma build antiga esquecida no port\u00e1til. Consulta o <a href=\"https:\/\/nodejs.org\/en\/about\/previous-releases\" target=\"_blank\" rel=\"noopener\">calend\u00e1rio oficial de vers\u00f5es do Node.js<\/a> para confirmar qual a LTS ativa.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Ferramenta<\/th><th>Vers\u00e3o recomendada<\/th><th>Como verificar<\/th><th>Notas<\/th><\/tr><\/thead><tbody><tr><td>Node.js<\/td><td>24 LTS (ou 22 LTS)<\/td><td><code>node --version<\/code><\/td><td>Usa sempre uma linha LTS suportada<\/td><\/tr><tr><td>npm<\/td><td>Inclu\u00eddo no Node<\/td><td><code>npm --version<\/code><\/td><td>S\u00f3 para inicializar o projeto<\/td><\/tr><tr><td>OpenSSL<\/td><td>3.x<\/td><td><code>openssl version<\/code><\/td><td>Gera CA e certificados<\/td><\/tr><tr><td>curl<\/td><td>Recente com TLS 1.3<\/td><td><code>curl --version<\/code><\/td><td>Opcional, para testes<\/td><\/tr><tr><td>Sistema operativo<\/td><td>Linux, macOS ou WSL2<\/td><td>(qualquer)<\/td><td>Os comandos assumem shell POSIX<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Confirma tudo antes de avan\u00e7ar. Abre o terminal e corre as tr\u00eas verifica\u00e7\u00f5es principais:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>node --version\n# Esperado: v24.x.x ou v22.x.x\n\nopenssl version\n# Esperado: OpenSSL 3.x.x\n\nnpm --version\n# Qualquer versao recente incluida no Node serve<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Se o <code>node --version<\/code> mostrar uma vers\u00e3o antiga (18 ou inferior), atualiza antes de continuar. As op\u00e7\u00f5es de TLS 1.3 e o comportamento de valida\u00e7\u00e3o de certificados s\u00e3o mais previs\u00edveis nas linhas recentes. Conhecimentos \u00fateis mas n\u00e3o obrigat\u00f3rios: saber o que \u00e9 uma chave p\u00fablica e o conceito de cadeia de confian\u00e7a. Se precisares de uma revis\u00e3o, o nosso artigo sobre <a href=\"\/pt\/https-e-tls\/\">HTTPS e TLS<\/a> explica o cadeado e o handshake em linguagem simples.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-1-e-2-inicializar-o-projeto-e-criar-a-ca-raiz\">Passo 1 e 2: inicializar o projeto e criar a CA raiz<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Come\u00e7a por criar a estrutura. Vamos manter os certificados numa pasta <code>certs<\/code> separada do c\u00f3digo, um h\u00e1bito que evita commits acidentais de chaves privadas para o Git.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir mtls-node && cd mtls-node\nnpm init -y\nmkdir certs\necho \"certs\/\" &gt; .gitignore\necho \"node_modules\/\" &gt;&gt; .gitignore<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Agora cria a Autoridade Certificadora raiz. A CA \u00e9 o cora\u00e7\u00e3o da confian\u00e7a: a sua chave privada assina todos os outros certificados, e o seu certificado p\u00fablico \u00e9 o que servidor e cliente usam para verificar tudo. Gera primeiro a chave privada da CA com uma curva el\u00edptica moderna.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Chave privada da CA (curva eliptica P-256, moderna e rapida)\nopenssl ecparam -name prime256v1 -genkey -noout -out certs\/ca-key.pem\n\n# Certificado raiz autoassinado, valido 10 anos\nopenssl req -new -x509 -sha256 -days 3650 \\\n  -key certs\/ca-key.pem \\\n  -out certs\/ca-cert.pem \\\n  -subj \"\/C=PT\/O=Exemplo Lda\/CN=mTLS Demo Root CA\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Repara em duas escolhas. Usamos <code>prime256v1<\/code> (tamb\u00e9m conhecida por P-256) em vez de RSA porque as chaves el\u00edpticas s\u00e3o mais pequenas e r\u00e1pidas para o mesmo n\u00edvel de seguran\u00e7a. A documenta\u00e7\u00e3o do Node recomenda curvas de pelo menos 224 bits para ECDSA, e a P-256 fica confortavelmente acima disso. Usamos <code>-sha256<\/code> porque o SHA-1 e o MD5 j\u00e1 n\u00e3o s\u00e3o aceit\u00e1veis para assinaturas, um ponto que tanto a documenta\u00e7\u00e3o do Node como a hist\u00f3ria da <a href=\"\/pt\/colisao-sha1\/\">colis\u00e3o SHAttered do SHA-1<\/a> deixam claro.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A chave <code>ca-key.pem<\/code> \u00e9 o segredo mais valioso de todo o sistema. Quem a tiver pode emitir certificados que o teu servidor vai aceitar. Em produ\u00e7\u00e3o, esta chave fica num m\u00f3dulo de hardware (HSM) ou num cofre de segredos, nunca no disco de um servidor de aplica\u00e7\u00e3o.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-3-e-4-emitir-os-certificados-de-servidor-e-de-cliente\">Passo 3 e 4: emitir os certificados de servidor e de cliente<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Com a CA pronta, emitimos dois certificados assinados por ela. Cada um precisa de tr\u00eas passos: gerar a chave privada, criar um pedido de assinatura (CSR) e assin\u00e1-lo com a CA. Come\u00e7a pelo servidor.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># 1. Chave privada do servidor\nopenssl ecparam -name prime256v1 -genkey -noout -out certs\/server-key.pem\n\n# 2. Pedido de assinatura (CSR), CN = localhost para testes\nopenssl req -new -sha256 \\\n  -key certs\/server-key.pem \\\n  -out certs\/server.csr \\\n  -subj \"\/C=PT\/O=Exemplo Lda\/CN=localhost\"\n\n# 3. Assinar com a CA, com SAN para localhost (obrigatorio em clientes modernos)\nopenssl x509 -req -sha256 -days 365 \\\n  -in certs\/server.csr \\\n  -CA certs\/ca-cert.pem -CAkey certs\/ca-key.pem -CAcreateserial \\\n  -out certs\/server-cert.pem \\\n  -extfile &lt;(printf \"subjectAltName=DNS:localhost,IP:127.0.0.1\")<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">O Subject Alternative Name (SAN) \u00e9 cr\u00edtico. Clientes TLS modernos, incluindo o Node.js, ignoram o campo CN e validam o nome do host contra o SAN. Se esqueceres o SAN, o cliente recusa o certificado com um erro de hostname, mesmo que tudo o resto esteja correto. Esta \u00e9 uma das armadilhas mais comuns e voltamos a ela mais \u00e0 frente.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Agora o certificado do cliente. O processo \u00e9 igual, mas o nome comum (CN) identifica o cliente, n\u00e3o um host. Vamos usar esse CN mais tarde para decidir permiss\u00f5es. Pensa no CN como o &#8220;nome de utilizador&#8221; criptogr\u00e1fico do cliente.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># 1. Chave privada do cliente\nopenssl ecparam -name prime256v1 -genkey -noout -out certs\/client-key.pem\n\n# 2. CSR, CN identifica o cliente (vamos usa-lo para autorizacao)\nopenssl req -new -sha256 \\\n  -key certs\/client-key.pem \\\n  -out certs\/client.csr \\\n  -subj \"\/C=PT\/O=Exemplo Lda\/CN=servico-faturacao\"\n\n# 3. Assinar com a CA\nopenssl x509 -req -sha256 -days 365 \\\n  -in certs\/client.csr \\\n  -CA certs\/ca-cert.pem -CAkey certs\/ca-key.pem -CAcreateserial \\\n  -out certs\/client-cert.pem<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Confirma que tudo foi assinado corretamente inspecionando um certificado. O comando seguinte mostra o emissor (deve ser a tua CA), o sujeito e o per\u00edodo de validade. Consulta a <a href=\"https:\/\/www.openssl.org\/docs\/man3.0\/man1\/openssl-req.html\" target=\"_blank\" rel=\"noopener\">p\u00e1gina de manual do openssl-req<\/a> para todas as op\u00e7\u00f5es dispon\u00edveis.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>openssl x509 -in certs\/client-cert.pem -noout -subject -issuer -dates\n\n# Saida esperada:\n# subject=C=PT, O=Exemplo Lda, CN=servico-faturacao\n# issuer=C=PT, O=Exemplo Lda, CN=mTLS Demo Root CA\n# notBefore=Mar  3 10:00:00 2026 GMT\n# notAfter=Mar  3 10:00:00 2027 GMT<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-5-o-servidor-tls-1-3-em-node-js\">Passo 5: o servidor TLS 1.3 em Node.js<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Chegou o c\u00f3digo. Cria um ficheiro <code>server.js<\/code>. Come\u00e7amos com um servidor HTTPS que carrega a sua chave e certificado, mas ainda sem exigir o certificado do cliente. Vamos adicionar a exig\u00eancia no passo seguinte, para veres a diferen\u00e7a.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ server.js\nimport https from 'node:https';\nimport fs from 'node:fs';\n\nconst options = {\n  key: fs.readFileSync('certs\/server-key.pem'),\n  cert: fs.readFileSync('certs\/server-cert.pem'),\n  ca: fs.readFileSync('certs\/ca-cert.pem'),\n\n  \/\/ Forcar TLS 1.3, sem fallback para versoes antigas\n  minVersion: 'TLSv1.3',\n  maxVersion: 'TLSv1.3',\n};\n\nconst server = https.createServer(options, (req, res) =&gt; {\n  res.writeHead(200, { 'Content-Type': 'application\/json' });\n  res.end(JSON.stringify({ mensagem: 'Ligacao TLS 1.3 estabelecida' }));\n});\n\nserver.listen(8443, () =&gt; {\n  console.log('Servidor mTLS a escutar em https:\/\/localhost:8443');\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">As linhas <code>minVersion<\/code> e <code>maxVersion<\/code> a &#8216;TLSv1.3&#8217; s\u00e3o a tua primeira linha de defesa. Elas dizem ao Node para recusar qualquer liga\u00e7\u00e3o que n\u00e3o fale TLS 1.3. Isto elimina de uma vez toda uma classe de ataques contra vers\u00f5es antigas do protocolo (TLS 1.0, 1.1 e os pontos fracos do 1.2 mal configurado). O TLS 1.3, definido no <a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/rfc8446\" target=\"_blank\" rel=\"noopener\">RFC 8446<\/a>, removeu cipher suites inseguras e simplificou o handshake.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Repara tamb\u00e9m que o ficheiro usa <code>import<\/code> em vez de <code>require<\/code>. O ecossistema Node de 2026 \u00e9 cada vez mais ESM-first. Para isto funcionar, adiciona <code>\"type\": \"module\"<\/code> ao teu <code>package.json<\/code>. Arranca o servidor com <code>node server.js<\/code> e confirma que v\u00eas a mensagem de escuta.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-6-exigir-e-validar-o-certificado-do-cliente\">Passo 6: exigir e validar o certificado do cliente<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Agora transformamos um servidor HTTPS normal num servidor mTLS. Duas op\u00e7\u00f5es fazem a magia: <code>requestCert<\/code> pede o certificado ao cliente, e <code>rejectUnauthorized<\/code> recusa a liga\u00e7\u00e3o se esse certificado n\u00e3o for v\u00e1lido contra a CA. Atualiza o objeto <code>options<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const options = {\n  key: fs.readFileSync('certs\/server-key.pem'),\n  cert: fs.readFileSync('certs\/server-cert.pem'),\n  ca: fs.readFileSync('certs\/ca-cert.pem'),\n\n  minVersion: 'TLSv1.3',\n  maxVersion: 'TLSv1.3',\n\n  \/\/ O coracao do mTLS:\n  requestCert: true,        \/\/ pede o certificado ao cliente\n  rejectUnauthorized: true, \/\/ recusa se o certificado nao for valido\n};<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Com <code>rejectUnauthorized: true<\/code>, qualquer cliente sem um certificado assinado pela tua CA \u00e9 rejeitado durante o handshake. O callback do servidor nunca chega a correr para esses clientes. Esta \u00e9 a propriedade &#8220;falha fechada&#8221; do mTLS bem feito: a seguran\u00e7a n\u00e3o depende de o teu c\u00f3digo de aplica\u00e7\u00e3o lembrar-se de verificar nada.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"inspecionar-o-certificado-do-cliente-autenticado\">Inspecionar o certificado do cliente autenticado<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Quando um cliente v\u00e1lido se liga, queres saber quem \u00e9. O Node exp\u00f5e o certificado do par atrav\u00e9s de <code>req.socket.getPeerCertificate()<\/code> e o estado de valida\u00e7\u00e3o atrav\u00e9s de <code>req.socket.authorized<\/code>. Vamos usar isto para registar a identidade de cada pedido.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const server = https.createServer(options, (req, res) =&gt; {\n  const cert = req.socket.getPeerCertificate();\n  const autorizado = req.socket.authorized;\n\n  \/\/ Com rejectUnauthorized:true so chegamos aqui se autorizado for true,\n  \/\/ mas verificamos na mesma por defesa em profundidade.\n  if (!autorizado || !cert || !cert.subject) {\n    res.writeHead(401);\n    res.end(JSON.stringify({ erro: 'Certificado de cliente em falta ou invalido' }));\n    return;\n  }\n\n  const cliente = cert.subject.CN;\n  console.log(`Pedido autenticado de: ${cliente}`);\n\n  res.writeHead(200, { 'Content-Type': 'application\/json' });\n  res.end(JSON.stringify({ mensagem: `Ola, ${cliente}`, autenticado: true }));\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">O campo <code>cert.subject.CN<\/code> cont\u00e9m o &#8220;servico-faturacao&#8221; que definimos quando emitimos o certificado do cliente. A partir daqui, a identidade do cliente \u00e9 um facto criptogr\u00e1fico, n\u00e3o uma afirma\u00e7\u00e3o que ele faz num cabe\u00e7alho HTTP que poderia falsificar.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-7-mapear-identidade-e-autorizacao-ao-nivel-da-aplicacao\">Passo 7: mapear identidade e autoriza\u00e7\u00e3o ao n\u00edvel da aplica\u00e7\u00e3o<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Aqui est\u00e1 o erro conceptual mais perigoso do mTLS: pensar que autentica\u00e7\u00e3o \u00e9 o mesmo que autoriza\u00e7\u00e3o. N\u00e3o \u00e9. O mTLS prova <em>quem<\/em> \u00e9 o cliente. Mas decidir <em>o que<\/em> esse cliente pode fazer \u00e9 trabalho da tua aplica\u00e7\u00e3o. Um certificado v\u00e1lido n\u00e3o deve dar acesso a tudo automaticamente.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A documenta\u00e7\u00e3o do Node refor\u00e7a este ponto: o certificado identifica o cliente, mas o servidor ainda deve mapear essa identidade para permiss\u00f5es, escopos ou pol\u00edticas internas. Vamos construir uma tabela simples de autoriza\u00e7\u00e3o que mapeia o CN do certificado para um papel.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Mapa de autorizacao: CN do certificado -&gt; permissoes\nconst POLITICAS = {\n  'servico-faturacao': { papel: 'escrita', rotas: ['\/faturas'] },\n  'servico-relatorios': { papel: 'leitura', rotas: ['\/faturas', '\/relatorios'] },\n};\n\nfunction autorizar(cn, rota) {\n  const politica = POLITICAS[cn];\n  if (!politica) return false;\n  return politica.rotas.includes(rota);\n}\n\n\/\/ Dentro do handler:\nconst cliente = cert.subject.CN;\nconst rota = new URL(req.url, 'https:\/\/localhost').pathname;\n\nif (!autorizar(cliente, rota)) {\n  res.writeHead(403);\n  res.end(JSON.stringify({ erro: `Cliente ${cliente} sem acesso a ${rota}` }));\n  return;\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Este padr\u00e3o separa de forma limpa as duas responsabilidades. O TLS trata da autentica\u00e7\u00e3o (&#8220;este \u00e9 mesmo o servico-faturacao&#8221;). A fun\u00e7\u00e3o <code>autorizar<\/code> trata do controlo de acesso (&#8220;o servico-faturacao pode escrever em \/faturas, mas n\u00e3o pode ver relat\u00f3rios&#8221;). \u00c9 a mesma filosofia de princ\u00edpio do menor privil\u00e9gio que aplicamos em <a href=\"\/pt\/autenticacao-ssh-chaves\/\">autentica\u00e7\u00e3o SSH com chaves Ed25519<\/a>: a identidade forte \u00e9 o in\u00edcio, n\u00e3o o fim, do controlo de acesso.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-8-o-cliente-mtls-em-node-js\">Passo 8: o cliente mTLS em Node.js<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Um servidor que exige certificados de cliente \u00e9 in\u00fatil sem um cliente que os apresente. Cria <code>client.js<\/code>. O cliente carrega a sua pr\u00f3pria chave e certificado e, crucialmente, a CA, para poder verificar o certificado do servidor.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ client.js\nimport https from 'node:https';\nimport fs from 'node:fs';\n\nconst options = {\n  hostname: 'localhost',\n  port: 8443,\n  path: '\/faturas',\n  method: 'GET',\n\n  \/\/ O cliente apresenta o SEU certificado\n  key: fs.readFileSync('certs\/client-key.pem'),\n  cert: fs.readFileSync('certs\/client-cert.pem'),\n\n  \/\/ E verifica o certificado do servidor contra a CA\n  ca: fs.readFileSync('certs\/ca-cert.pem'),\n\n  minVersion: 'TLSv1.3',\n};\n\nconst req = https.request(options, (res) =&gt; {\n  let dados = '';\n  res.on('data', (c) =&gt; (dados += c));\n  res.on('end', () =&gt; {\n    console.log('Estado:', res.statusCode);\n    console.log('Resposta:', dados);\n  });\n});\n\nreq.on('error', (e) =&gt; console.error('Erro de ligacao:', e.message));\nreq.end();<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Corre o servidor numa janela (<code>node server.js<\/code>) e o cliente noutra (<code>node client.js<\/code>). Se tudo estiver bem, v\u00eas uma resposta 200 com a mensagem de sauda\u00e7\u00e3o. Para provar que o mTLS funciona, tenta ligar-te sem certificado, por exemplo com <code>curl https:\/\/localhost:8443\/faturas -k<\/code>. A liga\u00e7\u00e3o \u00e9 recusada porque o curl, sem certificado de cliente, falha o handshake. Esse \u00e9 o sistema a fazer o seu trabalho.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-9-forcar-tls-1-3-e-cipher-suites-seguras\">Passo 9: for\u00e7ar TLS 1.3 e cipher suites seguras<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">J\u00e1 for\u00e7\u00e1mos TLS 1.3 com <code>minVersion<\/code>. Vale a pena perceber o que isso te d\u00e1. O TLS 1.3 s\u00f3 permite cinco cipher suites, todas com sigilo perfeito futuro (forward secrecy) e cifra autenticada. N\u00e3o h\u00e1 como negociar uma combina\u00e7\u00e3o fraca por engano, ao contr\u00e1rio do TLS 1.2 onde a configura\u00e7\u00e3o errada abre buracos.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Cipher suite TLS 1.3<\/th><th>Cifra<\/th><th>Hash<\/th><th>Recomenda\u00e7\u00e3o<\/th><\/tr><\/thead><tbody><tr><td>TLS_AES_256_GCM_SHA384<\/td><td>AES-256-GCM<\/td><td>SHA-384<\/td><td>Preferida para dados sens\u00edveis<\/td><\/tr><tr><td>TLS_CHACHA20_POLY1305_SHA256<\/td><td>ChaCha20-Poly1305<\/td><td>SHA-256<\/td><td>\u00d3tima em hardware sem AES-NI<\/td><\/tr><tr><td>TLS_AES_128_GCM_SHA256<\/td><td>AES-128-GCM<\/td><td>SHA-256<\/td><td>S\u00f3lida e r\u00e1pida<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Se quiseres restringir explicitamente as cipher suites de TLS 1.3, podes passar a op\u00e7\u00e3o <code>ciphers<\/code>. Na maioria dos casos, deixar o Node usar a ordem por omiss\u00e3o do OpenSSL \u00e9 a escolha certa, porque essa ordem j\u00e1 prioriza as op\u00e7\u00f5es fortes. S\u00f3 restrinjas se tiveres um requisito de conformidade espec\u00edfico.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Restringir cipher suites de TLS 1.3 (opcional)\nconst options = {\n  \/\/ ... resto da configuracao ...\n  minVersion: 'TLSv1.3',\n  ciphers: [\n    'TLS_AES_256_GCM_SHA384',\n    'TLS_CHACHA20_POLY1305_SHA256',\n  ].join(':'),\n};<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A OWASP mant\u00e9m recomenda\u00e7\u00f5es atualizadas sobre configura\u00e7\u00e3o de TLS. Se vais para produ\u00e7\u00e3o, vale a pena passar pela <a href=\"https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Transport_Layer_Security_Cheat_Sheet.html\" target=\"_blank\" rel=\"noopener\">folha de dicas de TLS da OWASP<\/a> antes de fixar a tua configura\u00e7\u00e3o final.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-10-e-11-rotacao-expiracao-e-revogacao-de-certificados\">Passo 10 e 11: rota\u00e7\u00e3o, expira\u00e7\u00e3o e revoga\u00e7\u00e3o de certificados<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Certificados expiram, e \u00e9 bom que assim seja. Um certificado de cliente v\u00e1lido 365 dias limita o dano se uma chave for comprometida sem ser detetada. Mas isto cria uma obriga\u00e7\u00e3o operacional: tens de rodar os certificados antes de expirarem, ou os teus servi\u00e7os param de comunicar \u00e0 meia-noite de uma data qualquer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A rota\u00e7\u00e3o \u00e9 simplesmente repetir o passo de emiss\u00e3o com uma nova chave e novo certificado, depois recarregar no servi\u00e7o. Automatiza a verifica\u00e7\u00e3o de expira\u00e7\u00e3o para nunca seres apanhado de surpresa.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Verificar se um certificado expira nos proximos 30 dias\nopenssl x509 -in certs\/client-cert.pem -noout -checkend 2592000\n# Codigo de saida 0 = ainda valido por mais de 30 dias\n# Codigo de saida 1 = expira dentro de 30 dias, RODAR JA\n\n# Ver a data exata de expiracao\nopenssl x509 -in certs\/client-cert.pem -noout -enddate<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A revoga\u00e7\u00e3o resolve o problema oposto: e se um certificado ainda v\u00e1lido for comprometido e precisares de o invalidar j\u00e1? Para sistemas pequenos, a abordagem mais simples \u00e9 manter uma lista de allow no c\u00f3digo (como o nosso mapa <code>POLITICAS<\/code>): remove o CN comprometido e ele perde acesso imediatamente, mesmo com certificado tecnicamente v\u00e1lido. Para sistemas maiores, usa uma Lista de Revoga\u00e7\u00e3o de Certificados (CRL) ou OCSP, que o Node consegue verificar com configura\u00e7\u00e3o adicional.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-12-testar-com-openssl-curl-e-automacao\">Passo 12: testar com OpenSSL, curl e automa\u00e7\u00e3o<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Confiar sem testar \u00e9 neglig\u00eancia. A ferramenta <code>openssl s_client<\/code> deixa-te simular um cliente mTLS e ver o handshake completo, incluindo a vers\u00e3o de protocolo negociada e a cipher suite escolhida.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Ligar com certificado de cliente e inspecionar o handshake\nopenssl s_client -connect localhost:8443 \\\n  -cert certs\/client-cert.pem \\\n  -key certs\/client-key.pem \\\n  -CAfile certs\/ca-cert.pem \\\n  -tls1_3\n\n# Procura na saida:\n# Protocol  : TLSv1.3\n# Cipher    : TLS_AES_256_GCM_SHA384\n# Verify return code: 0 (ok)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">O <code>curl<\/code> faz um teste mais pr\u00f3ximo do mundo real. Com os certificados certos, deve devolver 200. Sem eles, deve falhar.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Pedido autenticado correto\ncurl --cert certs\/client-cert.pem \\\n     --key certs\/client-key.pem \\\n     --cacert certs\/ca-cert.pem \\\n     https:\/\/localhost:8443\/faturas\n# Esperado: {\"mensagem\":\"Ola, servico-faturacao\", ...}\n\n# Sem certificado de cliente (deve FALHAR)\ncurl --cacert certs\/ca-cert.pem https:\/\/localhost:8443\/faturas\n# Esperado: erro de handshake TLS, ligacao recusada<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Esta dualidade (sucesso com certificado, falha sem ele) \u00e9 o teste mais importante de todos. Se o segundo comando devolver dados em vez de um erro, a tua configura\u00e7\u00e3o mTLS est\u00e1 partida e qualquer um pode aceder. Inclui ambos os testes num script de CI para apanhar regress\u00f5es antes de chegarem a produ\u00e7\u00e3o.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"erros-comuns-e-armadilhas-no-mtls-em-node-js\">Erros comuns e armadilhas no mTLS em Node.js<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">O mTLS falha quase sempre pelas mesmas raz\u00f5es. Conhecer estas armadilhas poupa-te horas de depura\u00e7\u00e3o frustrante. A tabela resume as mais frequentes.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Armadilha<\/th><th>Sintoma<\/th><th>Solu\u00e7\u00e3o<\/th><\/tr><\/thead><tbody><tr><td>Falta de SAN no certificado do servidor<\/td><td>Erro de hostname no cliente<\/td><td>Adicionar <code>subjectAltName<\/code> com o nome ou IP<\/td><\/tr><tr><td><code>rejectUnauthorized: false<\/code> esquecido<\/td><td>Clientes inv\u00e1lidos s\u00e3o aceites<\/td><td>Garantir <code>true<\/code> em produ\u00e7\u00e3o<\/td><\/tr><tr><td>CA em falta no cliente<\/td><td>Cliente recusa o servidor (self-signed)<\/td><td>Carregar <code>ca-cert.pem<\/code> no cliente<\/td><\/tr><tr><td>Chave privada com permiss\u00f5es abertas<\/td><td>Risco de fuga de chave<\/td><td><code>chmod 600<\/code> nas chaves <code>.pem<\/code><\/td><\/tr><tr><td>Certificado expirado<\/td><td>Liga\u00e7\u00f5es param de repente<\/td><td>Monitorizar com <code>-checkend<\/code><\/td><\/tr><tr><td>Confundir autentica\u00e7\u00e3o com autoriza\u00e7\u00e3o<\/td><td>Qualquer cert v\u00e1lido acede a tudo<\/td><td>Mapear CN para permiss\u00f5es<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">A pior de todas, e a mais comum em tutoriais apressados pela internet, \u00e9 <code>rejectUnauthorized: false<\/code>. As pessoas adicionam essa linha para &#8220;fazer funcionar&#8221; durante o desenvolvimento e esquecem-se de a remover. O resultado \u00e9 um servidor que pede certificados mas aceita qualquer um, incluindo certificados autoassinados por um atacante. \u00c9 seguran\u00e7a teatral: parece mTLS, mas n\u00e3o protege nada. Trata qualquer <code>rejectUnauthorized: false<\/code> num code review como um bug cr\u00edtico.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"resolucao-de-problemas-8-erros-e-como-os-corrigir\">Resolu\u00e7\u00e3o de problemas: 8 erros e como os corrigir<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Quando o handshake falha, o Node devolve c\u00f3digos de erro espec\u00edficos. Esta tabela mapeia os mais comuns para a sua causa e corre\u00e7\u00e3o.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>C\u00f3digo de erro<\/th><th>Causa prov\u00e1vel<\/th><th>Corre\u00e7\u00e3o<\/th><\/tr><\/thead><tbody><tr><td><code>UNABLE_TO_VERIFY_LEAF_SIGNATURE<\/code><\/td><td>Cliente n\u00e3o tem a CA carregada<\/td><td>Passar <code>ca<\/code> nas op\u00e7\u00f5es do cliente<\/td><\/tr><tr><td><code>ERR_TLS_CERT_ALTNAME_INVALID<\/code><\/td><td>SAN n\u00e3o corresponde ao hostname<\/td><td>Reemitir cert com SAN correto<\/td><\/tr><tr><td><code>CERT_HAS_EXPIRED<\/code><\/td><td>Certificado fora de validade<\/td><td>Rodar o certificado<\/td><\/tr><tr><td><code>DEPTH_ZERO_SELF_SIGNED_CERT<\/code><\/td><td>Cert n\u00e3o assinado pela CA esperada<\/td><td>Reemitir assinado pela CA correta<\/td><\/tr><tr><td><code>ECONNRESET<\/code> no handshake<\/td><td>Cliente sem certificado e servidor exige<\/td><td>Adicionar <code>cert<\/code> e <code>key<\/code> ao cliente<\/td><\/tr><tr><td><code>ERR_SSL_WRONG_VERSION_NUMBER<\/code><\/td><td>Cliente tenta HTTP em porta TLS<\/td><td>Usar <code>https:\/\/<\/code>, n\u00e3o <code>http:\/\/<\/code><\/td><\/tr><tr><td><code>ERR_TLS_HANDSHAKE_TIMEOUT<\/code><\/td><td>Vers\u00f5es de TLS incompat\u00edveis<\/td><td>Alinhar <code>minVersion<\/code> nos dois lados<\/td><\/tr><tr><td><code>EACCES<\/code> ao ler chave<\/td><td>Permiss\u00f5es de ficheiro erradas<\/td><td>Corrigir dono e <code>chmod<\/code> da chave<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Quando estiveres perdido, ativa o registo detalhado do Node com a vari\u00e1vel de ambiente <code>NODE_DEBUG=tls node server.js<\/code>. Isto despeja cada passo do handshake, incluindo que certificados foram pedidos e porque a valida\u00e7\u00e3o falhou. \u00c9 verboso, mas mostra exatamente onde o processo quebra. Combina com <code>openssl s_client<\/code> do lado do cliente para ver os dois \u00e2ngulos do mesmo handshake.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Um detalhe sobre o <code>UNABLE_TO_VERIFY_LEAF_SIGNATURE<\/code>: ele aparece quando o lado que valida n\u00e3o tem como construir a cadeia at\u00e9 uma CA de confian\u00e7a. Em mTLS, isto acontece tanto no cliente (sem a CA do servidor) como no servidor (sem a CA do cliente). Verifica sempre que a op\u00e7\u00e3o <code>ca<\/code> est\u00e1 presente em ambos os lados e aponta para o mesmo <code>ca-cert.pem<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"dicas-avancadas-para-mtls-em-producao\">Dicas avan\u00e7adas para mTLS em produ\u00e7\u00e3o<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Sair do localhost para produ\u00e7\u00e3o muda algumas coisas. Estas pr\u00e1ticas separam um prot\u00f3tipo de um sistema que aguenta auditoria.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Nunca metas chaves no c\u00f3digo nem no reposit\u00f3rio.<\/strong> Carrega-as de um cofre de segredos (Vault, AWS Secrets Manager) ou de ficheiros com permiss\u00f5es restritas montados em runtime. A linha <code>certs\/<\/code> no <code>.gitignore<\/code> \u00e9 o m\u00ednimo absoluto.<\/li><li><strong>Usa uma CA interm\u00e9dia.<\/strong> Em produ\u00e7\u00e3o, a CA raiz fica offline e assina apenas uma CA interm\u00e9dia, que por sua vez emite os certificados do dia a dia. Se a interm\u00e9dia for comprometida, revogas s\u00f3 essa, sem tocar na raiz.<\/li><li><strong>Recarrega certificados sem reiniciar.<\/strong> O <code>https.Server<\/code> aceita um <code>SNICallback<\/code> e o m\u00e9todo <code>setSecureContext()<\/code> permite trocar certificados em runtime, \u00fatil para rota\u00e7\u00e3o sem downtime.<\/li><li><strong>Termina o mTLS o mais perto poss\u00edvel da aplica\u00e7\u00e3o.<\/strong> Se um proxy (Nginx, Envoy) terminar o TLS, garante que ele passa a identidade do certificado validado para a app por um cabe\u00e7alho de confian\u00e7a, e que esse cabe\u00e7alho n\u00e3o pode vir do exterior.<\/li><li><strong>Monitoriza expira\u00e7\u00f5es de forma centralizada.<\/strong> Um certificado expirado em produ\u00e7\u00e3o \u00e9 um incidente de disponibilidade. Alerta com 30 dias de anteced\u00eancia, no m\u00ednimo.<\/li><li><strong>Regista a identidade em cada pedido.<\/strong> O CN do certificado deve aparecer nos teus logs de acesso. Isto d\u00e1-te uma trilha de auditoria criptograficamente forte, valiosa em resposta a incidentes.<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Para servi\u00e7os de alto volume, considera a reutiliza\u00e7\u00e3o de sess\u00f5es TLS (session resumption), que o TLS 1.3 suporta com tickets de sess\u00e3o e reduz o custo do handshake em liga\u00e7\u00f5es repetidas. O Node ativa isto por omiss\u00e3o, mas em clusters com v\u00e1rios processos precisas de partilhar o segredo dos tickets entre eles para a reutiliza\u00e7\u00e3o funcionar entre inst\u00e2ncias.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"o-projeto-completo-estrutura-final\">O projeto completo: estrutura final<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Juntando tudo, a \u00e1rvore do projeto fica assim. Os ficheiros de certificado vivem em <code>certs\/<\/code> (e nunca no Git), o c\u00f3digo fica na raiz.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mtls-node\/\n|- certs\/\n|  |- ca-cert.pem        # CA publica (confiada por ambos)\n|  |- ca-key.pem         # CA privada (SEGREDO MAXIMO)\n|  |- server-cert.pem    # certificado do servidor\n|  |- server-key.pem     # chave do servidor\n|  |- client-cert.pem    # certificado do cliente\n|  |- client-key.pem     # chave do cliente\n|- server.js              # servidor mTLS com TLS 1.3\n|- client.js              # cliente mTLS\n|- package.json           # com \"type\": \"module\"\n|- .gitignore             # ignora certs\/ e node_modules\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Aqui est\u00e1 o <code>server.js<\/code> final, com autentica\u00e7\u00e3o, autoriza\u00e7\u00e3o e TLS 1.3 for\u00e7ado, pronto para adaptares. Este \u00e9 o projeto completo e funcional que prometemos no in\u00edcio.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ server.js (versao completa)\nimport https from 'node:https';\nimport fs from 'node:fs';\n\nconst options = {\n  key: fs.readFileSync('certs\/server-key.pem'),\n  cert: fs.readFileSync('certs\/server-cert.pem'),\n  ca: fs.readFileSync('certs\/ca-cert.pem'),\n  minVersion: 'TLSv1.3',\n  maxVersion: 'TLSv1.3',\n  requestCert: true,\n  rejectUnauthorized: true,\n};\n\nconst POLITICAS = {\n  'servico-faturacao': { rotas: ['\/faturas'] },\n  'servico-relatorios': { rotas: ['\/faturas', '\/relatorios'] },\n};\n\nfunction autorizar(cn, rota) {\n  const p = POLITICAS[cn];\n  return p ? p.rotas.includes(rota) : false;\n}\n\nconst server = https.createServer(options, (req, res) =&gt; {\n  const cert = req.socket.getPeerCertificate();\n  if (!req.socket.authorized || !cert?.subject) {\n    res.writeHead(401); res.end('{\"erro\":\"nao autenticado\"}'); return;\n  }\n  const cn = cert.subject.CN;\n  const rota = new URL(req.url, 'https:\/\/localhost').pathname;\n  if (!autorizar(cn, rota)) {\n    res.writeHead(403); res.end(`{\"erro\":\"${cn} sem acesso\"}`); return;\n  }\n  console.log(`OK: ${cn} -&gt; ${rota}`);\n  res.writeHead(200, { 'Content-Type': 'application\/json' });\n  res.end(JSON.stringify({ mensagem: `Ola, ${cn}`, rota }));\n});\n\nserver.listen(8443, () =&gt; console.log('mTLS em https:\/\/localhost:8443'));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Com este projeto tens uma base s\u00f3lida de mTLS em Node.js que podes estender. Adiciona mais certificados de cliente para mais servi\u00e7os, integra com um cofre de segredos, ou coloca-o atr\u00e1s de uma malha de servi\u00e7o. Os princ\u00edpios (CA de confian\u00e7a, certificados nos dois lados, autoriza\u00e7\u00e3o separada da autentica\u00e7\u00e3o) mant\u00eam-se em qualquer escala.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"perguntas-frequentes-sobre-mtls-em-node-js\">Perguntas frequentes sobre mTLS em Node.js<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"qual-a-diferenca-entre-tls-e-mtls\">Qual a diferen\u00e7a entre TLS e mTLS?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">O TLS normal autentica s\u00f3 o servidor: o cliente verifica que est\u00e1 a falar com o servidor certo, mas o servidor n\u00e3o sabe quem \u00e9 o cliente ao n\u00edvel do protocolo. O mTLS adiciona a autentica\u00e7\u00e3o do cliente, exigindo que ambos os lados apresentem e validem certificados. O resultado \u00e9 confian\u00e7a bidirecional estabelecida antes de qualquer dado aplicacional ser trocado.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"preciso-de-comprar-certificados-de-uma-ca-publica-para-mtls\">Preciso de comprar certificados de uma CA p\u00fablica para mTLS?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">N\u00e3o, e na maioria dos casos n\u00e3o deves. Para comunica\u00e7\u00e3o interna entre servi\u00e7os, a tua pr\u00f3pria CA privada (como cri\u00e1mos no passo 2) \u00e9 a escolha certa: d\u00e1-te controlo total sobre quem recebe certificados. CAs p\u00fablicas como Let&#8217;s Encrypt servem para certificados de servidor que navegadores p\u00fablicos precisam de confiar, n\u00e3o para certificados de cliente de uma API interna.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"o-mtls-substitui-tokens-jwt-ou-chaves-de-api\">O mTLS substitui tokens JWT ou chaves de API?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">N\u00e3o exatamente, resolvem camadas diferentes. O mTLS autentica a m\u00e1quina ou servi\u00e7o ao n\u00edvel da liga\u00e7\u00e3o. JWT e chaves de API costumam autenticar um utilizador ou um pedido espec\u00edfico dentro da liga\u00e7\u00e3o. Muitos sistemas robustos combinam os dois: o mTLS prova que o servi\u00e7o cliente \u00e9 leg\u00edtimo, e um token dentro do pedido identifica o utilizador final. V\u00ea o nosso guia de <a href=\"\/pt\/autenticacao-dois-fatores-nodejs\/\">autentica\u00e7\u00e3o de dois fatores em Node.js<\/a> para as camadas de utilizador.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"porque-devo-usar-curvas-elipticas-p-256-em-vez-de-rsa\">Porque devo usar curvas el\u00edpticas (P-256) em vez de RSA?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">As chaves de curva el\u00edptica oferecem o mesmo n\u00edvel de seguran\u00e7a que chaves RSA muito maiores, com menos custo computacional e handshakes mais r\u00e1pidos. Uma chave P-256 \u00e9 compar\u00e1vel em seguran\u00e7a a uma RSA de 3072 bits, mas muito mais leve. O Node recomenda curvas de pelo menos 224 bits para ECDSA, e a P-256 cumpre isso. Para um mergulho no tema, v\u00ea as nossas <a href=\"\/pt\/assinaturas-digitais\/\">assinaturas digitais explicadas<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"como-revogo-um-certificado-de-cliente-comprometido\">Como revogo um certificado de cliente comprometido?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Para sistemas pequenos, a forma mais r\u00e1pida \u00e9 remover o CN do cliente da tua lista de autoriza\u00e7\u00e3o da aplica\u00e7\u00e3o (o mapa <code>POLITICAS<\/code>): o acesso desaparece de imediato, mesmo com o certificado tecnicamente v\u00e1lido. Para sistemas maiores, usa uma CRL (Lista de Revoga\u00e7\u00e3o de Certificados) ou OCSP, mecanismos padr\u00e3o que o Node consegue verificar com configura\u00e7\u00e3o adicional.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"o-mtls-protege-contra-um-certificado-roubado\">O mTLS protege contra um certificado roubado?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Se um atacante roubar tanto o certificado como a chave privada do cliente, pode personificar esse cliente at\u00e9 o certificado ser revogado ou expirar. Por isso a prote\u00e7\u00e3o da chave privada (permiss\u00f5es restritas, cofres de segredos, HSMs) e a validade curta dos certificados s\u00e3o t\u00e3o importantes. O mTLS \u00e9 t\u00e3o forte quanto a tua gest\u00e3o das chaves privadas.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"posso-usar-mtls-com-um-proxy-como-nginx-a-frente\">Posso usar mTLS com um proxy como Nginx \u00e0 frente?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Sim, e \u00e9 comum. O proxy pode terminar o mTLS e passar a identidade validada do cliente para a app Node por um cabe\u00e7alho de confian\u00e7a. O ponto cr\u00edtico de seguran\u00e7a: garante que esse cabe\u00e7alho s\u00f3 pode ser definido pelo proxy interno e nunca aceite vindo do exterior, ou um atacante pode forjar a identidade. Para m\u00e1xima seguran\u00e7a, termina o mTLS na pr\u00f3pria app Node, como fizemos neste tutorial.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"que-versao-de-tls-devo-usar-em-2026\">Que vers\u00e3o de TLS devo usar em 2026?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">TLS 1.3, sem exce\u00e7\u00f5es para tr\u00e1fego novo. \u00c9 mais r\u00e1pido, removeu cipher suites inseguras e simplificou o handshake face ao TLS 1.2. For\u00e7a-o com <code>minVersion: 'TLSv1.3'<\/code> nos dois lados. S\u00f3 desce para TLS 1.2 se tiveres de comunicar com um sistema legado que n\u00e3o suporta 1.3, e nesse caso documenta a exce\u00e7\u00e3o e planeia a migra\u00e7\u00e3o.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"related-coverage\">Related Coverage<\/h3>\n\n\n\n<ul class=\"wp-block-list\"><li><a href=\"\/pt\/https-e-tls\/\">HTTPS e TLS: O que o Cadeado Protege (e o que N\u00e3o Protege)<\/a><\/li><li><a href=\"\/pt\/autenticacao-ssh-chaves\/\">Autentica\u00e7\u00e3o SSH com Chaves Ed25519: 12 Passos<\/a><\/li><li><a href=\"\/pt\/autenticacao-dois-fatores-nodejs\/\">Autentica\u00e7\u00e3o de Dois Fatores em Node.js: 12 Passos<\/a><\/li><li><a href=\"\/pt\/assinaturas-digitais\/\">Como Funcionam as Assinaturas Digitais<\/a><\/li><li><a href=\"\/pt\/funcoes-hash\/\">Fun\u00e7\u00f5es de Hash Criptogr\u00e1ficas Explicadas<\/a><\/li><li><a href=\"\/pt\/sha-256\/\">SHA-256 Explicado: o Padr\u00e3o Atual de Hashing<\/a><\/li><li><a href=\"\/pt\/security-hub\/\">Seguran\u00e7a Online Explicada<\/a><\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">O mTLS em Node.js deixou de ser um luxo de grandes empresas para se tornar uma pr\u00e1tica base de qualquer arquitetura zero-trust em 2026. Com a tua pr\u00f3pria CA, certificados nos dois lados, TLS 1.3 for\u00e7ado e autoriza\u00e7\u00e3o separada da autentica\u00e7\u00e3o, tens um padr\u00e3o que escala de dois servi\u00e7os a duzentos. O c\u00f3digo deste tutorial \u00e9 o teu ponto de partida: clona-o, adapta-o e leva a confian\u00e7a bidirecional para os teus sistemas.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>O modelo de seguran\u00e7a &#8220;confia na rede interna&#8221; morreu. Em 2026, qualquer servi\u00e7o que aceite liga\u00e7\u00f5es sem provar quem est\u00e1 do outro lado \u00e9 um alvo. O mTLS em Node.js\u2026<\/p>\n","protected":false},"author":8,"featured_media":71,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-70","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-security"],"_links":{"self":[{"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/posts\/70","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/users\/8"}],"replies":[{"embeddable":true,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/comments?post=70"}],"version-history":[{"count":1,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/posts\/70\/revisions"}],"predecessor-version":[{"id":72,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/posts\/70\/revisions\/72"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/media\/71"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/media?parent=70"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/categories?post=70"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/tags?post=70"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}