mTLS: Implementando Autenticação Mútua entre Cliente e Servidor em Go
mTLS (Mutual TLS) autentica mutuamente cliente e servidor. Ambos apresentam certificados digitais assinados por uma autoridade certificadora confiável. Controlando a emissão dos certificados, você garante que apenas clientes autorizados acessem seu serviço. Além disso, identifica facilmente o cliente sem precisar de uma API Key ou Token. Também recomendo usar um cabeçalho HMAC (Hash-based Message Authentication Code) como camada adicional de segurança e integridade da mensagem.
Criando Certificados
Neste exemplo, criarei os certificados de forma simples para testes locais. Ao implementar em produção, tome cuidado com os parâmetros utilizados. É importante incluir Subject Alternative Names (SANs), pois apenas o CN (Common Name) não é mais recomendado.
Primeiro, crie o par de chaves da CA (Certificate Authority):
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes \
-key ca.key -sha256 -days 3650 \
-out ca.pem \
-subj "/C=BR/ST=Estado/L=Cidade/O=MinhaEmpresa/OU=TI/CN=MinhaCA"
Com isso, você terá os arquivos ca.key
e ca.pem
, que são a chave privada e pública da CA.
Crie um arquivo de texto server_cert_ext.cnf
com as configurações do certificado do servidor:
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = req_ext
[ dn ]
C = BR
ST = Estado
L = Cidade
O = MinhaEmpresa
OU = TI
CN = localhost
[ req_ext ]
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = localhost
IP.1 = 127.0.0.1
Agora, crie o par de chaves do servidor:
openssl genrsa -out server.key 2048
openssl req -new \
-key server.key \
-out server.csr \
-config server_cert_ext.cnf
Assine o certificado do servidor com a CA, incluindo as extensões:
openssl x509 -req \
-in server.csr \
-CA ca.pem \
-CAkey ca.key \
-CAcreateserial \
-out server.pem \
-days 365 \
-sha256 \
-extfile server_cert_ext.cnf \
-extensions req_ext
Agora, o certificado do servidor está pronto. Crie o par de chaves do cliente. Primeiro, crie o arquivo de configuração client_cert_ext.cnf
:
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = req_ext
[ dn ]
C = BR
ST = Estado
L = Cidade
O = MinhaEmpresa
OU = CODIGODOCLIENTE
CN = Cliente
[ req_ext ]
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = Cliente
Observe que no campo OU
, coloquei um código do cliente para identificar quem acessa o serviço. Agora, crie o par de chaves do cliente:
openssl genrsa -out client.key 2048
openssl req -new -key client.key \
-out client.csr \
-config client_cert_ext.cnf
Assine o certificado do cliente com a CA, incluindo as extensões:
openssl x509 -req \
-in client.csr \
-CA ca.pem \
-CAkey ca.key \
-CAcreateserial \
-out client.pem \
-days 365 \
-sha256 \
-extfile client_cert_ext.cnf \
-extensions req_ext
Agora, você possui todos os certificados necessários:
- ca.pem: Certificado da CA.
- ca.key: Chave privada da CA.
- server.pem: Certificado do servidor com SANs.
- server.key: Chave privada do servidor.
- client.pem: Certificado do cliente com SANs.
- client.key: Chave privada do cliente.
Servidor
Crie um servidor simples em Go que utiliza os certificados criados:
cert, err := tls.LoadX509KeyPair("server.pem", "server.key")
if err != nil {
log.Fatalf("Erro ao carregar certificado do servidor: %v", err)
}
caCert, err := os.ReadFile("ca.pem")
if err != nil {
log.Fatalf("Erro ao ler CA cert: %v", err)
}
caCertPool := x509.NewCertPool()
if ok := caCertPool.AppendCertsFromPEM(caCert); !ok {
log.Fatalf("Erro ao adicionar CA cert ao pool")
}
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
ClientCAs: caCertPool,
ClientAuth: tls.RequireAndVerifyClientCert,
}
server := &http.Server{
Addr: ":8080",
TLSConfig: tlsConfig,
}
http.HandleFunc("/", helloHandler)
log.Fatal(server.ListenAndServeTLS("", ""))
código fonte completo do servidor
O processo é similar ao de criar um servidor comum. Carregue o certificado e a chave privada do servidor. Carregue também o certificado da CA e adicione ao ClientCAs
. Configure ClientAuth
para tls.RequireAndVerifyClientCert
, exigindo que o cliente apresente um certificado válido. Existem outras opções de configuração que você pode ajustar conforme necessário.
O handler helloHandler
verifica se o cliente está autorizado:
func helloHandler(w http.ResponseWriter, r *http.Request) {
if len(r.TLS.PeerCertificates) > 0 {
client := r.TLS.PeerCertificates[0]
ret := fmt.Sprintf(
"Common Name: %s\nOrganization Unit: %s\nSerial Number: %s\n",
client.Subject.CommonName,
client.Subject.OrganizationalUnit[0],
client.SerialNumber.Text(16))
fmt.Fprintf(w, ret)
log.Printf("Client CN: %s, OU: %s, Serial: %s\n",
client.Subject.CommonName,
client.Subject.OrganizationalUnit[0],
client.SerialNumber.Text(16))
return
}
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprintf(w, `{ "error": "client certificate required" }`)
}
O handler verifica se o cliente possui um certificado. Se sim, formata e retorna informações do certificado. Caso contrário, responde com erro de autorização.
Cliente
Crie um cliente simples em Go que utiliza o certificado criado:
cert, err := tls.LoadX509KeyPair("client.pem", "client.key")
if err != nil {
log.Fatalf("Erro ao carregar certificado do cliente: %v", err)
}
caCert, err := os.ReadFile("ca.pem")
if err != nil {
log.Fatalf("Erro ao ler CA cert: %v", err)
}
caCertPool := x509.NewCertPool()
if ok := caCertPool.AppendCertsFromPEM(caCert); !ok {
log.Fatalf("Erro ao adicionar CA cert ao pool")
}
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: caCertPool,
InsecureSkipVerify: false,
}
transport := &http.Transport{
TLSClientConfig: tlsConfig,
}
client := &http.Client{
Transport: transport,
}
resp, err := client.Get("https://localhost:8080/")
if err != nil {
log.Fatalf("Erro na requisição: %v", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatalf("Erro ao ler resposta: %v", err)
}
fmt.Printf("Resposta do servidor: %s\n", body)
código fonte completo do cliente
Como no servidor, carregue o certificado e a chave privada do cliente. Carregue também o certificado da CA e configure o transporte HTTP com TLSClientConfig
.
Pontos Importantes
- Segurança: mTLS oferece autenticação segura. Inclua SANs nos certificados para maior segurança.
- Expiração: Gerencie a renovação dos certificados para evitar que clientes percam acesso por certificados expirados.
- Complexidade: mTLS é mais complexo que outras formas de autenticação. Facilite a integração criando SDKs para os clientes.
- Gerenciamento de Certificados: Mantenha os arquivos de chaves públicas e privadas seguros. Planeje bem o armazenamento e a distribuição desses arquivos.
Conclusão
mTLS é a forma mais segura de autenticação. Após resolver os desafios iniciais de gerenciamento de chaves e certificados, você terá um sistema robusto e seguro. Os códigos de cliente e servidor são simples e podem ser integrados facilmente ao seu projeto, tornando o uso transparente. No entanto, a cada novo cliente, será necessário orientá-lo sobre certificados e segurança da informação.
Vídeo com a explicação: