Skip to main content

208.2 Configurações do Apache para HTTPS


Introdução


Vamos entender (de forma introdutória), o que é HTTPS e como ele funciona no Apache. O HTTPS (HyperText Transfer Protocol Secure) é uma implementação segura do protocolo HTTP. Ele adiciona uma camada de segurança à comunicação, utilizando o protocolo TLS.


Essa camada de segurança aplica criptografia aos dados trafegados entre o cliente e o servidor, impedindo que terceiros consigam ler facilmente as informações caso elas sejam interceptadas. Já no HTTP comum, os dados trafegam sem criptografia. Por isso, caso sejam capturados por terceiros, podem ser lidos com muito mais facilidade.


Por praticidade, muitas vezes dizemos que é usado SSL para criptografia, mas, na prática, atualmente o mais comum é o uso de TLS.


O TLS (Transport Layer Security) é o sucessor do SSL (Secure Sockets Layer). Ambos são protocolos de segurança criados para proteger comunicações em redes de computadores. Hoje em dia, o TLS é o protocolo utilizado, pois é a evolução do SSL. O SSL é considerado obsoleto e possui falhas de segurança conhecidas.


O SSL/TLS fornece criptografia dos dados, integridade da comunicação e autenticação. Em uma conexão HTTPS comum, o servidor apresenta um certificado digital para provar sua identidade ao cliente.



Handshake com uso de SSL/TLS


Vamos entender como acontece o handshake quando usamos SSL/TLS. Caso queira relembrar como acontece o handshake normal do TCP/IP, veja aqui.


Antes do handshake TLS acontecer, primeiro ocorre o three-way handshake do TCP. Depois que a conexão TCP é estabelecida, o cliente e o servidor iniciam a negociação TLS. Somente após essa etapa o tráfego HTTP passa a ser enviado de forma criptografada.


Veja uma imagem retirada do site da IBM demonstrando o passo a passo desse handshake: Handshake SSL/TLS


De forma simplificada, o processo funciona assim:

  1. O cliente envia um Client Hello para o servidor.

    Dentro dessa mensagem, o cliente informa os recursos que suporta, como:

    • versões de TLS suportadas;

    • algoritmos de criptografia suportados;

    • cipher suites disponíveis;

    • dados aleatórios usados na geração das chaves;

    • extensões TLS, como SNI, quando aplicável.

    O SNI (Server Name Indication) permite que o cliente informe qual domínio deseja acessar. Isso é importante quando vários sites HTTPS estão hospedados no mesmo endereço IP.


  1. O servidor responde com um Server Hello.

    Nessa resposta, o servidor escolhe os parâmetros que serão usados na conexão, como:

    • versão do TLS;
    • cipher suite escolhida;
    • parâmetros de troca de chaves;
    • certificado digital do servidor.

    A cipher suite define o conjunto de algoritmos usados na comunicação, como autenticação, troca de chaves, criptografia e integridade dos dados.


  1. O servidor envia seu certificado digital.

    Esse certificado normalmente é emitido para o domínio acessado, por exemplo:

    www.exemplo.com

    O certificado serve para autenticar o servidor perante o cliente. Ele contém informações como:

    • nome do domínio;

    • chave pública;

    • autoridade certificadora que assinou o certificado;

    • prazo de validade;

    • cadeia de certificação.

    O certificado não prova apenas "qual máquina" está respondendo, mas sim que aquele servidor possui um certificado válido para o domínio acessado.


  1. O cliente valida o certificado do servidor.

    O cliente verifica se:

    • o certificado foi emitido por uma autoridade confiável;
    • o nome do domínio corresponde ao certificado;
    • o certificado ainda está dentro do prazo de validade;
    • a cadeia de certificação é válida.

    Se a validação falhar, o navegador exibirá um alerta de segurança.


  1. Cliente e servidor realizam a troca de chaves.

    Nessa etapa, cliente e servidor negociam ou derivam uma chave compartilhada que será usada para proteger a comunicação.

    Em versões antigas do TLS, especialmente com troca de chave RSA, o cliente podia enviar um segredo pré-mestre criptografado com a chave pública do servidor.

    Já em configurações modernas, é comum o uso de algoritmos como DHE ou ECDHE, nos quais cliente e servidor trocam parâmetros públicos e derivam uma chave compartilhada sem enviar diretamente a chave secreta pela rede.


  1. As chaves de sessão são geradas.

    A partir do segredo compartilhado, cliente e servidor geram chaves simétricas que serão usadas para criptografar e proteger os dados da sessão. A partir desse ponto, a comunicação HTTP passa a trafegar de forma protegida, formando o HTTPS.


  1. Opcionalmente, o cliente pode enviar um certificado.

    Normalmente, apenas o servidor envia certificado para o cliente. Porém, em ambientes que usam autenticação mútua, chamada de mTLS (mutual TLS), o servidor também solicita um certificado do cliente. Nesse caso, o cliente precisa apresentar seu próprio certificado para se autenticar.



Certificado Digital


O certificado digital é um documento eletrônico, geralmente um arquivo, que relaciona uma identidade a uma chave pública. Essa identidade pode representar uma pessoa, um equipamento, uma organização, um serviço ou um domínio.


Esse certificado é assinado digitalmente por uma entidade confiável chamada Autoridade Certificadora, ou Certificate Authority (CA).


A função do certificado digital é comprovar que determinada chave pública pertence à identidade descrita no certificado. No caso do HTTPS, o certificado normalmente comprova que a chave pública apresentada pertence ao domínio acessado, como por exemplo:

www.exemplo.com

Assim, quando o cliente acessa um site HTTPS, o servidor envia seu certificado digital. O cliente, normalmente o navegador, verifica se esse certificado é válido, se foi emitido por uma CA confiável, se ainda está dentro do prazo de validade e se corresponde ao domínio acessado.


A chave pública por si só não comprova a identidade do servidor. Quem faz essa associação entre identidade e chave pública é o certificado digital, assinado por uma CA confiável.


A entidade que emite o certificado é conhecida como Autoridade Certificadora ou Certificate Authority (CA). Os navegadores e sistemas operacionais possuem uma lista de CAs confiáveis, chamada de repositório de autoridades confiáveis ou trust store. Eles não confiam em qualquer CA existente, mas apenas nas autoridades certificadoras que fazem parte dessa lista de confiança.


Além disso, muitas vezes o certificado do site não é assinado diretamente por uma CA raiz, mas por uma CA intermediária. Nesse caso, o servidor envia uma cadeia de certificados, permitindo que o cliente valide o certificado do site até chegar a uma CA raiz confiável.



Padrão X.509


O X.509 é um padrão usado para definir o formato de certificados digitais de chave pública. Esses certificados são usados para associar uma identidade a uma chave pública, permitindo que sistemas validem se estão se comunicando com a entidade correta, como um servidor, usuário, serviço ou dispositivo.


Na prática, um certificado X.509 funciona como uma identidade digital. Ele pode ser usado, por exemplo, para validar se um site realmente pertence ao domínio acessado pelo cliente durante uma conexão HTTPS.


Os certificados X.509 normalmente são emitidos por uma Autoridade Certificadora, também chamada de CA (Certificate Authority). A CA é responsável por assinar o certificado digital, indicando que aquela identidade foi validada de acordo com suas regras. Em uma cadeia de confiança, podem existir CAs raiz e CAs intermediárias.


Um certificado X.509 contém informações como o nome da entidade certificada, a autoridade que emitiu o certificado, a chave pública, o número de série, o período de validade, os algoritmos utilizados, extensões do certificado e a assinatura digital da autoridade certificadora.


Esse padrão é amplamente usado em protocolos de segurança, principalmente no TLS (Transport Layer Security), que é o protocolo utilizado atualmente em conexões HTTPS. O SSL também usava certificados digitais, mas hoje é considerado um protocolo antigo e obsoleto.



Cabeçalhos do X.509


Vamos ver os principais campos presentes em um certificado X.509, considerando principalmente a versão 3, que é a versão mais comum atualmente.

CampoDescrição
VersionIdentifica a versão do padrão X.509 usada no certificado. O mais comum é encontrar certificados na versão v3.
Serial NumberNúmero de série do certificado. Esse valor deve ser único para cada certificado emitido por uma determinada Autoridade Certificadora.
Signature AlgorithmIdentifica o algoritmo usado pela CA para assinar o certificado, incluindo o algoritmo de hash utilizado no processo.
IssuerIdentifica a entidade que emitiu e assinou o certificado. Normalmente é representado por um DN (Distinguished Name) da Autoridade Certificadora.
ValidityDefine o período de validade do certificado. O campo Not Before indica a partir de quando o certificado é válido, enquanto o campo Not After indica até quando ele pode ser usado.
SubjectIdentifica a entidade para a qual o certificado foi emitido. Pode representar um servidor, usuário, serviço ou dispositivo.
Subject Public Key InfoContém a chave pública da entidade identificada no campo Subject, além das informações sobre o algoritmo associado a essa chave.
Issuer Unique IdentifierCampo opcional usado para identificar de forma única o emissor do certificado. Esse campo é pouco comum na prática e não representa a assinatura do certificado.
Subject Unique IdentifierCampo opcional usado para identificar de forma única o sujeito do certificado. Também é pouco comum na prática.
ExtensionsCampo usado em certificados X.509 v3 para adicionar informações extras, como Subject Alternative Name, Key Usage, Extended Key Usage, Basic Constraints e outros parâmetros importantes.

Como curiosidade, seguem alguns comandos úteis com openssl. Para descobrir o emissor de um certificado:

openssl s_client -showcerts \
-servername www.sysnetbr.eng.br \
-connect www.sysnetbr.eng.br:443 2>/dev/null \
| openssl x509 -noout -issuer

Para descobrir quais domínios estão associados ao certificado:

openssl s_client -showcerts \
-servername www.sysnetbr.eng.br \
-connect www.sysnetbr.eng.br:443 2>/dev/null \
| openssl x509 -noout -ext subjectAltName

O campo Subject Alternative Name, também chamado de SAN, é especialmente importante em certificados TLS atuais, porque é nele que normalmente ficam os nomes DNS válidos para o certificado.



Path de Certificação


O caminho de certificação, também chamado de cadeia de certificados, é a sequência de certificados usada para validar se um certificado final pode ser confiável.


Quando acessamos um serviço HTTPS, o servidor apresenta um certificado para provar sua identidade, esse certificado normalmente não é assinado diretamente por uma CA raiz, mas por uma CA intermediária.


Essa CA intermediária, por sua vez, foi assinada por outra CA acima dela, até chegar em uma CA raiz confiável pelo sistema operacional, navegador ou aplicação. A ideia é que a confiança seja construída em cadeia:

CA raiz confiável
└── CA intermediária
└── Certificado do servidor

Cada certificado da cadeia é assinado pelo certificado acima dele. O certificado do servidor é assinado pela CA intermediária, a CA intermediária é assinada por outra CA, e assim por diante, até chegar em uma CA raiz.


A CA raiz normalmente possui um certificado autoassinado. Isso significa que ela é a emissora e também a entidade do próprio certificado. Porém, a confiança nesse certificado não vem da assinatura em si, mas do fato de ele já estar instalado como confiável no sistema, navegador ou aplicação.


Por isso, ao validar um certificado, o cliente verifica se consegue montar um caminho confiável entre o certificado apresentado pelo servidor e uma CA raiz já conhecida.


Podemos visualizar parte dessa cadeia usando o openssl:

openssl s_client \
-connect google.com.br:443 \
-servername google.com.br

Abaixo deixo um exemplo de saída que você pode ver, apenas a parte importante:

CONNECTED(00000003)
depth=2 C = US, O = Google Trust Services LLC, CN = GTS Root R1
verify return:1
depth=1 C = US, O = Google Trust Services LLC, CN = GTS CA 1C3
verify return:1
depth=0 CN = *.google.com.br
verify return:1
---
Certificate chain
0 s:CN = *.google.com.br
i:C = US, O = Google Trust Services LLC, CN = GTS CA 1C3

1 s:C = US, O = Google Trust Services LLC, CN = GTS CA 1C3
i:C = US, O = Google Trust Services LLC, CN = GTS Root R1

2 s:C = US, O = Google Trust Services LLC, CN = GTS Root R1
i:C = BE, O = GlobalSign nv-sa, OU = Root CA, CN = GlobalSign Root CA

Nesse exemplo, o certificado final é o *.google.com.br, que foi emitido pela CA intermediária GTS CA 1C3. Essa CA intermediária foi emitida por GTS Root R1.


O campo s representa o Subject, ou seja, para quem aquele certificado foi emitido. Já o campo i representa o Issuer, ou seja, quem emitiu e assinou aquele certificado.

Em alguns casos, a cadeia apresentada pelo servidor pode não terminar diretamente em um certificado autoassinado. Isso pode acontecer por causa de certificados intermediários, certificados raiz cruzados (cross-signed) ou pela forma como o cliente monta o caminho de confiança usando os certificados já instalados localmente.



Como gerar o certificado?


Em um ambiente Linux/Unix, o administrador normalmente começa gerando uma chave privada e uma CSR (Certificate Signing Request), que significa solicitação de assinatura de certificado.


A CSR é um arquivo enviado para uma Autoridade Certificadora, também chamada de CA. Esse arquivo contém informações sobre a entidade que está solicitando o certificado, como nome da organização, domínio, e-mail administrativo, localização e outros dados que podem ser exigidos pela CA.


Além dessas informações, a CSR também contém a chave pública associada à chave privada gerada no servidor.

A chave privada não é enviada para a CA. Ela deve permanecer protegida no servidor, porque é usada pelo serviço para provar que ele realmente possui a chave correspondente ao certificado.


Depois que a CSR é enviada para a CA, a autoridade certificadora valida as informações recebidas. Se tudo estiver correto, a CA assina a solicitação e emite um certificado digital, que depois pode ser instalado no servidor web.


Em CAs públicas, como a Let's Encrypt, esse processo costuma ser automatizado, já que ferramentas como o certbot conseguem gerar a chave, solicitar o certificado, validar o domínio e configurar o servidor web de forma mais simples.


Para um ambiente de testes, também podemos criar um certificado autoassinado. Esse tipo de certificado permite usar criptografia na conexão, mas não será considerado confiável pelos navegadores, porque não foi assinado por uma CA reconhecida pelo sistema ou navegador.



Rollover de chave privada - Não cai na LPIC-2


O rollover de chave privada é o processo de troca planejada da chave privada usada por um certificado digital. Isso pode ser feito por segurança, por política interna, por suspeita de vazamento da chave antiga ou simplesmente como parte de uma rotina de renovação.


Quando uma nova chave privada é gerada, uma nova chave pública também passa a existir, porque as duas fazem parte do mesmo par criptográfico. Como o certificado digital contém a chave pública, não é possível simplesmente trocar a chave privada e continuar usando o mesmo certificado antigo.


O certificado antigo continua existindo e pode continuar válido até sua data de expiração, desde que seja usado com a chave privada antiga correspondente. Porém, se o administrador quiser usar uma nova chave privada, será necessário gerar uma nova CSR e solicitar um novo certificado, agora contendo a nova chave pública.


Em ambientes de produção, essa troca costuma ser feita com cuidado para evitar indisponibilidade. Normalmente o novo certificado é gerado antes da remoção do antigo, permitindo uma janela de sobreposição. Depois que o novo certificado é instalado e testado, o serviço passa a usar o novo par de arquivos.


Se a chave privada antiga tiver sido comprometida, o certificado antigo deve ser revogado na CA, porque alguém que tenha acesso a essa chave poderia tentar se passar pelo serviço enquanto o certificado ainda estiver válido.

Esse tema não costuma ser cobrado diretamente na LPIC-2, mas ajuda a entender por que a chave privada precisa ser protegida e por que um certificado está sempre ligado ao par de chaves usado na sua geração.



Gerando os Certificados


Agora vamos gerar os certificados que serão usados pelo Apache. Depois disso, vamos fazer a instalação deles no servidor web, indicando ao Apache onde estão o certificado e a chave privada.


Antes de começar, é necessário garantir que o Apache tenha suporte a SSL/TLS e que o openssl esteja instalado no sistema.

