PKI OpenSSL (pt 2)

OpenSSL PKI (parte 2) - IntermediateCA

Criar uma IntermediateCA

Mais uma vez, a(s) IntermediateCA(s) devem estar alojadas num sistema separado (server) daquele que aloja a RootCA, e o sistema da RootCA (root) deve estar airgapped e desligado da corrente.
No sistema online não me parece má ideia manter as CAs secundárias debaixo de /root/ca, o que permite colocar o certificado da RootCA no mesmo caminho (/root/ca/certs/ca.cert.pem). No entanto, esta é a única informação da RootCA que é necessário trazer para o host da(s) CA(s) secundária(s); é debatível se vale a pena trazer o CRL da RootCA em formato PEM e colocá-lo em /root/ca/crl/ca.crl.pem, a não ser que este sistema online seja o mesmo que esteja a ser usado para distribuir o CRL da RootCA (como visto anteriormente).

1
2
3
4
5
mkdir -p /root/ca/certs
cp ca.cert.pem /root/ca/certs/
mkdir /root/ca/crl
cp ca.crl.pem /root/ca/crl/
mkdir /root/ca/csr

Criamos a árvore de pastas completa para suportar a operação da IntermediateCA:

1
2
3
4
5
6
inter="/root/ca/inter-ca"
mkdir $inter/{certs,crl,csr,newcerts,private}
chmod 700 $inter/private
touch $inter/index.txt
echo 1000 > $inter/serial
echo 1000 > $inter/crlnumber

Criamos o ficheiro de configuração da Inter-CA:

1
cat > $inter/openssl.cnf

/root/ca/inter-ca/openssl.cnf

# OpenSSL intermediate CA configuration file.
# Copy to '/root/ca/inter-ca/openssl.cnf'.

[ ca ]
# 'man ca'
default_ca = CA_default

[ CA_default ]
# Directory and file locations.
dir               = /root/ca/inter-ca
certs             = $dir/certs
crl_dir           = $dir/crl
new_certs_dir     = $dir/newcerts
database          = $dir/index.txt
serial            = $dir/serial
RANDFILE          = $dir/private/.rand

# The root key and root certificate.
private_key       = $dir/private/inter-ca.key.pem
certificate       = $dir/certs/inter-ca.cert.pem

# For certificate revocation lists.
crlnumber         = $dir/crlnumber
crl               = $dir/crl/inter-ca.crl.pem
crl_extensions    = crl_ext
default_crl_days  = 30

# SHA-1 is deprecated, so use SHA-2 instead.
default_md        = sha512

name_opt          = ca_default
cert_opt          = ca_default
default_days      = 375
preserve          = no
policy            = policy_loose

# copy SAN, OCSP and other stuff to downstream certificates
copy_extensions = copy

[ policy_strict ]
# The root CA should only sign intermediate certificates that match.
# See the POLICY FORMAT section of 'man ca'.
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ policy_loose ]
# Allow the intermediate CA to sign a more diverse range of certificates.
# See the POLICY FORMAT section of the 'ca' man page.
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ req ]
# Options for the 'req' tool ('man req').
default_bits        = 4096
distinguished_name  = req_distinguished_name
string_mask         = utf8only

# SHA-1 is deprecated, so use SHA-2 instead.
default_md          = sha512

# Extension to add when the -x509 option is used.
x509_extensions     = v3_ca

[ req_distinguished_name ]
# See <https://en.wikipedia.org/wiki/Certificate_signing_request>.
countryName                     = Country Name (2 letter code)
stateOrProvinceName             = State or Province Name
localityName                    = Locality Name
0.organizationName              = Organization Name
organizationalUnitName          = Organizational Unit Name
commonName                      = Common Name
emailAddress                    = Email Address

# Optionally, specify some defaults.
countryName_default             = PT
stateOrProvinceName_default     = Lisboa
localityName_default            = Lisboa
0.organizationName_default      = TiagoJoaoSilva
organizationalUnitName_default  = TJS
emailAddress_default            = bofh@tjs.lan

[ v3_ca ]
# Extensions for a typical CA ('man x509v3_config').
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ v3_intermediate_ca ]
# Extensions for a typical intermediate CA ('man x509v3_config').
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ usr_cert ]
# Extensions for client certificates ('man x509v3_config').
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection

