Autenticação usando certificados de cliente HTTPS
Ouvimos muito sobre como as senhas são inseguras, e não devemos ser usadas sozinhas para autenticação. Eles são difíceis de lembrar, então os usuários são tentados a chegar a senhas fracas e reutilizá-las em vários sites. Mesmo que a senha seja forte, ainda é apenas uma pequena sequência que os usuários sabem.
Existem inúmeras maneiras desmitigar thi , como HMAC ou senhas únicas baseadas no tempo ou mais recentemente tokens de hardware de 2º fator universais. Todos eles baseados em algo que o usuário tem,em vez de algo que eles sabem. O que eles têm é uma chave secreta, que eles podem usar para gerar uma senha ou assinar mensagens.
O que parece ser esquecido no mundo do consumidor é que cada navegador teve um recurso incorporado desde que o TLS foi introduzido, chamado de autenticação mútua,que permite ao usuário apresentar um certificado, bem como o servidor. Isso significa que o usuário pode autenticar com algo que tem e — se o certificado estiver protegido por uma senha — algo que ele sabe.
Neste post, implementamos um simples Nó.js exemplo que usa certificados de cliente para autenticar o usuário.
Só precisamos de dependência externa, caso contrário, só dependemos do nó padrão.js servidor HTTPS. Também precisamos ler os certificados/chaves para configurar HTTPS .express fs
const express = require('express')
const fs = require('fs')
const https = require('https')
Configuração da chave privada e do certificado
Em primeiro lugar, precisamos gerar nossas chaves e certificados. Usamos a ferramenta de linha de comando. No Linux, é provável que ele já esteja instalado – se não, instale o pacote de sua distribuição. No Windows é um pouco mais complicado, veja este tutorial openssl openssl
;
Como em todos os servidores HTTPS regulares, precisamos gerar um certificado de servidor. Por uma questão de brevidade, usamos um certificado auto-assinado aqui — na vida real, você provavelmente quer usar uma autoridade de certificado bem conhecida, como o Let's Encrypt
.
Para gerar um certificado auto-assinado (no nosso caso, sem criptografia):
$ openssl req -x509 -newkey rsa:4096 -keyout server_key.pem -out server_cert.pem -nodes -days 365 -subj "/CN=localhost/O=Client\ Certificate\ Demo"
Este é, na verdade, um processo de três etapas combinado em um comando:
- Crie uma nova chave RSA 4096bit e salve-a, sem criptografia DES (e
server_key.pem-newkey-keyout-nodes
) - Crie um pedido de assinatura de certificado para um determinado assunto, válido por 365 dias (,
-days-subj
) - Assine o CSR usando a tecla servidor e salve-a como um certificado X.509 (,
server_cert.pem-x509-out
)
Poderíamos também ter feito isso com comandos de árvores, e. Usamos o formato PEM (a configuração padrão), que é um arquivo de texto codificado com base64 com um cabeçalho e um rodapé. Outra opção seria o formato DER, que usa codificação binária. Há um pouco de confusão a que a extensão do arquivo deve se referir: também é comum usar ou , referindo-se ao conteúdo do arquivo em vez da codificação (nesse caso eles podem conter dados codificados pelo DER e PEM)
.openssl genrsaopenssl reqopenssl x509----- BEGIN/END CERTIFICATE/PRIVATE KEY -----.key.crt
Configurando o servidor HTTP em node.js
Vamos adicionar nossa chave de servidor e certificado ao , que passamos para o servidor HTTPS mais tarde :options object
const opts = { key: fs.readFileSync('server_key.pem')
, cert: fs.readFileSync('server_cert.pem')
Em seguida, instruimos o servidor HTTPS a solicitar um certificado de cliente do usuário
, requestCert: true
Então dizemos para aceitar pedidos sem certificado válido. Precisamos disso para lidar com conexões inválidas também (por exemplo, para exibir uma mensagem de erro), caso contrário, eles receberiam apenas uma mensagem de erro HTTPS enigmática do navegador (para ser preciso) ERR_BAD_SSL_CLIENT_AUTH_CERT
, rejectUnauthorized: false
Finalmente, fornecemos uma lista de certificados ca que consideramos válidos. Por enquanto, assinamos certificados de clientes com nossa própria chave de servidor, por isso será o mesmo que nosso certificado de servidor.
, ca: [ fs.readFileSync('server_cert.pem') ]
}
Então criamos nosso aplicativo. Usamos expresso apenas para roteamento aqui — poderíamos usar o middleware também, com uma estratégia para certificados de clientes, mas por enquanto, mantemos as coisas simples .passport
const app = express()
Adicionamos nossa “página de aterrissagem” primeiro. Isso é desprotegido, então todos vão ver se apresentam um cert cliente ou não.
app.get('/', (req, res) => {
res.send('<a href="authenticate">Log in using client certificate</a>')
})
Em seguida, adicionamos nosso ponto final protegido: ele apenas exibe informações sobre o usuário e a validade de seu certificado. Podemos obter as informações do certificado da alça de conexão HTTPS:
app.get('/authenticate', (req, res) => {
const cert = req.connection.getPeerCertificate()
A bandeira será verdadeira se o certificado for válido e foi emitido por um CA listado anteriormente em . Exibimos o nome do nosso usuário (CN = Nome Comum) e o nome do emissor, que é .req.client.authorized opts.ca localhost
if (req.client.authorized) {
res.send(`Hello ${cert.subject.CN}, your certificate was issued by ${cert.issuer.CN}!`)
Eles ainda podem fornecer um certificado que não é aceito por nós. Infelizmente, o objeto será um objeto vazio em vez de se não houver certificado algum, então temos que verificar se há um campo conhecido em vez de veracidade .cert null
} else if (cert.subject) {
res.status(403)
.send(`Sorry ${cert.subject.CN}, certificates from ${cert.issuer.CN} are not welcome here.`)
E por último, eles podem vir até nós sem nenhum certificado:
} else {
res.status(401)
.send(`Sorry, but you need to provide a client certificate to continue.`)
}
})
Vamos criar nosso servidor HTTPS e estamos prontos para ir.
https.createServer(opts, app).listen(9999)
Então podemos iniciar nosso servidor com . npm i && node server.js
Configuração de certificados de clientes
Se tentarmos “fazer login” no nosso site agora, teremos uma resposta, porque ainda não temos certificados de cliente. Para testar nossa configuração, criamos dois certificados para nossos dois usuários, Alice e Bob. Alice é legal, pois tem um certificado válido emitido por nós, enquanto Bob é desagradável e tenta fazer login usando um certificado auto-assinado. 401
Para criar uma chave e um pedido de assinatura de certificado para Alice e Bob, podemos usar o seguinte comando:
$ openssl req -newkey rsa:4096 -keyout alice_key.pem -out alice_csr.pem -nodes -days 365 -subj "/CN=Alice"
$ openssl req -newkey rsa:4096 -keyout bob_key.pem -out bob_csr.pem -nodes -days 365 -subj "/CN=Bob"
Assinamos a RSE de Alice com nossa chave e guardamos como certificado. Aqui, atuamos como uma Autoridade de Certificados, por isso fornecemos nosso certificado e chave através dos parâmetros: -CA
$ openssl x509 -req -in alice_csr.pem -CA server_cert.pem -CAkey server_key.pem -out alice_cert.pem -set_serial 01 -days 365
Bob não acredita em autoridade, então ele apenas assina seu certificado por conta própria:
$ openssl x509 -req -in bob_csr.pem -signkey bob_key.pem -out bob_cert.pem -days 365
Tentando entrar
Para usar esses certificados em nosso navegador, precisamos empacotá-los no formato PKCS#12. Isso conterá tanto a chave privada quanto o certificado, assim o navegador pode usá-lo para criptografia. Para Alice, adicionamos a opção, que exclui o certificado CA do pacote. Como emitimos o certificado, já temos o certificado: não precisamos incluí-lo no certificado da Alice também. Você também pode proteger o certificado por senha. -clcerts
$ openssl pkcs12 -export -clcerts -in alice_cert.pem -inkey alice_key.pem -out alice.p12
$ openssl pkcs12 -export -in bob_cert.pem -inkey bob_key.pem -out bob.p12
Podemos importar essas chaves privadas para o navegador. No Firefox, vá para Preferências -> Certificados avançados de exibição > -> importare escolha ambos os arquivos.
Se você abrir https://localhost:9999 no navegador agora, um diálogo virá para escolher um certificado. Note que apenas o certificado de Alice está na lista: isso porque o navegador já sabe que apenas certs emitidos por nós serão aceitos (porque anunciamos usando a lista). Se continuar, verá nossa mensagem de sucesso com os detalhes de Alice. opts.ca
Isso é apenas uma limitação do navegador, você ainda pode tentar entrar com o cert do Bob usando cURL:
$ curl --insecure --cert bob.p12 --cert-type p12 https://localhost:9999/authenticate
E veja que Bob não é bem-vindo aqui!
É claro que essa solução não é prática na vida real: não queremos generer chaves para nossos usuários através da linha de comando e tê-los instalando-os em seus navegadores manualmente. No próximo artigo, veremos como podemos gerar novos certificados de clientes dinamicamente e instalá-los perfeitamente no navegador dos usuários.
Para experimentar este servidor, há clone do github repo deste post,onde você também pode encontrar as chaves e certificados.
Fontes
- Autenticação de cliente SSL em Node.js
- Q12149 — HOWTO: DER vs. CRT vs.CER vs. Certificados PEM e Como Convertê-los
- Mini tutorial para configurar certificados SSL do lado do cliente
- cliente-certificado-auth
- resposta de Diegows para “Como criar um certificado auto-assinado com abertura?”
Autor: Andras Sevcsik-Zajácz