# Verificando os arquivos do módulo SSL:
$ sudo ls -l /etc/apache2/mods-available/*ssl*
-rw-r--r-- 1 root root 3110 Feb 23 2021 /etc/apache2/mods-available/ssl.conf
-rw-r--r-- 1 root root 97 Feb 23 2021 /etc/apache2/mods-available/ssl.load

# No Debian/Ubuntu, o módulo é referenciado pelo arquivo .load:
$ sudo grep '\.so' /etc/apache2/mods-available/ssl.load
LoadModule ssl_module /usr/lib/apache2/modules/mod_ssl.so

# Verificando se o OpenSSL está instalado:
$ dpkg -l openssl
ii openssl 1.1.1f-1ubuntu2.16 amd64 Secure Sockets Layer toolkit - cryptographic utility

No CentOS 7, o módulo SSL do Apache pode não estar instalado por padrão. Nesse caso, é necessário instalar o pacote mod_ssl. Já no Ubuntu/Debian, normalmente os arquivos do módulo ficam disponíveis em /etc/apache2/mods-available/, mas o módulo ainda pode precisar ser habilitado com a2enmod ssl.


sudo a2enmod ssl
sudo systemctl reload apache2

Também existe um arquivo chamado ssl.conf, que contém configurações gerais de SSL/TLS, como porta de escuta, protocolos permitidos e parâmetros criptográficos.

$ ls -lh /etc/apache2/mods-available/ssl.conf
-rw-r--r-- 1 root root 3.1K Feb 23 2021 /etc/apache2/mods-available/ssl.conf

Esses arquivos possuem configurações gerais para o SSL como: porta que vai ouvir, versões dos protocolos e quais protocolos (SSL/TLS).



Gerando a chave privada e o CSR


O primeiro passo é gerar a chave privada. Essa chave deve ficar protegida no servidor, porque ela é usada para provar que o servidor realmente possui a chave correspondente ao certificado.

# Criando um diretório para armazenar os arquivos:
$ sudo mkdir /etc/apache2/ssl

# Entrando no diretório:
cd /etc/apache2/ssl

# Gerando a chave privada:
$ sudo openssl genrsa -des3 -out /etc/apache2/ssl/test.com.ze.key 2048

Também é possível gerar a chave privada protegida por senha usando -des3. Porém, em servidores web, isso pode exigir a senha sempre que o Apache for iniciado ou reiniciado. Por isso, em ambientes reais, é comum usar uma chave sem senha, protegendo o arquivo com permissões adequadas no sistema.


Podemos verificar se a chave privada foi gerada corretamente com o comando abaixo:

sudo openssl rsa -noout -in test.com.ze.key

Se a chave estiver correta, o comando não deve retornar erro.


Depois da chave privada, podemos gerar a CSR (Certificate Signing Request).

sudo openssl req -new \
-key /etc/apache2/ssl/test.com.ze.key \
-out /etc/apache2/ssl/test.com.ze.csr \
-subj "/C=BR/ST=SP/L=Sao Paulo/O=Test Company/CN=test.com.ze" \
-addext "subjectAltName=DNS:test.com.ze"

A CSR contém as informações da entidade que está solicitando o certificado e também a chave pública associada à chave privada. A chave privada não é enviada para a CA. Podemos visualizar o conteúdo da CSR com o comando abaixo:

sudo openssl req -in /etc/apache2/ssl/test.com.ze.csr -text -noout


Criando uma CA local para testes


Em um ambiente real, a CSR normalmente seria enviada para uma CA pública ou para uma CA interna da empresa. Como aqui estamos em um ambiente de teste, vamos criar uma CA local e usar essa CA para assinar o certificado.


# Criando uma CA local:
sudo /usr/lib/ssl/misc/CA.pl -newca

Se você obter erros no comando acima, tente atualizar o 'openssl' e 'libssl1.1' !


Depois da criação, o certificado da CA ficará disponível em:

ls demoCA/cacert.pem

Esse processo cria uma CA local apenas para teste. Essa CA terá seu próprio certificado e sua própria chave privada. O certificado da CA será usado para validar os certificados emitidos por ela, enquanto a chave privada da CA será usada para assinar novos certificados.

No CentOS 8, o CA.pl pode estar em /usr/bin/CA.pl. Caso o arquivo não exista, instale o pacote openssl-perl.



Assinando o certificado com a CA local


Agora vamos assinar o CSR usando a CA local criada anteriormente.

# Copiando a CSR para o nome esperado pelo script:
sudo cp /etc/apache2/ssl/test.com.ze.csr newreq.pem

# Assinando a CSR:
sudo /usr/lib/ssl/misc/CA.pl -sign

# Copiando o certificado assinado para o diretório do Apache:
sudo cp newcert.pem /etc/apache2/ssl/test.com.ze.crt

Depois disso, teremos dois arquivos principais para configurar no Apache:

  • test.com.ze.key -> chave privada
  • test.com.ze.crt -> certificado assinado


Diferença entre KEY, CSR e CRT


A chave privada, geralmente salva com extensão .key, é o arquivo mais sensível do processo. Ela deve permanecer protegida no servidor.


O CSR, geralmente salvo com extensão .csr, é a solicitação de assinatura do certificado. Ela contém informações da entidade e a chave pública, mas não contém a chave privada.


O certificado, geralmente salvo com extensão .crt ou .pem, é o arquivo emitido pela CA após a assinatura da CSR. Esse é o arquivo configurado no servidor web para ser apresentado aos clientes.

  • .key -> chave privada
  • .csr -> solicitação de assinatura
  • .crt -> certificado emitido pela CA

O CSR não precisa ser tratado como segredo da mesma forma que a chave privada. O que precisa ser protegido com mais cuidado é a chave privada, porque quem possui essa chave pode tentar se passar pelo serviço.



Entendendo o campo CA:FALSE


Ao visualizar um certificado emitido para um servidor, é comum encontrar a extensão Basic Constraints com o valor CA:FALSE.

X509v3 Basic Constraints:
CA:FALSE

Isso significa que esse certificado não pode ser usado para emitir outros certificados. Ele é um certificado de entidade final, usado por um servidor, usuário, serviço ou dispositivo.


Certificados que podem assinar outros certificados possuem CA:TRUE.

X509v3 Basic Constraints:
CA:TRUE

Esse tipo de certificado normalmente é usado por uma CA raiz ou por uma CA intermediária. Por isso, em um certificado comum de servidor web, o esperado é encontrar CA:FALSE.



Criando um certificado autoassinado com CA:TRUE


Caso o objetivo seja criar uma CA local para testes, podemos gerar um certificado autoassinado com permissão para assinar outros certificados.

# Gerando a chave privada da CA:
openssl genrsa -out CA.key 2048

# Gerando o certificado autoassinado da CA:
openssl req -new -x509 -days 3650 \
-key CA.key \
-out CA.crt \
-subj "/C=BR/ST=SP/L=Sao Paulo/O=Test Company/CN=Test Local CA"

Dependendo da configuração do OpenSSL, pode ser necessário informar explicitamente as extensões de CA para garantir que o certificado seja criado com CA:TRUE.


Podemos verificar com:

openssl x509 -in CA.crt -text -noout | grep -A2 -i "Basic Constraints"
X509v3 Basic Constraints:
CA:TRUE

Depois disso, esse certificado pode ser usado como uma raiz de confiança em um ambiente de teste ou intranet.

Esse tipo de certificado não deve ser usado como CA pública. Para que os clientes confiem nele, é necessário importar o certificado da CA local na lista de autoridades confiáveis do sistema operacional, navegador ou aplicação.



Gerando um certificado autoassinado


Também é possível gerar a chave privada e o certificado autoassinado com um único comando, sem passar pelo processo de CSR e assinatura por uma CA separada.

sudo openssl req -new -x509 -days 365 -nodes \
-out /etc/apache2/ssl/zona.com.ze.crt \
-keyout /etc/apache2/ssl/zona.com.ze.key \
-subj "/C=BR/ST=SP/L=Sao Paulo/O=Test Company/CN=zona.com.ze"

Esse comando cria um certificado autoassinado. Ele permite usar criptografia na conexão, mas não será considerado confiável pelos navegadores ou sistemas clientes, a menos que o certificado seja instalado manualmente como confiável.

Para testes em navegadores modernos, o ideal é incluir também o campo Subject Alternative Name (SAN), porque apenas o CN normalmente não é suficiente para validação do nome do certificado.



Instalando os Certificados


Agora vamos ver como instalar o certificado no VirtualHost HTTPS do Apache. Neste exemplo, vamos considerar que já temos os seguintes arquivos:

  • Ubuntu

    • /etc/apache2/ssl/zona.com.ze.crt -> certificado do servidor
    • /etc/apache2/ssl/zona.com.ze.key -> chave privada do servidor
  • RedHat

    • /etc/httpd/ssl/zona.com.ze.crt -> certificado do servidor
    • /etc/httpd/ssl/zona.com.ze.key -> chave privada do servidor

O processo será feito no Ubuntu. Edite o arquivo do VirtualHost:

sudo vim /etc/apache2/sites-enabled/vagranthome.conf

Adicione ou ajuste a configuração do VirtualHost na porta 443:

/etc/apache2/sites-enabled/vagranthome.conf
<VirtualHost *:443>
ServerAdmin fulano@gmail.com
ServerName zona.com.ze

# Habilita SSL/TLS nesse VirtualHost:
SSLEngine on

# Certificado do servidor:
SSLCertificateFile /etc/apache2/ssl/zona.com.ze.crt

# Chave privada correspondente ao certificado:
SSLCertificateKeyFile /etc/apache2/ssl/zona.com.ze.key
</VirtualHost>

Depois, teste a configuração do Apache:

sudo apachectl configtest

Se o resultado for Syntax OK, reinicie ou recarregue o serviço:

sudo systemctl restart apache2

Algumas outras opções que podemos usar:

DiretivaDescrição
SSLEngineHabilita ou desabilita SSL/TLS no VirtualHost. Para HTTPS, deve estar como on.
SSLCertificateFileInforma o arquivo do certificado do servidor. Em versões atuais do Apache 2.4, esse arquivo também pode conter os certificados intermediários da cadeia.
SSLCertificateKeyFileInforma o arquivo da chave privada correspondente ao certificado do servidor.
SSLCACertificateFileInforma um arquivo com CAs usadas para validar certificados de clientes. Essa diretiva é mais comum em cenários com autenticação mútua por certificado, também chamada de mTLS.


Certificado intermediário


Um certificado intermediário é um certificado emitido por uma CA raiz ou por outra CA intermediária. Ele é usado para assinar certificados finais, como o certificado de um servidor web.


Na prática, a cadeia normalmente fica assim:

CA raiz
└── CA intermediária
└── Certificado do servidor

A CA raiz é a âncora de confiança, porque normalmente já está instalada como confiável no sistema operacional, navegador ou aplicação. Porém, por segurança, a chave privada da CA raiz costuma ficar protegida e offline. Por isso, as CAs públicas geralmente usam certificados intermediários para emitir certificados finais.


Dessa forma, se uma CA intermediária tiver algum problema, ela pode ser revogada sem expor diretamente a chave da CA raiz.



Como informar a cadeia intermediária no Apache


Quando configuramos HTTPS no Apache, não basta pensar apenas no certificado do servidor e na chave privada. Em muitos casos, também existe uma cadeia de certificados intermediários entre o certificado do site e a CA raiz confiável.


Na prática, a cadeia normalmente fica assim:

CA raiz
└── CA intermediária
└── Certificado do servidor

O certificado do servidor é o certificado emitido para o site, como zona.com.ze. Esse certificado foi assinado por uma CA intermediária. A CA intermediária, por sua vez, foi assinada por uma CA raiz ou por outra intermediária acima dela.


Durante a conexão TLS, o Apache precisa apresentar ao cliente o certificado do servidor e os certificados intermediários necessários para que o cliente consiga montar o caminho até uma CA raiz confiável.


Em versões atuais do Apache 2.4, isso normalmente é feito colocando o certificado do servidor e os intermediários no mesmo arquivo configurado em SSLCertificateFile.


A ordem correta é:

certificado do servidor
certificado intermediário 1
certificado intermediário 2

Um exemplo para montar seria juntar todos os CRTs em um único CRT:

cat zona.com.ze.crt intermediario.crt > zona.com.ze-fullchain.crt

Depois, no VirtualHost:

SSLCertificateFile /etc/apache2/ssl/zona.com.ze-fullchain.crt
SSLCertificateKeyFile /etc/apache2/ssl/zona.com.ze.key

Nesse exemplo, o arquivo zona.com.ze-fullchain.crt contém o certificado do servidor e o certificado intermediário. Já o arquivo zona.com.ze.key contém a chave privada correspondente ao certificado do servidor.


A CA raiz normalmente não precisa ser enviada pelo servidor, porque ela já deve existir na lista de autoridades confiáveis do sistema operacional, navegador ou aplicação cliente.

Em versões antigas do Apache, era comum usar a diretiva SSLCertificateChainFile para informar os certificados intermediários separadamente. Em versões atuais do Apache 2.4, o mais comum é colocar a cadeia dentro do próprio arquivo informado em SSLCertificateFile.


Nós podemos conseguir o certificado intermediário de três formas principais. A forma correta é pegar direto com a CA que emitiu o certificado. Quando uma CA emite um certificado, ela normalmente fornece algo como:

certificado do servidor
certificado intermediário
cadeia completa / bundle / fullchain

Dependendo da CA, o nome pode variar, como:

  • chain.pem
  • ca-bundle.crt
  • fullchain.pem

No caso da Let’s Encrypt, por exemplo, o certbot já deixa isso pronto:

  • /etc/letsencrypt/live/dominio/cert.pem -> certificado do site
  • /etc/letsencrypt/live/dominio/privkey.pem -> chave privada
  • /etc/letsencrypt/live/dominio/chain.pem -> intermediário(s)
  • /etc/letsencrypt/live/dominio/fullchain.pem -> certificado do site + intermediário(s)

Normalmente usamos o fullchain.pem:

SSLCertificateFile /etc/letsencrypt/live/dominio/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/dominio/privkey.pem

Se for certificado comprado/emitido por uma CA, ai baixamos no painel da CA, isso pode variar de acordo com a CA que emitiu.


Se você já tem o domínio funcionando, pode ver a cadeia que o servidor está entregando com:

openssl s_client -connect dominio.com:443 -servername dominio.com -showcerts

Isso mostra os certificados enviados pelo servidor. O primeiro geralmente é o certificado do site, e os próximos são os intermediários. Mas para configurar o seu servidor, o ideal é pegar a cadeia pela CA oficial, não copiar de qualquer lugar.


Com CA local, provavelmente você não possui certificado intermediário, nesse caso, o arquivo da sua CA local é o próprio certificado usado como raiz de confiança:

  • CentOS: /etc/pki/CA/cacert.pem
  • Ubuntu: ./demoCA/cacert.pem

Esse cacert.pem não é exatamente um intermediário, ele é o certificado da sua CA local. Para o cliente confiar no certificado do servidor, você instala esse cacert.pem como CA confiável no cliente.



Autenticação por Certificado de Cliente - mTLS


Também é possível configurar o Apache para permitir o acesso a um site, ou a uma parte dele, somente quando o cliente apresentar um certificado válido. Esse tipo de configuração é conhecido como autenticação por certificado de cliente ou mTLS (mutual TLS).


No TLS comum, apenas o servidor apresenta um certificado para o cliente. É isso que acontece quando acessamos um site HTTPS normal, já no mTLS, os dois lados apresentam certificados. Dessa forma, além de criptografar a conexão, o servidor também consegue autenticar quem está acessando o recurso.


Para que isso funcione, o certificado do cliente precisa ter sido assinado por uma CA que o Apache confia. Essa CA é informada na configuração através da diretiva SSLCACertificateFile.



Configurando o Apache


Neste exemplo, vamos proteger o diretório /var/www/html/cert, exigindo certificado de cliente apenas para esse caminho.

<VirtualHost *:443>
ServerAdmin fulano@gmail.com
ServerName test.com.ze
DocumentRoot /var/www/html

# Habilita SSL/TLS nesse VirtualHost
SSLEngine on

# Certificado do servidor
SSLCertificateFile /etc/apache2/ssl/test.com.ze.crt

# Chave privada do servidor
SSLCertificateKeyFile /etc/apache2/ssl/test.com.ze.key

# CA usada para validar os certificados dos clientes
SSLCACertificateFile /etc/apache2/ssl/cacert.pem

# Exige certificado de cliente apenas nesse diretório
<Directory /var/www/html/cert>
SSLVerifyClient require
</Directory>
</VirtualHost>

As diretivas principais para autenticação do cliente são:

SSLCACertificateFile /etc/apache2/ssl/cacert.pem

<Directory /var/www/html/cert>
SSLVerifyClient require
</Directory>

A diretiva SSLCACertificateFile informa ao Apache qual CA deve ser usada para validar os certificados apresentados pelos clientes. Já a diretiva SSLVerifyClient require obriga o cliente a apresentar um certificado válido. Se o cliente não apresentar um certificado, ou se o certificado não tiver sido assinado por uma CA confiável para o Apache, o acesso será negado.


Se a intenção for exigir certificado para o site inteiro, o SSLVerifyClient require pode ser colocado diretamente dentro do VirtualHost, fora do bloco Directory. Exemplo:

<VirtualHost *:443>
ServerName test.com.ze
DocumentRoot /var/www/html

SSLEngine on
SSLCertificateFile /etc/apache2/ssl/test.com.ze.crt
SSLCertificateKeyFile /etc/apache2/ssl/test.com.ze.key

SSLCACertificateFile /etc/apache2/ssl/cacert.pem
SSLVerifyClient require
</VirtualHost>

Nesse caso, o certificado do cliente será exigido para todo o VirtualHost.



Gerando o certificado do cliente


Agora vamos criar um certificado para o cliente, esse certificado será assinado pela mesma CA local usada pelo Apache para validar os clientes.


Entre no diretório onde os arquivos serão criados:

cd /etc/apache2/ssl

Gere a chave privada do cliente:

openssl genrsa -des3 -out client.key 2048

Depois, gere a CSR do cliente:

openssl req -new \
-key client.key \
-out client.csr \
-subj "/C=BR/ST=SP/L=Sao Paulo/O=Fulano da Silva Junior/CN=Fulano da Silva Junior/emailAddress=fulano@exemplo.com.br"

A CSR contém as informações do cliente e a chave pública correspondente à chave privada. Ela será usada pela CA para emitir o certificado do cliente. Copie a CSR para o nome esperado pelo script da CA:

cp client.csr newreq.pem

Assine a CSR usando a CA local:

sudo /usr/lib/ssl/misc/CA.pl -sign

Em sistemas Red Hat-like, o script pode ficar em outro caminho, como /etc/pki/tls/misc/CA.


Depois da assinatura, o certificado será gerado como newcert.pem. Renomeie o arquivo:

mv newcert.pem fulano.crt

Agora temos os principais arquivos do cliente:

  • client.key -> chave privada do cliente
  • client.csr -> solicitação de assinatura
  • fulano.crt -> certificado do cliente assinado pela CA


Gerando o arquivo P12


Para importar o certificado no navegador, normalmente usamos o formato PKCS#12, que costuma ter extensão .p12 ou .pfx. Esse arquivo pode conter o certificado do cliente e a chave privada correspondente. Por isso, ele deve ser protegido por senha.

openssl pkcs12 -export \
-inkey client.key \
-in fulano.crt \
-out fulano.p12

Durante a geração, será solicitada a senha da chave privada e depois uma senha de exportação para proteger o arquivo .p12. Depois disso, o arquivo fulano.p12 pode ser importado no navegador ou no sistema operacional do cliente.


Diferença entre os arquivos

  • client.key -> chave privada do cliente
  • client.csr -> pedido de certificado do cliente
  • fulano.crt -> certificado do cliente assinado pela CA
  • fulano.p12 -> pacote com certificado e chave privada para importar no navegador
  • cacert.pem -> certificado da CA que o Apache usa para validar o cliente

O arquivo .p12 precisa ser protegido, porque ele carrega a chave privada do cliente. Quem tiver acesso a esse arquivo e à senha poderá se autenticar como aquele cliente.



Erro de Post-Handshake Authentication


Em alguns cenários, principalmente quando o certificado de cliente é exigido apenas em um diretório específico, pode aparecer o erro abaixo:

You don't have permission to access this resource.
Reason: Cannot perform Post-Handshake Authentication.

Isso pode acontecer porque o Apache só descobre que precisa pedir o certificado depois que a requisição HTTP já foi recebida. Com TLS 1.3, essa autenticação tardia depende do recurso chamado post-handshake authentication.


Uma forma simples de evitar esse problema em laboratório é exigir o certificado do cliente no VirtualHost inteiro, colocando SSLVerifyClient require diretamente dentro do VirtualHost, e não apenas dentro do bloco Directory.


No Firefox, também é possível testar habilitando a opção abaixo em about:config:

security.tls.enable_post_handshake_auth = true

Porém, para documentação e laboratório, costuma ser mais simples exigir o certificado no VirtualHost inteiro.



Configurações de Segurança


Vamos aplicar algumas configurações de segurança no Apache, começando pela parte de SSL/TLS. Essas configurações podem ficar em locais diferentes dependendo da distribuição.


No Debian/Ubuntu, parte das configurações fica em:

/etc/apache2/mods-enabled/ssl.conf

No CentOS/Red Hat-like, normalmente fica em:

/etc/httpd/conf.d/ssl.conf

Também existem configurações gerais de segurança que, no Debian/Ubuntu, costumam ficar em:

/etc/apache2/conf-available/security.conf

Esse arquivo é importante porque algumas diretivas, como ServerTokens, ServerSignature e TraceEnable, podem já estar definidas nele. Se a mesma diretiva for configurada em outro lugar, pode não surtir o efeito esperado ou pode gerar conflito, dependendo da ordem em que os arquivos são carregados.


Diretivas relacionadas a SSL/TLS:

OpçãoDescrição
SSLProtocolDefine quais versões de SSL/TLS serão aceitas pelo servidor. Essa diretiva é usada para desabilitar protocolos antigos e permitir apenas versões consideradas seguras.
SSLCipherSuiteDefine quais cifras serão aceitas durante a negociação TLS. As cifras são separadas por : e podem ser incluídas ou removidas da lista.
SSLHonorCipherOrderDefine se o servidor deve preferir a ordem de cifras configurada nele ou aceitar a preferência enviada pelo cliente.
SSLOpenSSLConfCmdPermite passar configurações específicas para o OpenSSL usado pelo Apache. Pode ser usado para ajustes mais avançados.
SSLSessionTicketsControla o uso de tickets de sessão TLS, usados para reaproveitar sessões e reduzir o custo de novas conexões.
SSLCompressionControla a compressão na camada SSL/TLS. Normalmente deve ficar desabilitada, porque compressão em TLS pode abrir espaço para ataques como CRIME.
SSLUseStaplingHabilita OCSP Stapling, que permite ao servidor enviar ao cliente uma prova recente de que o certificado ainda é válido.
Header always set Strict-Transport-SecurityDefine o cabeçalho HSTS, usado para instruir o navegador a acessar o site somente via HTTPS por um determinado período.


SSLProtocol


A diretiva SSLProtocol define quais versões de SSL/TLS o Apache deve aceitar, exemplo:

SSLProtocol all -SSLv3

Nesse exemplo, o Apache permite todos os protocolos disponíveis, exceto o SSLv3. Também podemos fazer o contrário, desabilitando todos e habilitando apenas versões específicas:

SSLProtocol -all +TLSv1.2 +TLSv1.3

O sinal de menos remove uma versão da lista. O sinal de mais adiciona uma versão. Em ambientes atuais, o ideal é evitar protocolos antigos como SSLv2, SSLv3, TLS 1.0 e TLS 1.1. Sempre que possível, use TLS 1.2 e TLS 1.3.



SSLCipherSuite


A diretiva SSLCipherSuite define as cifras que o Apache pode usar durante a negociação TLS, exemplo:

SSLCipherSuite HIGH:!aNULL:!MD5

As cifras são separadas por :. O sinal de exclamação remove uma cifra ou grupo de cifras da lista.

  • HIGH -> permite cifras consideradas fortes
  • !aNULL -> remove cifras sem autenticação
  • !MD5 -> remove cifras baseadas em MD5

Essa configuração é importante porque o TLS não usa apenas um algoritmo. Durante a negociação, cliente e servidor precisam escolher um conjunto de algoritmos para troca de chaves, autenticação, criptografia e integridade.

Em TLS 1.3, o controle das cifras funciona de forma diferente das versões anteriores. Por isso, algumas configurações feitas em SSLCipherSuite podem afetar apenas TLS 1.2 e versões anteriores, dependendo da versão do Apache e do OpenSSL.



SSLHonorCipherOrder


A diretiva SSLHonorCipherOrder controla quem define a preferência das cifras durante a negociação TLS, exemplo:

SSLHonorCipherOrder on

Quando essa opção está como on, o servidor usa a ordem definida na configuração do Apache. Isso permite que o administrador defina quais cifras devem ser preferidas. Quando está como off, a preferência do cliente pode ser usada. Nesse caso, mesmo que o servidor suporte cifras mais fortes, o cliente pode acabar negociando outra cifra permitida.


Em servidores onde queremos maior controle da política criptográfica, é comum deixar essa opção como on.



SSLSessionTickets


A diretiva SSLSessionTickets controla o uso de tickets de sessão TLS, exemplo:

SSLSessionTickets off

Tickets de sessão permitem que o cliente reutilize informações de uma sessão TLS anterior, reduzindo o custo de novas conexões. Isso melhora desempenho, porque nem toda conexão precisa passar por uma negociação completa.


Por outro lado, dependendo da configuração e da política de rotação das chaves usadas nos tickets, isso pode ter impacto em propriedades como sigilo futuro (forward secrecy). Por isso, em configurações mais rígidas, é comum desabilitar SSLSessionTickets.


O que são tickets?

Tickets de sessão TLS são informações geradas pelo servidor para permitir que uma conexão TLS seja reutilizada depois.


Quando um cliente acessa um site HTTPS pela primeira vez, ele precisa fazer o handshake TLS completo com o servidor. Nesse processo, o cliente e o servidor negociam os parâmetros da conexão, validam o certificado e criam as chaves usadas para proteger a comunicação.


Depois que essa conexão é estabelecida, o servidor pode enviar um ticket de sessão para o cliente. Esse ticket funciona como uma referência temporária daquela sessão TLS. Em uma próxima conexão, o cliente pode apresentar esse ticket ao servidor para tentar retomar a sessão anterior, sem precisar repetir todo o processo do handshake completo.


A ideia é parecida com isto:

  • primeira conexão
    Cliente faz handshake TLS completo com o servidor

  • próxima conexão
    Cliente apresenta o ticket e tenta retomar a sessão TLS anterior


Com isso, a conexão pode ser estabelecida mais rapidamente e com menos custo para o servidor, porque parte da negociação TLS pode ser reaproveitada.



SSLCompression


A diretiva SSLCompression controla a compressão na camada SSL/TLS, exemplo:

SSLCompression off

Apesar de compressão parecer uma melhoria de desempenho, ela pode abrir espaço para ataques como CRIME. Por isso, a recomendação comum é manter a compressão TLS desabilitada. Em versões mais novas do Apache/OpenSSL, essa opção pode nem estar disponível ou a compressão pode já estar desabilitada por padrão.



SSLUseStapling


A diretiva SSLUseStapling habilita o OCSP Stapling, exemplo:

SSLUseStapling on

Normalmente também é necessário configurar um cache para o stapling:

SSLStaplingCache shmcb:/var/run/ocsp(128000)

O OCSP é usado para verificar se um certificado foi revogado. Sem OCSP Stapling, o cliente pode precisar consultar a CA para saber se o certificado ainda é válido. Com OCSP Stapling, o próprio servidor consulta periodicamente a CA e envia essa resposta ao cliente durante a conexão TLS. Isso melhora o desempenho e evita que cada cliente precise consultar a CA diretamente.



HSTS


O HSTS, ou HTTP Strict Transport Security, é uma política enviada pelo servidor através de um cabeçalho HTTP. Ela informa ao navegador que aquele site deve ser acessado somente via HTTPS, exemplo:

Header always set Strict-Transport-Security "max-age=63072000"

O valor max-age=63072000 indica que o navegador deve guardar essa política por 63072000 segundos, o que equivale a aproximadamente 2 anos. Depois que o navegador recebe esse cabeçalho, ele passa a acessar o site diretamente por HTTPS, mesmo que o usuário tente usar HTTP.


Para usar essa diretiva, o módulo headers precisa estar habilitado.

# No Debian/Ubuntu:
sudo a2enmod headers
sudo systemctl reload apache2

Tenha cuidado ao aplicar HSTS em produção. Se ele for configurado com um tempo muito alto e o HTTPS estiver incorreto, os usuários podem ficar impedidos de acessar o site por HTTP durante o período definido.



Diretivas relacionadas à exposição de informações


Além das configurações de SSL/TLS, também é importante reduzir a quantidade de informações que o Apache expõe nas respostas HTTP e nas páginas de erro.

OpçãoDescrição
ServerTokensControla quanta informação o Apache mostra no cabeçalho Server.
ServerSignatureControla se o Apache exibe informações do servidor nas páginas de erro geradas automaticamente.
TraceEnableHabilita ou desabilita o método HTTP TRACE.


ServerTokens


A diretiva ServerTokens controla a quantidade de informação exibida no cabeçalho Server, exemplo:

ServerTokens Prod

Com a configuração padrão, o Apache pode expor informações como versão do servidor web e sistema operacional, exemplo:

curl --head http://192.168.121.209
HTTP/1.1 200 OK
Date: Mon, 13 Feb 2023 14:47:47 GMT
Server: Apache/2.4.41 (Ubuntu)
Content-Type: text/html;charset=UTF-8

Depois de aplicar:

ServerTokens Prod

A saída fica mais enxuta:

curl --head http://192.168.121.209
HTTP/1.1 200 OK
Date: Mon, 13 Feb 2023 15:55:18 GMT
Server: Apache
Content-Type: text/html;charset=UTF-8

Isso não corrige uma vulnerabilidade por si só, mas reduz a exposição de informações do ambiente. Um atacante ainda pode tentar identificar a versão por outros meios, mas não é interessante entregar essa informação diretamente no cabeçalho HTTP.


No Debian/Ubuntu, essa configuração costuma ficar em:

/etc/apache2/conf-available/security.conf


ServerSignature


A diretiva ServerSignature controla se o Apache deve exibir informações do servidor em páginas de erro geradas automaticamente, exemplo:

ServerSignature Off

Quando essa opção está habilitada, páginas de erro podem exibir informações como nome do servidor, versão do Apache, porta e, em alguns casos, o e-mail definido em ServerAdmin.


Por segurança, é comum deixar essa opção como Off. No Debian/Ubuntu, essa configuração também costuma ficar em:

/etc/apache2/conf-available/security.conf


TraceEnable


A diretiva TraceEnable controla o método HTTP TRACE, exemplo:

TraceEnable Off

O método TRACE faz o servidor devolver a requisição recebida. Ele pode ser útil para depuração, mas normalmente não é necessário em produção. Com o TRACE habilitado, o teste abaixo pode retornar a própria requisição:

curl -v -X TRACE http://192.168.121.209
< HTTP/1.1 200 OK
< Content-Type: message/http

TRACE / HTTP/1.1
Host: 192.168.121.209
User-Agent: curl/7.81.0
Accept: */*

