trilha

Fundamentos de Programação/03 - Sistemas Reais/Semana 10 - Rede e HTTP6 min

TLS

Perguntas-guia
  • O que TLS garante: confidencialidade, integridade, autenticidade — as três?
  • Qual o papel do certificado e de quem o assinou?
  • Por que o handshake usa assimétrico e o tráfego usa simétrico?
  • O que exatamente falha quando o navegador diz "certificado inválido"?

Conceito

TLS entrega três garantias, e quase todo mundo pensa só na primeira:

Garantia Significa Sem ela
confidencialidade ninguém no caminho lê qualquer roteador lê sua senha
integridade ninguém no caminho altera sem detecção injeção de conteúdo, downgrade
autenticidade você está falando com quem pensa que está man-in-the-middle perfeito

A terceira é a que o certificado resolve, e é a mais sutil: sem autenticidade, um atacante estabelece um canal cifrado com você e outro com o servidor, lendo tudo no meio. A criptografia funcionaria perfeitamente — com a pessoa errada.

O certificado liga uma identidade (o nome do host) a uma chave pública, e é assinado por uma autoridade certificadora. Seu sistema confia em ~150 CAs raiz; a cadeia vai do certificado do servidor até uma delas. Quem assinou é o que importa — o certificado em si é público e copiável.

Por que o handshake usa assimétrico e o tráfego usa simétrico: criptografia assimétrica é ordens de magnitude mais lenta. Então ela é usada apenas para (a) autenticar o servidor e (b) acordar uma chave simétrica. Depois disso, todo o tráfego usa a chave simétrica, que é rápida e frequentemente acelerada em hardware (AES-NI).

O que "certificado inválido" quer dizer, concretamente:

Causa Significado
expirado passou da validade (o mais comum, e o mais bobo)
nome errado o certificado é para a.com e você acessou b.com
emissor não confiável autoassinado, ou CA interna não instalada
revogado a CA declarou inválido (OCSP/CRL)
cadeia incompleta o servidor não enviou os intermediários

TLS 1.3 melhorou o que importa: handshake de 1 RTT (contra 2 no 1.2), remoção de cifras antigas e de renegociação, sigilo futuro (forward secrecy) obrigatório, e retomada 0-RTT.

Na prática

Verificado contra um servidor TLS local:

TLS: versao=TLS 1.3 cifra=TLS_AES_128_GCM_SHA256

E o custo, isolado das idas e voltas de rede (é loopback):

TCP connect:    223µs
TLS handshake:  3.889ms     <- 17x o TCP connect, só de CPU

Esses 3,9 ms são criptografia de chave pública — em rede real, somam-se a eles 1 RTT (TLS 1.3) ou 2 (TLS 1.2) (2. Ciclo de uma Requisição). É a razão pela qual keep-alive importa tanto em HTTPS: você paga isso uma vez por conexão, não por requisição.

Em Go

Go tem TLS na biblioteca padrão (crypto/tls), sem OpenSSL — implementação própria em Go. Consequências: um binário estático fala HTTPS sem dependência de sistema, e as vulnerabilidades de OpenSSL não te afetam.

Inspecionar a conexão é direto, e é como você audita o que realmente foi negociado:

resp, _ := cliente.Get(url)
cs := resp.TLS                       // *tls.ConnectionState
tls.VersionName(cs.Version)          // "TLS 1.3"
tls.CipherSuiteName(cs.CipherSuite)  // "TLS_AES_128_GCM_SHA256"
cs.PeerCertificates[0].Subject.CommonName
cs.PeerCertificates[0].NotAfter      // quando expira

Servidor TLS mínimo:

srv := &http.Server{Addr: ":443", Handler: mux, TLSConfig: &tls.Config{MinVersion: tls.VersionTLS12}}
srv.ListenAndServeTLS("cert.pem", "key.pem")

MinVersion merece atenção: sem definir, versões antigas podem ser aceitas. tls.VersionTLS12 é o piso razoável hoje.

InsecureSkipVerify: true desliga a autenticidade — as três garantias caem a duas
&tls.Config{InsecureSkipVerify: true} // NUNCA em produção

Ele mantém a cifra e desliga a verificação do certificado, o que reabre exatamente o man-in-the-middle que o TLS existia para impedir. O canal continua cifrado — com o atacante.

Se o problema é uma CA interna, a solução é adicionar a CA, não desligar a verificação:

pool := x509.NewCertPool()
pool.AppendCertsFromPEM(pemDaCAInterna)
&tls.Config{RootCAs: pool}

Em teste, httptest.NewTLSServer gera um certificado e srv.Client() devolve um cliente que confia nele — sem InsecureSkipVerify e sem gerar certificado à mão. É o jeito correto de testar código HTTPS (2. Teste de Integração).

mTLS (autenticação mútua, comum entre serviços) é ClientCAs + ClientAuth:

&tls.Config{ClientCAs: pool, ClientAuth: tls.RequireAndVerifyClientCert}

Respostas às perguntas-guia

1. O que TLS garante: confidencialidade, integridade, autenticidade — as três?

As três. E a autenticidade é a que sustenta as outras duas: sem saber com quem você fala, cifrar e verificar integridade não protegem de nada.

Em Go: InsecureSkipVerify remove exatamente a autenticidade, mantendo a aparência de segurança.

2. Qual o papel do certificado e de quem o assinou?

O certificado liga nome a chave pública. Quem assinou é o que cria a confiança — o certificado sozinho não prova nada, porque qualquer um gera um.

Em Go: cs.PeerCertificates[0].Issuer mostra o emissor; RootCAs define em quem você confia.

3. Por que o handshake usa assimétrico e o tráfego usa simétrico?

Porque assimétrico é lento demais para o volume, e simétrico não resolve o problema de acordar a chave com quem você nunca conversou. Cada um faz o que é bom.

Em Go: medido — 3,9 ms de handshake contra 223 µs de TCP connect, e depois o tráfego é AES-GCM acelerado em hardware.

4. O que exatamente falha quando o navegador diz "certificado inválido"?

Uma das cinco causas da tabela. Em Go o erro é específico e vale ler em vez de contornar: x509: certificate has expired, x509: certificate is valid for a.com, not b.com, x509: certificate signed by unknown authority.

Cada uma tem correção diferente — e nenhuma delas é InsecureSkipVerify.

Trade-offs

Do conceito:

  • TLS compra as três garantias e cobra 1–2 RTTs por conexão nova mais CPU de handshake.
  • TLS 1.3 compra 1 RTT e sigilo futuro obrigatório, e cobra compatibilidade com clientes antigos.
  • Certificado de CA pública compra confiança universal e cobra renovação e exposição em logs de transparência; CA interna compra controle e cobra distribuir a raiz para todo cliente.

Em Go:

  • crypto/tls próprio compra binário estático e independência de OpenSSL; cobra depender do ciclo de releases de Go para correções.
  • httptest.NewTLSServer compra teste HTTPS honesto quase de graça.
  • InsecureSkipVerify compra cinco minutos hoje e cobra um incidente depois.

Exemplo prático

package main

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"time"
)

func main() {
	srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "conteúdo protegido")
	}))
	defer srv.Close()

	// 1. o cliente CORRETO em teste: confia no certificado do servidor de teste
	resp, err := srv.Client().Get(srv.URL)
	if err != nil {
		fmt.Println("erro:", err)
		return
	}
	corpo, _ := io.ReadAll(resp.Body)
	resp.Body.Close()
	fmt.Printf("resposta: %q\n", corpo)

	cs := resp.TLS
	fmt.Printf("versão:  %s\n", tls.VersionName(cs.Version))
	fmt.Printf("cifra:   %s\n", tls.CipherSuiteName(cs.CipherSuite))
	fmt.Printf("cert válido até: %s\n", cs.PeerCertificates[0].NotAfter.Format(time.DateOnly))
	fmt.Printf("nomes no cert:   %v %v\n",
		cs.PeerCertificates[0].DNSNames, cs.PeerCertificates[0].IPAddresses)

	// 2. cliente SEM confiar na CA do teste: a autenticidade falha
	semCA := &http.Client{Timeout: 3 * time.Second}
	_, err = semCA.Get(srv.URL)
	fmt.Printf("\nsem a CA:  %v\n", err)

	// 3. a correção CORRETA: adicionar a CA, não desligar a verificação
	pool := x509.NewCertPool()
	pool.AddCert(srv.Certificate())
	comCA := &http.Client{
		Timeout:   3 * time.Second,
		Transport: &http.Transport{TLSClientConfig: &tls.Config{RootCAs: pool, MinVersion: tls.VersionTLS12}},
	}
	r3, err := comCA.Get(srv.URL)
	if err == nil {
		r3.Body.Close()
		fmt.Println("com a CA:  ok, verificação ATIVA")
	}

	// 4. a correção ERRADA: mantém a cifra, joga fora a autenticidade
	inseguro := &http.Client{
		Timeout:   3 * time.Second,
		Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}},
	}
	r4, err := inseguro.Get(srv.URL)
	if err == nil {
		r4.Body.Close()
		fmt.Println("InsecureSkipVerify: ok — e vulnerável a man-in-the-middle")
	}
}

A diferença entre os itens 3 e 4 é o ponto da nota: os dois "funcionam", e só um deles é seguro. O erro do item 2 (certificate signed by unknown authority) é um sintoma correto — contorná-lo com InsecureSkipVerify é desligar o alarme em vez de apagar o fogo.

Relacionado


Parte de Semana 10 - Rede e HTTP · 00 - MOC Fundamentos de Programação

Buscar

Busca por título, seção e texto das notas