[ server_cert ]
# Extensions for server certificates ('man x509v3_config').
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth

[ crl_ext ]
# Extension for CRLs ('man x509v3_config').
authorityKeyIdentifier=keyid:always

[ ocsp ]
# Extension for OCSP signing certificates ('man ocsp').
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, digitalSignature
extendedKeyUsage = critical, OCSPSigning


Num mundo ideal, haveria várias sub-CAs, nomeadas pelo uso a que se destinariam (geralmente com conjuntos de extensões diferentes para cada utilização):

  • tls-ca: certificados para servidores e clientes
  • vpn-ca: certificados para VPNs
  • mime-ca: certificados para email
  • code-ca: certificados para code signing
  • net-ca: certificados para activos de rede
    A configuração dessas extensões está fora do âmbito deste documento. Mas cada uma delas teria a sua árvore de pastas e a sua cópia do openssl.cnf, ao qual eu daria um nome mais específico em cada caso.
    Na realidade, parece que quase ninguém se dá a este trabalho; geralmente existe uma CA separada para VPNs externas, se a empresa assinar software existe uma CA para code-signing, e depois uma única CA para todos os sistemas internos.

Para que não seja preciso criar ficheiros de configuração separados para cada certificado re-especificando todas as funcionalidades que são comuns à InterCA, acrescentamos o seguinte a /root/ca/inter-ca/openssl.cnf:

[ CA_default ]
(…)
# copy extensions missing from the CSR to the final certificate
copy_extensions = copy

De notar que como esta IntermediateCA é mais dinâmica que a RootCA, a vigência dos CRLs não deve ser maior do que 30 dias; aconselho que se configure uma tarefa periódica que refresque o CRL a cada 29 dias.

default_crl_days  = 30

Criamos uma nova palavra-passe complexa para a InterCA:

1
2
openssl rand -base64 12 > $inter/private/inter-ca.key.pass
chmod 400 $inter/private/inter-ca.key.pass

Criamos a chave da InterCA indicando a palavra-passe complexa:

1
2
openssl genrsa -aes256 -passout file:$inter/private/inter-ca.key.pass \
    -out $inter/private/inter-ca.key.pem 8192

Generating RSA private key, 8192 bit long modulus (2 primes)
......................................................................................................................................................................................................+++
.....................................................................................................................................................................................................+++
e is 65537 (0x010001)

1
# chmod 400 $inter-ca/private/inter-ca.key.pem

Criamos o Certificate Signing Request para assinarmos com a CA (note-se que fica nos CSRs da RootCA):

1
2
3
4
openssl req -config $inter/openssl.cnf -new -sha512 \
	-passin file:$inter/private/inter-ca.key.pass \
    -key $inter/private/inter-ca.key.pem \
    -out /root/ca/csr/inter-ca.csr.pem

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 []:**inter-ca-tjs-2**
Email Address [bofh@tjs.lan]

Assinar o CSR com a RootCA

Voltamos ao sistema offline da RootCA e criamos a árvore de pastas necessária.
Como apenas vai suportar a criação das InterCAs e não a sua operação, é muito mais simples:

1
2
3
inter="/root/ca/inter-ca"
mkdir $inter/certs
chmod 740 $inter/certs

Trazemos do sistema online o CSR da IntermediateCA, que colocamos no mesmo local onde estava no sistema online (a pasta de CSRs da RootCA):

1
cp inter-ca.csr.pem /root/ca/csr

Para que a informação CDL e/ou AIA da RootCA seja passada à IntermediateCA, acrescentamos esta directiva à secção [ CA_default ] de /root/ca/openssl.cnf

# copy extensions missing from the CSR to the final certificate
copy_extensions = copy

Agora assinamos o CSR; note-se que vamos indicar que a extensão a usar do /root/ca/openssl.cnf é a [ v3_intermediate_ca ]!

1
2
3
4
5
openssl ca -config /root/ca/openssl.cnf -extensions v3_intermediate_ca \
    -days 3650 -notext -md sha512 \
    -passin file:/root/ca/private/ca.key.pass \
    -in /root/ca/csr/inter-ca.csr.pem \
    -out $inter/certs/inter-ca.cert.pem