Com o TRACE desabilitado, o Apache deve retornar algo como:

HTTP/1.1 405 Method Not Allowed

Isso indica que o método TRACE não está permitido para aquela URL.



Exemplo de configuração básica


Um exemplo simples de configuração de segurança ficaria assim:

ServerTokens Prod
ServerSignature Off
TraceEnable Off

SSLProtocol -all +TLSv1.2 +TLSv1.3
SSLCipherSuite HIGH:!aNULL:!MD5
SSLHonorCipherOrder on
SSLCompression off

Header always set Strict-Transport-Security "max-age=63072000"

No Debian/Ubuntu, lembre-se de habilitar o módulo headers caso use HSTS:

sudo a2enmod headers

Depois de alterar a configuração, valide a sintaxe:

sudo apachectl configtest

Se estiver tudo certo, recarregue o Apache:

sudo systemctl reload apache2

Em sistemas Red Hat-like:

sudo systemctl reload httpd


Testando a configuração


Para ver o cabeçalho Server:

curl --head http://192.168.121.209

Para testar via HTTPS ignorando o alerta de certificado, quando for certificado de laboratório:

curl -k --head https://192.168.121.209

Para testar se o método TRACE está desabilitado:

curl -v -X TRACE http://192.168.121.209

Também podemos usar ferramentas externas para avaliar a configuração TLS do servidor, como o SSL Configuration Generator da Mozilla, o Cipherlist e serviços de teste como o TOP do NIC.br.

