PKI baseada em OpenSSL (parte 1)
Fontes:
https://openssl-ca.readthedocs.io/en/latest/index.html
https://www.golinuxcloud.com/tutorial-pki-certificates-authority-ocsp/
https://social.technet.microsoft.com/wiki/contents/articles/2900.offline-root-certification-authority-ca.aspx
https://pki-tutorial.readthedocs.io/en/latest/index.html
Root CA airgapped
É suposto o sistema que contém a RootCA ser airgapped, ter acesso controlado, e ser ligado apenas para emissão de novas CAs e revogação de CAs comprometidas. Neste exemplo, podemos colocar tudo no mesmo servidor mas deve-se tentar separar o mais possível a estrutura da RootCA das outras CAs.
O primeiro passo é criar uma Root Certification Authority (RootCA), que só vai criar outras CAs.
Mudar para o utilizador root
|
|
Criamos uma palavra-passe complexa:
|
|
ayUiXnIjZFRh
Criamos a chave indicando a palavra-passe complexa:
|
|
Generating RSA private key, 8192 bit long modulus (2 primes) ......................................................+++ ..+++ e is 65537 (0x010001)
Criamos e auto-assinamos o certificado da RootCA:
|
|
You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [PT]: State or Province Name [Lisboa]: Locality Name [Lisboa]: Organization Name [TiagoJoaoSilva]: Organizational Unit Name [TJS]: Common Name []:ca-tjs-1 Email Address [bofh@tjs.lan]:
|
|
Podemos examinar o conteúdo do certificado:
|
|
Certificate: Data: Version: 3 (0x2) Serial Number: 25:92:4d:14:da:6f:9f:8b:4b:49:55:fc:68:be:9e:c7:57:24:cd:70 Signature Algorithm: sha512WithRSAEncryption Issuer: C = PT, ST = Lisboa, L = Lisboa, O = TiagoJoaoSilva, OU = TJS, CN = ca-tjs-1, emailAddress = bofh@tjs.lan Validity Not Before: Aug 12 19:38:30 2021 GMT Not After : Aug 7 19:38:30 2041 GMT Subject: C = PT, ST = Lisboa, L = Lisboa, O = TiagoJoaoSilva, OU = TJS, CN = ca-tjs-1, emailAddress = bofh@tjs.lan Subject Public Key Info: Public Key Algorithm: rsaEncryption RSA Public-Key: (8192 bit) Modulus: [...] Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Subject Key Identifier: 01:F2:CC:54:E0:F2:58:AC:E2:14:8E:2B:DB:6D:B6:FF:5C:25:41:A0 X509v3 Authority Key Identifier: keyid:01:F2:CC:54:E0:F2:58:AC:E2:14:8E:2B:DB:6D:B6:FF:5C:25:41:A0 X509v3 Basic Constraints: critical CA:TRUE X509v3 Key Usage: critical Digital Signature, Certificate Sign, CRL Sign Signature Algorithm: sha512WithRSAEncryption [...]
Revogação de certificados
Antes de irmos mais longe, acho que temos de falar já na revogação de certificados, ao contrário de todos os outros tutoriais da Internet que deixam esse assunto para o fim, se chegam sequer a falar nele.
Se não o fizermos agora, será demasiado tarde para ser incluído em qualquer certificado criado pela RootCA.
Como revogar um certificado
|
|
-crl_reason
também pode ter os valores keyCompromise, CACompromise, affiliationChanged, superseded, cessationOfOperation, certificateHold, ou (só em Delta-CRLs) removeFromCRL
-crl_hold
coloca automaticamente a reason como certificateHold mas está fora do âmbito deste documento
-crl_compromise AAAAMMDDHHMMSSZ
coloca a reason como
keyCompromise na data assinalada.
-crl_CA_compromise AAAAMMDDHHMMSSZ
faz o mesmo, mas a reason é CACompromise
Precisamos de configurar um mecanismo para a RootCA disseminar a informação dos certificados revogados, e isso precisa de ser feito antes de se criar qualquer certificado subsequente, acrescentando informações ao openssl.cnf
da RootCA antes de criar qualquer CA secundária.
Distribuição de CRL
O método clássico é a distribuição directa das listas CRL, que pode ser feita por vários meios, como um servidor HTTP, uma directoria LDAP ou AD, etc.
Pode existir ainda uma complicação adicional, porque para evitar a transmissão constante de ficheiros grandes podem-se utilizar Delta-CRLs apenas com as alterações ao CRL; no entanto, apesar de esta ser uma possibilidade em Windows ou em outros pacotes de PKI, não está implementada no OpenSSL.
Estas listas costumam ser actualizadas (porque expiram e todos os certificados dessa CA deixam de ser aceites) no máximo de 30 em 30 dias, mas a lista de CRLs da RootCA não deve expirar (ou expirar apenas anualmente quando for altura de actualizar o sistema que contém a RootCA).
[CA_default]
default_crl_days = 385
Nessa altura, refresca-se o CRL e (no caso de o sistema da RootCA ser airgapped), extrair o CRL (em papel, QR Code, CD-R ou outro meio seguro) e levá-lo para o sistema de onde vai ser distribuído.
O formato de distribuição destas listas é DER (codificação binária), mas o ficheiro tem de ter a extensão .CRL (RFC 2585).
O outro método, mais recomendado nos dias de hoje, é a utilização de um OCSP Responder para responder a consultas sobre a validade de um certificado, que é uma operação rápida que não necessita de muita largura de banda, permite que a disseminação da revogação seja assinada com uma chave da CA-mãe, e que a resposta fique em cache nos clientes (através de OCSP Stapling).
Como o CRL da RootCA vai conter muito poucos certificados (a RootCA só vai emitir outras CAs, chamadas IntermediateCAs, SubordinateCAs ou SigningCAs, que serão poucas), vamos configurar uma emissão manual de CRLs para a RootCA.
Começamos por colocar a informação do local de onde será servido o CRL na secção [ v3_intermediate_ca ]
do /root/ca/openssl.cnf
, para ser incluída nos certificados das CAs emitidas pela RootCA. Vamos utilizar um servidor HTTP na porta 7788:
[ v3_intermediate_ca ]
crlDistributionPoints = URI:http://server.tjs.lan:7788/root-ca.crl
Criamos o CRL (https://blog.didierstevens.com/2013/05/08/howto-make-your-own-cert-and-revocation-list-with-openssl/)
|
|
Verificamos o conteúdo do CRL:
|
|
Certificate Revocation List (CRL): Version 2 (0x1) Signature Algorithm: sha512WithRSAEncryption Issuer: C = PT, ST = Lisboa, L = Lisboa, O = TiagoJoaoSilva, OU = TJS, CN = ca-tjs-1, emailAddress = bofh@tjs.lan Last Update: Aug 13 23:46:07 2021 GMT Next Update: Sep 2 23:46:07 2022 GMT CRL extensions: X509v3 Authority Key Identifier: keyid:01:F2:CC:54:E0:F2:58:AC:E2:14:8E:2B:DB:6D:B6:FF:5C:25:41:A0 X509v3 CRL Number: 4097 Revoked Certificates: Serial Number: 1001 Revocation Date: Aug 13 22:11:25 2021 GMT CRL entry extensions: X509v3 CRL Reason Code: Unspecified Serial Number: 1002 Revocation Date: Aug 13 23:41:51 2021 GMT CRL entry extensions: X509v3 CRL Reason Code: Superseded Signature Algorithm: sha512WithRSAEncryption [...]
E temos de converter o PEM (Base64) para formato DER:
|
|
Sistema ligado à rede
Como a RootCA deve ser airgapped e offline, não é no sistema da RootCA que se deve configurar a distribuição de CRLs, porque esse sistema tem de estar acessível na rede.
Configuramos um novo sistema Debian 11 e recriamos a pasta que contém o CRL da RootCA:
|
|
Fazemos lá chegar o ficheiro root-ca.crl
de alguma maneira segura (gravar um CD-R, impressão e scanning com OCR, etc.) e colocámo-lo em /root/ca/crl
Instalar e configurar o servidor web do CRL
Vamos usar um servidor muito básico, o lighttpd
|
|
De acordo com o wiki do Debian sobre o Lighttpd a instalação por defeito configura o serviço, agarra a porta 80/TCP
, corre com o utilizador www-data
, configura a webroot
para ser /var/www/html
, e os ficheiros default são index.php
ou index.html
, se bem que venha configurado para não correr ficheiros .php
.
Vamos configurar outra porta (7788), a webroot
será outra para não interferir com a default que é usada por outros servidores web, e desligar o desvio automático para index.html
Além disso, o que pode ser importante, a configuração de tipos MIME foi pré-feita com um script Perl, que se calhar vamos ter de editar ou refazer porque os ficheiros CRL têm de ser servidos por HTTP com um tipo MIME específico, application/pkix-crl
(https://pki-tutorial.readthedocs.io/en/latest/mime.html)
|
|
Mudar as linhas:
1 server.modules = (
2 # "mod_indexfile",
3 # "mod_access",
4 # "mod_alias",
5 # "mod_redirect",
6 )
8 server.document-root = "/var/www/crl"
10 server.errorlog = "/var/log/lighttpd/crl-error.log"
14 server.port = 7788
41 #index-file.names = ( "index.php", "index.html" )
Criar a nova pasta webroot
:
|
|
Mover para lá o index.html
deixado pelo pacote lighttpd
e desactivá-lo:
|
|
Colocar o CRL em formato DER dentro da webroot
:
|
|
Para configurar os tipos MIME, o lighttpd.conf
chama um script que cria uma configuração MIME a partir dos tipos definidos em /etc/mime.types
; será que este último já tem uma definição para o tipo MIME do CRL?
|
|
application/pkix-crl crl
Portanto, a linha 47 do
lighttpd.conf
vai criar uma lista de tipos MIME que inclui o nosso tipo CRL de cada vez que o serviço lighttpd
for iniciado.
47 include_shell "/usr/share/lighttpd/create-mime.conf.pl"
Confirmamos a sintaxe do ficheiro lighttpd.conf
:
|
|
2021-08-14 02:26:38: configfile.c.1142) WARNING: unknown config-key: url.access-deny (ignored)
Este erro não é importante, por isso:
|
|
● lighttpd.service - Lighttpd Daemon Loaded: loaded (/lib/systemd/system/lighttpd.service; enabled; vendor preset: enabled) Active: active (running)
|
|
Synchronizing state of lighttpd.service with SysV service script with /lib/systemd/systemd-sysv-install. Executing: /lib/systemd/systemd-sysv-install enable lighttpd
Abrimos a porta 7788 na firewall:
|
|
E testamos o acesso ao ficheiro:
|
|
Resolving localhost (localhost)... ::1, 127.0.0.1 Connecting to localhost (localhost)|::1|:7788... connected. HTTP request sent, awaiting response... 200 OK Length: 1359 (1.3K) [application/pkix-crl] Saving to: ‘root-ca.crl’ root-ca.crl 100%[=================================================>] 1.33K --.-KB/s in 0s 2021-08-14 02:33:18 (168 MB/s) - ‘root-ca.crl’ saved [1359/1359]
Foi estabelecida ligação e o ficheiro é servido com o tipo MIME correcto.
Mas infelizmente, só se devem usar CRLs quando temos a certeza que os clientes suportam este formato (e nesse aspecto, quem se porta melhor é a Microsoft); por exemplo, é impossível revogar certificados de Webserver por distribuição de CRLs porque apenas o Internet Explorer tem código para descarregar e analisar um CRL (Firefox e Chrome não querem saber) https://news.netcraft.com/archives/2013/05/13/how-certificate-revocation-doesnt-work-in-practice.html .
Por isso é mais seguro configurar o mecanismo alternativo, que é um OCSP Responder, também chamado AIA devido à directiva que o configura ( AuthorityInfoAccess
).
Configuração do CNF com o endereço do OCSP Responder
Começamos por colocar no openssl.cnf
da RootCA a seguinte directiva, na secção [ intermediate_ca ]
authorityInfoAccess = OCSP;URI:http://ocsp.tjs.lan:7899
A porta TCP do OCSP pode ser qualquer uma, sendo usual colocá-lo numa porta de tráfego HTTP pouco usada (como a 8008). Escolhi a porta 7899 porque depois de analisada a lista de portas da IANA, as portas 7888-7899/TCP não estão atribuídas, podendo ser usadas para este fim. Também se poderia desligar o lighttpd e reutilizar a porta 7788.
O protocolo é HTTP e não HTTPS porque como se verificaria o certificado SSL do OCSP? (https://social.technet.microsoft.com/Forums/office/en-US/c65e1784-39be-4732-a135-bfba7446ad05/should-the-ocsp-responder-service-be-running-http-80-or-https-443-)
A configuração de um OCSP Responder será documentada no segmento seguinte, que será a configuração da IntermediateCA; deixamos já a informação no .CNF da RootCA caso mais tarde se deseje implementar um OCSP Responder para ela também.
Note-se: se estas informações não forem configuradas agora, só poderão ser introduzidas na PKI (e passadas a certificados emitidos pela RootCA) revogando e re-emitindo todos os certificados já emitidos; por isso a estrutura de uma PKI deve ser ponderada antes de ser implementada.
Podemos passar à configuração da IntermediateCA.