TLDR
openssl genpkey -algorithm RSA -out ca.key
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt
openssl genpkey -algorithm RSA -out domain.key
openssl req -new -key domain.key -out domain.csr -addext "subjectAltName = DNS:*.devexplorador.local, DNS:devexplorador.local" -out domain.csr
openssl x509 -req -in domain.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out domain.crt -days 1000 -sha256 -copy_extensions=copyall
cat domain.crt ca.crt > domain_full_chain.crt
Introdução
Segurança é um aspecto muito importante na implantação de um sistema web. Nunca se sabe se há algum man-in-the-middle escutando e armazenando todas as mensagens enviadas do cliente para o servidor e vice-versa. Assim, é fundamental a utilização de uma camada adicional de segurança para prevenir esse roubo de dados. Dentre as soluções mais importantes, podemos citar o uso de TLS para criar um canal seguro por meio do protocolo HTTPS e um certificado SSL do tipo X509.
A conexão por meio de HTTPS tem duas finalidades principais:
- Garantir a confiabilidade para o cliente que a comunicação está sendo feita com o servidor correto, ou seja, não é um servidor fake para receber dados pessoais dos usuários;
- Gerar um canal seguro de forma que, mesmo se os dados forem interceptados por um terceiro, este não conseguiria lê-los devido a criptografia das mensagens.
Quando uma aplicação está na internet, a garantia da confiabilidade é tão importante que os navegadores destacam essa informação. Sabe aquele cadeado que aparece na URL? Ele diz se o site é ou não confiável segundo as configurações do que ele considera confiável. A segurança só é garantida se o navegador confiar em certificados realmente confiáveis. Ou seja, somente ter o cadeado não é suficiente. Se alguém acessar o seu computador e cadastrar um certificado malicioso para ser considerado válido, o cadeado apareceria mesmo se a conexão não fosse segura.
Isso somente destaca a importância do conhecimento do funcionamento do HTTPS. Assim, a configuração de um certificado auto-assinado permite estudar o funcionamento dessa camada de segurança e também depurar bugs que aparecem em produção somente quando este certificado é utilizado. Eu já me deparei com um problema de um usuário que acessavam a página do frontend da empresa, mas não conseguiam fazer login no sistema. Não havia nenhum log de erro no nosso backend. No final de tudo, o problema era que o navegador não confiava no certificado do backend, bloqueava as requisições e por isso nunca tinha log de erro no nosso sistema.
Certificado self-signed
Como explicado antes, o navegador possui algumas configurações para determinar se um certificado é ou não confiável. Como isso funciona? Essas configurações padrão vem hardcoded no código do navegador. Ou seja, a Google coloca os certificados das instituições que ela confia no Chrome. A Microsoft faz o mesmo com o Edge. Cada empresa que desenvolve navegador define os certificados considerados seguros. Obviamente, elas incluem apenas certificados de empresas e instituições renomadas e que garantem a segurança de suas chaves privadas. Essas organizações que detêm essas chaves seguras são as autoridades certificadoras raízes (Root Certificate Authority – Root CA). Logo, essas CAs conseguem emitir certificados X509 que os navegadores confiam.
Devido a quantidade de sites na internet, seria impossível restringir o controle de todos esses certificados por esse pequeno grupo de CAs. Por isso, existe um segundo nível chamado de autoridades certificadoras intermediárias. Essas autoridades também emitem certificados X509. Entretanto, como o navegador vai confiar nesses certificados se ele somente confia em certificados gerados por uma CA raiz? As autoridades certificadoras intermediárias pedem para as CAs raízes assinarem seus certificados. Assim, se o certificado de uma CA foi assinado por uma CA raiz, ele também pode ser considerado válido. Isso que chamamos de cadeia de certificados. CAs intermediárias podem assinar certificados de outras CAs intermediárias e temos uma verdadeira árvore de certificados que garantem a confiabilidade da internet. Normalmente, quando mais níveis abaixo, maior a fragilidade de um certificado, pois se a chave de alguma CA é comprometida, todos os certificados abaixo dela ficam comprometidos.
Logo, como obtenho um certificado X509 assinado por uma CA? Eu envio uma solicitação de assinatura de certificado (Certificate Signing Request – CSR) para uma CA, pago o valor correspondente e tenho um certificado válido por um certo período. Obviamente, essa CA vai exigir provas que você é realmente quem você diz. Se eu quero gerar um certificado para o site meuappperfeito.com, eu preciso provar que tenho o domínio meuappperfeito.com. Validações simples podem ser feitas pelo DNS em alguns minutos.
Esse cenário funciona muito bem para internet. Porém como eu faço esse teste na minha máquina local? Uma opção, seria pagar para uma CA gerar um certificado assinado. Entretanto, isso não é muito prático e nem interessante economicamente. Neste caso, podemos gerar um certificado auto-assinado, ou seja, um certificado de uma chave públic assinada pela própria chave privada. No nosso caso, o navegador não vai confiar neste certificado por padrão, pois ele não será assinado por uma CA raiz. Entretanto, podemos configurar o navegador manualmente para que ele confie neste certificado. Em um ambiente de desenvolvimento ou mesmo para empresas pequenas, essa solução é muito interessante. Como curiosidade, os certificados das CA raízes são self-signed, pois elas mesmas assinam os próprios certificados.
Resumindo, vamos fazer os seguintes passos neste tutorial:
- Gerar uma chave privada para a nossa própria CA (ca.key) e um certificado auto-assinado (ca.crt);
- Gerar uma chave privada para o nosso domínio devexplorador.local (domain.key) e uma solicitação de assinatura de certificado (domain.csr);
- Gerar um certificado X509 para o nosso domínio assinado pela CA (domain.crt) e gerar a cadeia de certificados (domain_full_chain.crt);
- Adicionar o certificado da nossa CA (ca.crt) no navegador;
- Configurar o nginx para usar a chave privada (domain.key) e a cadeia de certificados (domain_full_chain.crt) na comunicação HTTPS.
Gerar a chave privada da nossa CA e o certificado auto-assinado
O tutorial será feito no Linux, mas deve funcionar igualmente no MacOS e no WSL do Windows.
Vá para uma pasta para criar todos os arquivos de uma forma organizada. Digite o seguinte comando no terminal para criar a chave privada ca.key.
openssl genpkey -algorithm RSA -out ca.key
Com a chave privada, vamos criar um certificado X509 auto-assinado. Vamos especificar 3650 dias, ou seja 10 anos de validade.
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt
Digite as informações do seu certificado.
Country Name (2 letter code) [AU]: BR
State or Province Name (full name) [Some-State]: Sao Paulo
Locality Name (eg, city) []: Sao Paulo
Organization Name (eg, company) [Internet Widgits Pty Ltd]: Dev Explorador
Organizational Unit Name (eg, section) []: Dev Explorador Root CA
Common Name (e.g. server FQDN or YOUR name) []: devexplorador.local,*.devexplorador.local
Email Address []: [email protected]
Gerar a chave, a solicitação de assinatura e o certificado do nosso site
Gere a chave privada do domínio.
openssl genpkey -algorithm RSA -out domain.key
Vamos gerar a nossa solicitação de assinatura de certificado CSR.
openssl req -new -key domain.key -out domain.csr -addext "subjectAltName = DNS:*.devexplorador.local, DNS:devexplorador.local" -out domain.csr
Observe que colocamos os domínios devexplorador.local e *.devexplorador.local. Isso permitirá que nosso certificado seja usado tanto para a URL principal como para qualquer outro subdomínio. Por exemplo, esse certificado poderia ser usado para devexplorador.local para o meu frontend e api.devexplorador.local para ao meu backend. Caso precise de mais domínios ou mesmo subdomínios de subdomínios, coloque tudo que for necessário. Certificados com * são chamados de certificados SSL wildcard.
Country Name (2 letter code) [AU]:BR
State or Province Name (full name) [Some-State]:Sao Paulo
Locality Name (eg, city) []:Sao Paulo
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Dev Explorador
Organizational Unit Name (eg, section) []:Dev Engenharia
Common Name (e.g. server FQDN or YOUR name) []:*.devexplorador.local
Email Address []:[email protected]
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
Para conferir os dados do CSR:
openssl req -noout -text -in domain.csr
Por fim, vamos gerar um certificado para o nosso site assinado com a chave privada da CA
openssl x509 -req -in domain.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out domain.crt -days 1000 -sha256 -copy_extensions=copyall
Para conferir os dados do certificado, execute o comando:
openssl x509 -in domain.crt -text -noout
Com o certificado gerado, temos que gerar o arquivo com a cadeia completa de certificados. Para isso, basta concatenar o certificado do domínio com o certificado da CA em um único arquivo:
cat domain.crt ca.crt > domain_full_chain.crt
Adicionar o certificado da nossa CA no navegador
Precisamos configurar o navegador para que ele confie no certificado da nossa CA. Obviamente, esse procedimento só pode ser feito com certificados confiáveis. Como nós que criamos o certificado e sua respectiva chave privada, ele certamente é seguro.
No Google Chrome, acesse as configurações de segurança e busque por certificados gerenciados pelo Chrome. Essa etapa pode variar de acordo com o sistema operacional.
Importe o certificado da CA (ca.crt) e marque para confiar na identificação de sites:
Configurar o nginx para usar o certificado do domínio
Primeiramente, vamos configurar um nginx. Caso você já tenha o nginx instalado, basta alterar o arquivo de configurações. No nosso caso, vamos configurar um container docker para os nossos testes
Crie um arquivo docker-compose.yaml:
version: "3.3"
services:
nginx:
image: nginx:alpine
volumes:
- ./nginx-default.conf.template:/etc/nginx/conf.d/default.conf
- ./domain_full_chain.crt:/etc/nginx/ssl/domain_full_chain.crt
- ./domain.key:/etc/nginx/ssl/domain.key
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
- "80:80"
- "443:443"
Crie o arquivo de configurações do nginx (nginx-default.conf.template):
server {
listen 80 default_server;
server_name _;
return 301 https://$host$request_uri; # Redireciona requisições http para https
}
server {
server_name *.devexplorador.local;
listen 443 ssl default_server;
index index.html;
client_max_body_size 256M;
root /usr/share/nginx/html;
ssl_certificate /etc/nginx/ssl/domain_full_chain.crt;
ssl_certificate_key /etc/nginx/ssl/domain.key;
location / {
try_files $uri $uri/ /index.html =404;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
proxy_redirect off;
}
}
Por fim, basta subir o container do nginx.
docker compose up nginx
Acessando http://localhost, você deve ser redirecionado para a página em https. Note que o navegador deve informar que a conexão é insegura. Por enquanto, ignore a validação e você deve conseguir ver a página padrão do nginx.
Por enquanto, o navegador não confia no certificado porque ele foi criado para a url devexplorador.local e *.devexplorador.local. Normalmente, seria necessário configurar um DNS para resolver esses domínios. Entretanto, para o nosso teste, vamos configurar o arquivo de hosts para resolver app.devexplorador.com para 127.0.0.1. Edite o arquivo /etc/hosts com sudo (no Windows, o arquivo fica em C:\Windows\System32\drivers\etc\hosts e precisa ser editado com permissão de administrador). Crie o arquivo caso ele não exista. Adicione essa linha no final do arquivo.
127.0.0.1 app.devexplorador.local
Logo, o sistema deve resolver esse hostname para localhost. Use o ping para confirmar:
ping app.devexplorador.local
PING app.devexplorador.local (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.064 ms
64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.116 ms
Agora, acesse a página https://app.devexplorador.local. Se tudo der certo, a página será acessada com HTTPS e com certificado válido.
Conclusão
Com isso, finalizamos nosso tutorial de configuração de um certificado auto-assinado no nginx. Com o conhecimento adquirido neste tutorial, você deve estar apto a conseguir gerar CSR e um certificado assinado por uma CA para o seu site e configurá-lo no seu nginx. Por fim, não esqueça de remover o certificado auto-assinado da CA do navegador após os testes por questão de segurança. Remova também a linha adicionada no arquivo de hosts.