Essas ferramentas ajudam a criar uma configuração inicial mais segura, mas a configuração ideal pode variar conforme a versão do Apache, OpenSSL, distribuição Linux e compatibilidade necessária com clientes antigos.



Hardening do Apache


Vamos ver algumas configurações de hardening no Apache, começando pela parte de SSL/TLS.


Uma das configurações possíveis é definir parâmetros Diffie-Hellman para uso em negociações de chave. Esses parâmetros são usados em conexões TLS que utilizam cifras baseadas em DHE, permitindo que cliente e servidor negociem uma chave de sessão sem que essa chave precise trafegar diretamente pela rede.


Neste exemplo, vamos usar o grupo ffdhe4096. O termo ffdhe significa Finite Field Diffie-Hellman Ephemeral. Ele indica o uso de Diffie-Hellman efêmero baseado em campo finito, já o número 4096 representa o tamanho do grupo usado na negociação, em bits.


É importante entender que o arquivo ffdhe4096 não é uma chave privada do servidor. Ele representa parâmetros públicos usados no processo de troca de chaves. A chave de sessão real é criada durante a negociação TLS e é temporária.


A ideia é mais ou menos assim:

  • parâmetros Diffie-Hellman -> usados na negociação TLS
  • chave de sessão -> criada durante a conexão