Using configuration from /root/ca/openssl.cnf
Check that the request matches the signature
Signature ok
Certificate Details:
        Serial Number: 4099 (0x1003)
        Validity
            Not Before: Aug 13 23:44:07 2021 GMT
            Not After : Aug 11 23:44:07 2031 GMT
        Subject:
            countryName               = PT
            stateOrProvinceName       = Lisboa
            organizationName          = TiagoJoaoSilva
            organizationalUnitName    = TJS
            commonName                = inter-ca-tjs-2
            emailAddress              = bofh@tjs.lan
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                E3:75:25:D7:74:08:0B:23:F0:AF:E4:EC:D0:D4:52:CB:5A:3A:10:2E
            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, pathlen:0
            X509v3 Key Usage: critical
                Digital Signature, Certificate Sign, CRL Sign
            X509v3 CRL Distribution Points:

                Full Name:
                  URI:http://server.tjs.lan:7788/root-ca.crl

Certificate is to be certified until Aug 11 23:44:07 2031 GMT (3650 days)
Sign the certificate? [y/n]:Y

1 out of 1 certificate requests certified, commit? [y/n]Y
Write out database with 1 new entries
Data Base Updated


Verificamos a emissão:

1
openssl verify -CAfile /root/ca/certs/ca.cert.pem $inter/certs/inter-ca.cert.pem


/root/ca/inter-ca/certs/inter-ca.cert.pem: OK


Criamos um certificado para a cadeia de autoridade com um certificado Chain (também chamado Bundle), concatenando os certificados por ordem ascendente - o último será a RootCA ou o mais próximo.

1
2
3
cat $inter/certs/inter-ca.cert.pem /root/ca/certs/ca.cert.pem > \
    $inter/certs/inter-ca_chain.cert.pem
chmod 444 $inter/certs/inter-ca_chain.cert.pem

Activar a IntermediateCA no sistema online

Uma vez que deixamos no sistema online tudo aquilo que a IntermediateCA necessita para funcionar, só temos de trazer (de forma segura) os certificados assinados pela RootCA para o local adequado; não encontrei nenhuma fonte que indicasse que se deveria deixar os certificados da própria IntermediateCA em /root/ca/certs, por isso vamos deixá-los em /root/ca/inter-ca/certs

1
cp inter-ca.cert.pem inter-ca_chain.cert.pem $inter/certs

Revogação de certificados da IntermediateCA

Como o número de certificados a emitir (ou a revogar) pela IntermediateCA será muito maior, é vantajoso que se utilize o método de verificação de revogação por 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 /root/ca/inter-ca/openssl.cnf a seguinte directiva nas secções [server_cert] e [usr_cert]

authorityInfoAccess = OCSP;URI:http://ocsp.tjs.lan:7888

Criar o certificado

Geramos uma chave:

1
openssl genrsa -out /root/ca/inter-ca/private/ocsp.tjs.lan.key.pem 4096


Generating RSA private key, 4096 bit long modulus (2 primes)
...............................++++
................................................++++
e is 65537 (0x010001)


Precisamos de inserir no CSR Informação SAN (Subject Alternative Name) específica para o OCSP:

1
cat > /root/ca/inter-ca/san/ocsp.tjs.lan-san.cnf
[req]
distinguished_name = req_distinguished_name
req_extensions = req_ext
prompt = no

[req_distinguished_name]
C   = PT
ST  = Lisboa
L   = Lisboa
O   = Tiago Joao Silva
OU  = TJS
CN  = ocsp.tjs.lan
[req_ext]
subjectAltName = @alt_names

[alt_names]
IP.1 = 192.168.1.70
DNS.1 = ocsp.tjs.lan 

Criamos o CSR:

1
2
3
openssl req -config /root/ca/inter-ca/san/ocsp.tjs.lan-san.cnf -new -sha512 \
      -key /root/ca/inter-ca/private/ocsp.tjs.lan.key.pem \
      -out /root/ca/inter-ca/csr/ocsp.tjs.lan.csr.pem

Confirmar que o CSR tem a informação SAN:

1
2
openssl req -noout -text -in /root/ca/inter-ca/csr/ocsp.tjs.lan.csr.pem | \
    grep -A 1 "Subject Alternative Name"


            X509v3 Subject Alternative Name: 
                IP Address:192.168.20.170, DNS:ocsp.tjs.lan


Assinar o CSR com -extensions ocsp:

1
2
3
4
5
openssl ca -config /root/ca/inter-ca/openssl.cnf \
    -extensions ocsp -days 823 -notext -md sha512 \
    -passin file:/root/ca/inter-ca/private/inter-ca.key.pass \
    -in /root/ca/inter-ca/csr/ocsp.tjs.lan.csr.pem \
    -out /root/ca/inter-ca/certs/ocsp.tjs.lan.cert.pem


Using configuration from /root/ca/inter-ca/openssl.cnf
Check that the request matches the signature
Signature ok
Certificate Details:
        Serial Number: 4099 (0x1003)
        Validity
            Not Before: Aug 22 22:53:56 2021 GMT
            Not After : Nov 23 22:53:56 2023 GMT
        Subject:
            countryName               = PT
            stateOrProvinceName       = Lisboa
            localityName              = Lisboa
            organizationName          = Tiago Joao Silva
            organizationalUnitName    = TJS
            commonName                = ocsp.tjs.lan
        X509v3 extensions:
            X509v3 Basic Constraints:
                CA:FALSE
            X509v3 Subject Key Identifier:
                BD:A6:EE:6D:0F:73:0E:47:DC:26:9A:A8:B0:40:18:55:DB:35:6B:5A
            X509v3 Authority Key Identifier:
                keyid:E3:75:25:D7:74:08:0B:23:F0:AF:E4:EC:D0:D4:52:CB:5A:3A:10:2E

            X509v3 Key Usage: critical
                Digital Signature
            X509v3 Extended Key Usage: critical
                OCSP Signing
Certificate is to be certified until Nov 23 22:53:56 2023 GMT (823 days)
Sign the certificate? [y/n]:Y

1 out of 1 certificate requests certified, commit? [y/n]Y
Write out database with 1 new entries
Data Base Updated


Confirmar que o certificado tem a extensão OCSP:

1
2
openssl x509 -noout -text -in /root/ca/inter-ca/certs/ocsp.tjs.lan.cert.pem | \
    grep -A1 X509v3

        X509v3 extensions:
            X509v3 Basic Constraints:
                CA:FALSE
            X509v3 Subject Key Identifier:
                BD:A6:EE:6D:0F:73:0E:47:DC:26:9A:A8:B0:40:18:55:DB:35:6B:5A
            X509v3 Authority Key Identifier:
                keyid:E3:75:25:D7:74:08:0B:23:F0:AF:E4:EC:D0:D4:52:CB:5A:3A:10:2E
--
            X509v3 Key Usage: critical
                Digital Signature
            X509v3 Extended Key Usage: critical
                OCSP Signing

1
chmod 400 $inter/certs/ocsp.tjs.lan.cert.pem 

Criar um OCSP Responder no OpenSSL (não usar em produção)

Para testar, vamos activar o Responder temporariamente:

  • vamos usar a porta 7888, uma das que já vimos que está livre (ver a Parte 1 para detalhes)
  • usando a opção -nrequest 1, vai fechar depois de receber um único pedido
  • e vamos gravar um log.
1
2
3
4
5
6
openssl ocsp -index /root/ca/inter-ca/index.txt -port 7888 -text \
    -CA /root/ca/inter-ca/certs/inter-ca_chain.cert.pem \
    -rkey /root/ca/inter-ca/private/ocsp.tjs.lan.key.pem \
    -rsigner /root/ca/inter-ca/certs/ocsp.tjs.lan.cert.pem \
    -out /root/ca/inter-ca/ocsp-log.txt \
      -nrequest 1 & 


ocsp: waiting for OCSP client connections...


Abrir a porta na firewall se esta estiver activa

1
ufw allow 7888

Fazer uma pergunta ao Responder (usando o certificado emitido para o OCSP, ou outro):

1
2
3
4
openssl ocsp -CAfile /root/ca/inter-ca/certs/ca-chain.cert.pem \
      -url http://127.0.0.1:7888 -resp_text \
      -issuer /root/ca/inter-ca/certs/inter-ca.cert.pem \
      -cert /root/ca/inter-ca/certs/ocsp.tjs.lan.cert.pem


[…]
Response verify OK
/root/ca/inter-ca/certs/ocsp.tjs.lan.cert.pem: good
        This Update: Aug 22 23:06:21 2021 GMT