O uso de parâmetros efêmeros ajuda a fornecer sigilo futuro (forward secrecy). Isso significa que, mesmo que a chave privada do servidor seja comprometida no futuro, conexões antigas não poderão ser descriptografadas apenas com essa chave, porque cada sessão usou chaves temporárias próprias.


Esses parâmetros podem ser obtidos em fontes confiáveis, como o repositório do projeto Internet Standards, ou gerados localmente com o OpenSSL. Para mais detalhes, consulte o repositório do projeto no GitHub.



Diffie-Hellman


O Diffie-Hellman é um método de troca de chaves criptográficas, ele permite que duas partes criem um segredo compartilhado mesmo usando um canal de comunicação inseguro.


Esse método foi apresentado por Whitfield Diffie e Martin Hellman em 1976 e ainda é usado como base em vários protocolos de segurança, como HTTPS, VPNs e outros mecanismos de comunicação segura.


A ideia principal é que o cliente e o servidor não precisam enviar a chave secreta diretamente pela rede. Em vez disso, eles trocam informações públicas e, a partir dessas informações, cada lado consegue calcular o mesmo segredo compartilhado.


Esse segredo compartilhado pode então ser usado como base para criar as chaves de sessão, que serão usadas pelos algoritmos simétricos responsáveis por proteger os dados transmitidos.


É importante entender que o Diffie-Hellman não é usado para cifrar diretamente todos os dados da conexão. Ele é usado principalmente na etapa de negociação das chaves. Depois disso, a proteção dos dados normalmente é feita por algoritmos simétricos, como AES.


Em conexões TLS modernas, é comum o uso de variações efêmeras do Diffie-Hellman, como DHE ou ECDHE. O termo efêmero significa que as chaves usadas na negociação são temporárias, criadas para aquela sessão. Isso ajuda a fornecer sigilo futuro (forward secrecy), porque mesmo que a chave privada do servidor seja comprometida no futuro, sessões antigas não poderão ser descriptografadas apenas com ela.


Em poucas palavras, o Diffie-Hellman protege a troca/criação da chave de sessão. Depois que essa chave é criada, outros algoritmos (como AES) são usados para proteger os dados transmitidos.


A chave de sessão é uma chave temporária criada para proteger uma conexão específica. Depois que o cliente e o servidor criam essa chave, ela é usada pelos algoritmos de criptografia simétrica (como AES) para cifrar e decifrar os dados transmitidos durante aquela sessão.


Ela é chamada de chave de sessão porque normalmente vale apenas para aquela conexão. Em uma nova conexão, outra chave de sessão pode ser criada.


Chave AES

No TLS, a chave usada pelo AES não é definida manualmente pelo administrador e sim criada automaticamente durante o handshake TLS. O administrador configura quais protocolos e cifras o servidor aceita, mas a chave de sessão é gerada dinamicamente para cada conexão entre cliente e servidor.


O Diffie-Hellman, ou ECDHE em conexões mais modernas, ajuda cliente e servidor a chegarem em um segredo compartilhado sem enviar esse segredo diretamente pela rede. Depois disso, o TLS usa esse segredo para derivar as chaves de sessão.


Essas chaves de sessão são então usadas por algoritmos simétricos (como AES) para cifrar e decifrar os dados transmitidos.


Se o administrador não configurar parâmetros Diffie-Hellman manualmente, o TLS ainda pode funcionar normalmente, dependendo das versões do Apache, OpenSSL e das cifras habilitadas. Em conexões modernas, é comum o uso de ECDHE, que é uma variação do Diffie-Hellman baseada em curvas elípticas. Nesse caso, cliente e servidor negociam automaticamente um grupo/curva compatível durante o handshake TLS.


Já o cliente, não possui uma chave Diffie-Hellman fixa, ele possui suporte a determinados métodos de troca de chave, como ECDHE ou DHE. Durante cada conexão, valores temporários são gerados para permitir que cliente e servidor cheguem ao mesmo segredo compartilhado.

  1. O cliente informa os grupos/curvas que suporta;
  2. O servidor escolhe uma opção compatível;
  3. Os dois trocam valores públicos temporários;
  4. Cada lado usa seu valor privado temporário;
  5. Ambos chegam ao mesmo segredo compartilhado;
  6. O TLS vai então usar esse segredo para criar as chaves de sessão;

O arquivo ffdhe4096 é usado quando queremos definir parâmetros para DHE baseado em campo finito. Porém, em muitos ambientes atuais, o mais comum é usar ECDHE, que normalmente já é negociado automaticamente pelo OpenSSL/Apache.


Se cliente e servidor não tiverem nenhum método de troca de chave compatível, o handshake TLS falha e a conexão não é estabelecida.



Começando o Hardening


Vamos aplicar as configurações no Apache. A ideia é remover protocolos antigos, limitar as cifras aceitas, desabilitar recursos desnecessários e ativar opções que ajudam na validação do certificado.


Antes de alterar, é recomendado fazer backup do arquivo de configuração:

sudo cp /etc/apache2/mods-available/ssl.conf /etc/apache2/mods-available/ssl.conf.bkp

Em Debian/Ubuntu, o arquivo abaixo costuma ser usado para configurações globais do módulo SSL:

sudo vim /etc/apache2/mods-available/ssl.conf


Baixando os parâmetros Diffie-Hellman

Crie o diretório, caso ainda não exista:

sudo mkdir -p /etc/apache2/ssl

Entre no diretório:

cd /etc/apache2/ssl

Baixe o arquivo com parâmetros Diffie-Hellman de 4096 bits:

sudo wget https://raw.githubusercontent.com/internetstandards/dhe_groups/master/ffdhe4096.pem

Esse arquivo não é uma chave privada. Ele contém parâmetros públicos Diffie-Hellman, que podem ser usados em negociações DHE.


ECDHE

Hoje em dia, é mais comum configurar o servidor para preferir ECDHE nas conexões TLS. O ECDHE é mais usado em ambientes modernos porque trabalha com curvas elípticas (como X25519 e secp384r1), entregando boa segurança com menor custo computacional do que DHE tradicional.


Mesmo assim, ainda é possível manter parâmetros DHE fortes, como ffdhe4096.pem, para casos de compatibilidade. Nesse caso, o servidor tenta negociar ECDHE primeiro, caso o cliente não suporte ECDHE, mas ainda suporte DHE e exista uma cifra DHE habilitada, o servidor pode negociar DHE usando os parâmetros configurados, ou seja, o arquivo ffdhe4096.pem não é usado nas conexões ECDHE. Ele só entra na negociação quando uma cifra DHE é escolhida.


Um exemplo de configuração seria:

# Preferência para ECDHE, com DHE como alternativa.
SSLCipherSuite EECDH+AESGCM:EDH+AESGCM

# Curvas usadas em ECDHE.
SSLOpenSSLConfCmd ECDHParameters Automatic
SSLOpenSSLConfCmd Curves X25519:secp384r1:prime256v1

# Parâmetros usados apenas se DHE for negociado.
SSLOpenSSLConfCmd DHParameters "/etc/apache2/ssl/ffdhe4096.pem"

Nessa configuração, EECDH+AESGCM representa a preferência por ECDHE, enquanto EDH+AESGCM mantém DHE como alternativa. Se o objetivo for aceitar apenas clientes modernos, a parte EDH+AESGCM poderia ser removida, mas isso pode reduzir compatibilidade com clientes mais antigos.



Configurações TLS no Apache

Adicione as opções abaixo ao final do arquivo /etc/apache2/mods-available/ssl.conf:

# Desabilita compressão no nível TLS.
# A compressão TLS pode abrir espaço para ataques como 'CRIME'.
SSLCompression off

# Desabilita tickets de sessão TLS.
# Isso reduz problemas relacionados ao reaproveitamento de tickets antigos.
SSLSessionTickets off