[1]+  Done                    openssl ocsp -index /root/ca/inter-ca/index.txt -port 7888 -text -CA /root/ca/inter-ca/certs/inter-ca_chain.cert.pem -rkey /root/ca/inter-ca/private/ocsp.tjs.lan.key.pem -rsigner /root/ca/inter-ca/certs/ocsp.tjs.lan.cert.pem -out /root/ca/inter-ca/ocsp-log.txt -crl_check_all -nrequest 1


Verificamos o que aconteceu na comunicação com o OCSP:

1
head -30 /root/ca/inter-ca/ocsp-log.txt

OCSP Request Data:
    Version: 1 (0x0)
    Requestor List:
        Certificate ID:
          Hash Algorithm: sha1
          Issuer Name Hash: AE59C598DC56A005EA5239AE471093B7924F5018
          Issuer Key Hash: E37525D774080B23F0AFE4ECD0D452CB5A3A102E
          Serial Number: 1003
    Request Extensions:
        OCSP Nonce:
            04104CA2F5C63FA8F6A8D89256935DB37177
OCSP Response Data:
    OCSP Response Status: successful (0x0)
    Response Type: Basic OCSP Response
    Version: 1 (0x0)
    Responder Id: C = PT, ST = Lisboa, L = Lisboa, O = Tiago Joao Silva, OU = TJS, CN = ocsp.tjs.lan
    Produced At: Aug 22 23:06:21 2021 GMT
    Responses:
    Certificate ID:
      Hash Algorithm: sha1
      Issuer Name Hash: AE59C598DC56A005EA5239AE471093B7924F5018
      Issuer Key Hash: E37525D774080B23F0AFE4ECD0D452CB5A3A102E
      Serial Number: 1003
    Cert Status: good
    This Update: Aug 22 23:06:21 2021 GMT

    Response Extensions:
        OCSP Nonce:
            04104CA2F5C63FA8F6A8D89256935DB37177
    Signature Algorithm: sha256WithRSAEncryption

Configurar o OCSP Responder como um serviço permanente

Se o sistema usar o init systemd, criamos um Service Unit (outros inits usarão os seus mecanismos)

1
cat > /etc/systemd/system/ocsp-responder_inter-ca.service
[Unit]
Description = OCSP responder using OpenSSL (for CA inter-ca)
After = network.target

[Service]
ExecStart = /usr/bin/openssl ocsp \
-index /root/ca/inter-ca/index.txt \
-port 7888 \
-rkey /root/ca/inter-ca/private/ocsp.tjs.lan.key.pem \
-rsigner /root/ca/inter-ca/certs/ocsp.tjs.lan.cert.pem \
-CA /root/ca/inter-ca/certs/inter-ca_chain.cert.pem \
-text -crl_check_all\
-out /root/ca/inter-ca/ocsp-log.txt

[Install]
WantedBy = multi-user.target
1
systemctl daemon-reload

Testar o serviço:

1
2
systemctl start ocsp-responder_inter-ca
systemctl status ocsp-responder_inter-ca

● ocsp-responder_inter-ca.service - OCSP responder using OpenSSL (for CA inter-ca)
     Loaded: loaded (/etc/systemd/system/ocsp-responder_inter-ca.service; disabled; vendor preset: enabled)
     Active: active (running) since Mon 2021-08-23 00:21:27 WEST; 22s ago
[…]
Aug 23 00:21:27 debian systemd[1]: Started OCSP responder using OpenSSL (for CA inter-ca).
Aug 23 00:21:27 debian openssl[2585]: ocsp: waiting for OCSP client connections...

Fazer um pedido ao serviço:

1
2
3
4
# openssl ocsp -CAfile /root/ca/inter-ca/certs/ca-chain.cert.pem \
      -url http://127.0.0.1:7888 -resp_text \
      -issuer /root/ca/inter-ca/certs/inter-ca.cert.pem \
      -cert /root/ca/inter-ca/certs/ocsp.tjs.lan.cert.pem

Confirmar que a resposta é idêntica à que deu no teste feito anteriormente. Depois deixar o serviço como permanente:

1
systemctl enable ocsp-responder_inter-ca

Outras CAs

Caso esta não seja a única IntermediateCA, outras se poderiam configurar seguindo exactamente o mesmo método, bastando mudar a porta de escuta (até à 7899/TCP há mais 10 portas disponíveis).
No entanto, não é recomendado usar o OpenSSL como um OCSP Responder, porque a implementação é mínima e pouco robusta.