# Deixa o OpenSSL selecionar automaticamente os parâmetros ECDH.
SSLOpenSSLConfCmd ECDHParameters Automatic

# Define a ordem preferida de curvas elípticas para ECDHE.
SSLOpenSSLConfCmd Curves X25519:secp521r1:secp384r1:prime256v1

# Define parâmetros Diffie-Hellman para cifras DHE.
# Esse arquivo é usado apenas quando uma cifra DHE for negociada.
SSLOpenSSLConfCmd DHParameters "/etc/apache2/ssl/ffdhe4096.pem"

# Ativa OCSP Stapling.
# Com isso, o Apache pode entregar a resposta OCSP durante o handshake TLS,
# evitando que o cliente precise consultar a CA diretamente.
SSLUseStapling On
SSLStaplingResponderTimeout 5
SSLStaplingReturnResponderErrors off
SSLStaplingCache shmcb:${APACHE_RUN_DIR}/ssl_stapling(32768)

O OCSP Stapling é usado para melhorar a validação do certificado, em vez de o navegador consultar a CA para verificar o status do certificado, o próprio servidor entrega uma resposta OCSP recente durante o handshake TLS.


Vamos focar em cada uma das opções agora!



Observação sobre os parâmetros DH

A linha abaixo só será relevante quando uma cifra DHE for usada:

SSLOpenSSLConfCmd DHParameters "/etc/apache2/ssl/ffdhe4096.pem"

Em ambientes modernos, normalmente ECDHE é mais comum que DHE. Mesmo assim, manter parâmetros DHE fortes evita que o servidor use parâmetros fracos caso uma cifra DHE seja negociada.


Se essa diretiva gerar erro em alguma versão específica do Apache ou OpenSSL, uma alternativa é anexar os parâmetros DH ao primeiro arquivo configurado em SSLCertificateFile. Essa abordagem também é documentada pelo Apache, mas deixa o arquivo de certificado menos limpo.



Protocolos TLS permitidos

Vamos melhorar a segurança aceitando apenas versões mais seguras do TLS. Primeiro, localize a diretiva SSLProtocol e altere para:

SSLProtocol -all +TLSv1.3 +TLSv1.2

Essa configuração desabilita tudo primeiro com -all e depois habilita somente TLS 1.2 e TLS 1.3. Os protocolos antigos (como SSLv3, TLS 1.0 e TLS 1.1) não devem ser usados, porque são versões antigas e consideradas inseguras para ambientes atuais.



Cifras permitidas

Vamos melhorar a segurança aceitando primeiro ECDH e depois DHE. Primeiro, localize a diretiva SSLCipherSuite e altere para:

SSLCipherSuite EECDH+AESGCM:EDH+AESGCM

Essa configuração permite cifras baseadas em ECDHE e DHE usando AES-GCM. Na prática, essa diretiva afeta principalmente as cifras usadas com TLS 1.2. Já o TLS 1.3 trabalha com um conjunto próprio de cifras e normalmente usa os padrões fornecidos pelo OpenSSL.



Ordem das cifras

Vamos configurar o servidor para usar uma ordem de preferência definida de cifras, em vez de aceitar diretamente a preferência enviada pelo cliente. Primeiro, localize a diretiva SSLHonorCipherOrder e deixe assim:

SSLHonorCipherOrder on


HSTS

O HSTS informa ao navegador que aquele site deve ser acessado somente via HTTPS. O ideal é configurar essa opção dentro do VirtualHost HTTPS do site:

<VirtualHost *:443>
ServerName exemplo.com

SSLEngine on

Header always set Strict-Transport-Security "max-age=63072000"
</VirtualHost>

O valor 63072000 representa dois anos em segundos. Use HSTS com cuidado, porque o navegador guarda essa política por um tempo. Se o HTTPS do site quebrar, o navegador pode continuar forçando o acesso por HTTPS até a política expirar.


Para usar a diretiva Header, o módulo headers precisa estar habilitado:

sudo a2enmod headers


Testando a configuração

Antes de reiniciar o Apache, teste a configuração:

sudo apachectl configtest

Se o resultado for Syntax OK, reinicie o serviço:

sudo systemctl restart apache2

Também é possível conferir se o Apache está escutando em HTTPS:

sudo ss -lntp | grep :443

E testar com curl:

curl -k -I https://localhost

Para verificar o protocolo e a cifra negociada:

openssl s_client -connect localhost:443 -tls1_2

# ou:
openssl s_client -connect localhost:443 -tls1_3


Server Name Indicator - SNI


O Server Name Indication (SNI) é uma extensão do TLS que permite ao cliente informar, durante o handshake TLS, qual nome de site ele está tentando acessar.


Isso é importante quando vários sites HTTPS estão hospedados no mesmo endereço IP. Antes de enviar o certificado ao cliente, o servidor precisa saber qual site foi solicitado para escolher o certificado correto. Sem SNI, o servidor receberia uma conexão TLS para um endereço IP, mas não saberia qual certificado deveria apresentar quando existissem vários sites HTTPS no mesmo IP, exemplo:

site1.exemplo.com -> 192.168.0.10
site2.exemplo.com -> 192.168.0.10

Os dois nomes apontam para o mesmo IP, mas cada site pode ter seu próprio VirtualHost e seu próprio certificado TLS.


Com SNI, o cliente informa o nome desejado no início da conexão TLS, exemplo:

Cliente acessa: https://site1.exemplo.com
Cliente envia no handshake TLS: site1.exemplo.com
Servidor escolhe o VirtualHost e o certificado de site1.exemplo.com

No Apache, isso normalmente é feito com VirtualHosts baseados em nome, usando diretivas como ServerName, ServerAlias, SSLCertificateFile e SSLCertificateKeyFile. Segue um exemplo bem simples:

<VirtualHost *:443>
ServerName site1.exemplo.com

SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/site1.exemplo.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/site1.exemplo.com/privkey.pem
</VirtualHost>

<VirtualHost *:443>
ServerName site2.exemplo.com

SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/site2.exemplo.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/site2.exemplo.com/privkey.pem
</VirtualHost>

Nesse cenário, os dois sites podem usar o mesmo endereço IP e a mesma porta 443, mas com certificados diferentes. Uma observação importante é que, no TLS tradicional, o SNI é enviado antes da conexão estar totalmente criptografada. Por isso, ele pode revelar o nome do site acessado para quem observa o tráfego de rede, mesmo assim, ele é amplamente usado porque resolve o problema prático de hospedar vários sites HTTPS no mesmo IP.



Comandos importantes


Baixar o certificado do servidor:

# Ver o certificado do Site:
$ openssl s_client -showcerts -connect test.com.ze:443 </dev/null

# Baixar o certificado do site:
$ openssl s_client -showcerts -connect test.com.ze:443 </dev/null 2>&- | openssl x509 -outform PEM > test.com.ze.pem

# Ver informações completas sobre o certificado:
$ openssl s_client -connect test.com.ze:443

# Testar senha (se não der erro, a senha está correta):
$ openssl rsa -noout -in ca.key

# Mostra o conteúdo do certificado:
$ openssl x509 -in test.com.ze.pem -text -noout

# Verifica a data de validade do certificado quando temos ele em formato de arquivo:
$ openssl x509 -enddate -noout -in test.com.ze.pem
notAfter=Feb 7 20:55:55 2024 GMT

# Verifica a data de validade do certificado direto do servidor:
$ openssl s_client -servername test.com.ze -connect test.com.ze:443 </dev/null 2>&- | openssl x509 -noout -dates
notBefore=Feb 7 20:55:55 2023 GMT
notAfter=Feb 7 20:55:55 2024 GMT

# Verificar outros dominios do qual um certificado atende:
$ openssl s_client -showcerts -servername ipv6.br -connect ipv6.br:443 </dev/null 2>&- | openssl x509 -noout -ext subjectAltName
X509v3 Subject Alternative Name:
DNS:curso.ipv6.br, DNS:ds.ipv6.br, DNS:ipv6.br, DNS:ipv6.nic.br, DNS:v4.ipv6.br, DNS:v6.ipv6.br, DNS:www.ipv6.br, DNS:www.ipv6.nic.br


Fontes importantes


https://www.ibm.com/docs/en/ibm-mq/9.0?topic=tls-overview-ssltls-handshake

https://ssl-config.mozilla.org/

https://cipherlist.eu/

https://cwiki.apache.org/confluence/display/httpd/NameBasedSSLVHostsWithSNI