Créer une PKI (Public Key Infrastructure) pour générer ses propres certificats SSL/TLS avec Vault (#2)

Parmi ses multiples fonctionnalités, Vault permet de gérer sa propre PKI.

Qu’est-ce qu’une PKI ?

Une Infrastructure à Clés Publiques (PKI, pour Public Key Infrastructure) est un ensemble de rôles, politiques, logiciels, et procédures nécessaires pour créer, gérer, distribuer, utiliser, stocker et révoquer les certificats numériques et gérer les clés publiques.

Ou plus simplement une infrastructure permettant de gérer vos propres certificats SSL/TLS.

Voici quelques avantages à gérer sa propre PKI :

  • Contrôle total : vous contrôlez la chaîne complète : de la création à la révocation des certificats.
  • Sécurité renforcée : il n’y a plus de dépendance avec un tiers – ce qui réduit les risques de sécurité.
  • Coût : le coût peut parfois être moins important que de passer par un tiers.
  • Rapidité : une fois que la solution est installée et configurée et que la gestion des certificats est automatisée – la génération ou la révocation d’un certificats sont des opérations simples et rapides.

Dans beaucoup de cas, générer et utiliser des certificats SSL/TLS émis par un tiers de confiance déjà connu est beaucoup plus simple et demande beaucoup moins de travail pour une entreprise.

Attention cependant, l’autorité de certification que vous allez créée ne sera pas reconnue par les navigateurs ou les logiciels.

Pré-requis et environnement

Nous déployons sur l’environnement suivant :

  • OS : Ubuntu 22.x
  • Version VAULT : 1.15.4
  • Domaine : https://vault.tld
  • Société : DYNDATA

Le déploiement a été abordé dans un article précèdent.
Nous allons opérer via la CLI de vault, cela peut être tout autant être réalisé par l’API.

Structure

Une PKI est structurée par différents niveaux, indiqués dans le schéma.

Nous allons créer la structure suivante :

ROOT CA (Autorité de certification racine) – durée de vie : 10 ans
C’est l’entité de confiance de plus haut niveau – elle créée, émet et gère les certificats racines qui sont à la base de la chaîne de confiance.
Ce niveau permet de signer les certificats du niveau inférieur – l’intermediate CA.
La clef privée du root ca est ultra sensible et ne doit pas être compromise – nous la générons et stockons hors Vault.

INTERMEDIATE CA – durée de vie : 10 ans
Ce niveau est signé par le root ca.
Ce niveau est un relais entre le certificat root et les certificats des niveaux inférieurs.

Si un certificat de ce niveau est compromis – cela n’affecte pas systématiquement toute la PKI, contrairement au root ca.

ISSUING CA – durée de vie : 1 an
Ce niveau inférieur à celui du niveau intermédiaire, est responsable de la génération des certificats finaux, il permet une meilleure organisation de la PKI.
Le certificat issuing est signé par l’intermédiaire.
En cas de compromission, l’impact sera moins important que pour les niveaux supérieurs.

Génération du ROOT CA hors Vault

En root, on génère le certificat root avec certstrap.

apt install golang-go jq
cd /root/
git clone https://github.com/square/certstrap
cd certstrap
go build
./certstrap --depot-path root init \
--organization "dyndata" \
--common-name "DYNDATA ROOT CA" \
--expires "10 years" \
--curve P-256 \
--path-length 2

Created root/DYNDATA_ROOT_CA.key
Created root/DYNDATA_ROOT_CA.crt
Created root/DYNDATA_ROOT_CA.crl

Il est demandé de taper et retenir une passphrase, nous vous conseillons une passphrase d’une longueur de 20 caractères, avec tout type de caractère.

On peut ensuite vérifier le certificat root, il faut être vigilant sur ces valeurs : O / CN / Validity / Taille de la clef (P-256) :

openssl x509 -in DYNDATA_ROOT_CA.crt  -noout -text

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 1 (0x1)
        Signature Algorithm: ecdsa-with-SHA256
        Issuer: O = dyndata, CN = DYNDATA ROOT CA
        Validity
            Not Before: Dec 18 08:56:43 2023 GMT
            Not After : Dec 18 09:05:46 2033 GMT
        Subject: O = dyndata, CN = DYNDATA ROOT CA
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:a5:02:d6:27:2b:d1:23:4a:12:cd:5c:c8:dd:5f:
                    13:b0:b1:ae:9f:5e:eb:28:eb:83:fe:2a:b8:7c:1f:
                    f7:3e:bc:e1:03:a3:d7:f2:3a:ba:8f:3c:fc:38:07:
                    02:6f:b0:e4:ff:99:93:ad:95:85:00:34:31:8f:7b:
                    56:67:65:96:eb
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        X509v3 extensions:
            X509v3 Key Usage: critical
                Certificate Sign, CRL Sign
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:2
            X509v3 Subject Key Identifier:
                63:11:25:CD:59:A7:4F:0E:53:A5:CA:E0:18:14:6A:07:60:73:D6:3A
    Signature Algorithm: ecdsa-with-SHA256
    Signature Value:
        30:45:02:20:31:b8:ea:2c:42:2b:82:40:07:19:d5:08:4e:da:
        96:e8:c0:0d:8b:5d:b7:6c:52:6c:56:f2:88:95:5b:14:45:e2:
        02:21:00:ad:13:d6:1c:66:dc:4a:a9:a1:de:6c:79:9d:ef:c8:
        06:88:19:6d:9b:c2:46:4b:26:d6:e7:ef:6e:8d:28:3b:37

Génération de l’INTERMEDIATE CA

Nous allons placer nos fichiers dans /root/pki.
Puis on s’identifie avec le bon token.

mkdir /root/pki/ ; cd /root/pki
vault login

On active le chemin (path) « pki_interca » (avec une durée de vie de 10 ans), qui contiendra le certificat intermédiaire :

vault secrets enable -path=pki_interca -description="PKI backend for Intermediate CA" -max-lease-ttl=87600h pki

On indique à Vault les URL issuing + crl :

vault write pki_interca/config/urls issuing_certificates="https://vault.tld:8200/v1/pki_interca/ca" crl_distribution_points="https://vault.tld:8200/v1/pki_interca/crl"

Key                        Value
---                        -----
crl_distribution_points    [https://vault.tld:8200/v1/pki_interca/crl]
enable_templating          false
issuing_certificates       [https://vault.tld:8200/v1/pki_interca/ca]
ocsp_servers               []

On génère la clef privée et le CSR pour le certificat intermédiaire :

cd /root/pki

vault write -format=json pki_interca/intermediate/generate/internal key_bits=3072 common_name=DyndataIntermediate ttl=87600h > pki_pki_interca.csr.json

On vérifie le contenu du certificat, il faut être vigilant sur ces valeurs : CN / Public Key Algorithm / Taille de la clef (3072 bits) :

Certificate request self-signature verify OK
Certificate Request:
    Data:
        Version: 1 (0x0)
        Subject: CN = DyndataIntermediate
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (3072 bit)
                Modulus:
[...]
                Exponent: 65537 (0x10001)
        Attributes:
            Requested Extensions:
                X509v3 Subject Alternative Name:
                    DNS:DyndataIntermediate
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
[...]

On signe le certificat intermédiaire avec le certificat root :

cd /root/certstrap/

./certstrap --depot-path root sign \
--CA "DYNDATA ROOT CA" \
--intermediate \
--csr /root/pki/pki_pki_interca.csr \
--expires "10 years" \
--path-length 1 \
--cert /root/pki/pki_pki_interca.crt \
"DYNDATA Intermediate CA"

Building intermediate
Created root/DYNDATA_Intermediate_CA.crt from root/DYNDATA_Intermediate_CA.csr signed by root/DYNDATA_ROOT_CA.key

On ajoute ce certificat intermédiaire signé par le certificat root, dans vault :

cd /root/pki
 
vault write -format=json pki_interca/intermediate/set-signed certificate=@pki_pki_interca.crt > pki_pki_interca.crt.json ; cat pki_pki_interca.crt.json

{
  "request_id": "978ec2a7-ba4e-afbb-3aff-6db9ade8b565",
  "lease_id": "",
  "lease_duration": 0,
  "renewable": false,
  "data": {
    "existing_issuers": null,
    "existing_keys": null,
    "imported_issuers": [
      "43fe0995-930f-73b7-6051-ae06094543c7"
    ],
    "imported_keys": null,
    "mapping": {
      "43fe0995-930f-73b7-6051-ae06094543c7": "11183958-c3a4-544d-931d-6c51b95389ef"
    }
  },
  "warnings": null
}

Génération de l’ISSUING CA

On active le chemin (path) « issuing_dyndata_linux » (avec une durée de vie de 10 ans), qui contiendra le certificat issuing :

vault secrets enable -path=issuing_dyndata_linux -max-lease-ttl=87600h pki

Success! Enabled the pki secrets engine at: issuing_dyndata_linux/

On indique à Vault les URL issuing + crl :

vault write issuing_dyndata_linux/config/urls issuing_certificates="https://vault.tld:8200/v1/issuing_dyndata_linux/ca" crl_distribution_points="https://vault.tld:8200/v1/issuing_dyndata_linux/crl"

Key                        Value
---                        -----
crl_distribution_points    [https://vault.tld:8200/v1/issuing_dyndata_linux/crl]
enable_templating          false
issuing_certificates       [https://vault.tld:8200/v1/issuing_dyndata_linux/ca]
ocsp_servers               []

On génère la clef privée et le CSR pour le certificat intermédiaire :

cd /root/pki

vault write -format=json issuing_dyndata_linux/intermediate/generate/internal key_bits=3072 ttl=87600h organization="DYNDATA" common_name="DYNDATAissuing-Linux" > issuing_dyndata_linux.csr.json

On vérifie le contenu du certificat, il faut être vigilant sur ces valeurs : CN / Public Key Algorithm / Taille de la clef (3072 bits) :

cat issuing_dyndata_linux.csr.json | jq -r '.data.csr' > issuing_dyndata_linux.csr ; openssl req -text -noout -verify -in issuing_dyndata_linux.csr

Certificate request self-signature verify OK
Certificate Request:
    Data:
        Version: 1 (0x0)
        Subject: O = DYNDATA, CN = DYNDATAissuing-Linux
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (3072 bit)
                Modulus:
[...]
                Exponent: 65537 (0x10001)
        Attributes:
            Requested Extensions:
                X509v3 Subject Alternative Name:
                    DNS:DYNDATAssuing-Linux
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
[...]

On signe le certificat issuing avec le certificat intermédiaire :

cd /root/pki

vault write -format=json pki_interca/root/sign-intermediate organization="DYNDATA" csr=@issuing_dyndata_linux.csr key_bits=3072 ttl=8761h format=pem > issuing_dyndata_linux.csr.json

On vérifie le certificat issuing signé par le certificat intermédiaire :

cat issuing_dyndata_linux.csr.json | jq -r '.data.certificate'> issuing_dyndata_linux.crt ;
openssl x509 -in issuing_dyndata_linux.crt -text -noout

On ajoute ce certificat dans vault :

vault write -format=json issuing_dyndata_linux/intermediate/set-signed certificate=@issuing_dyndata_linux.crt > issuing_dyndata_linux.crt.chain.set-signed.json

Création d’un rôle et génération du certificat

On créé un répertoire spécifique pour placer les SSL générés :

mkdir -p /root/pki/SSL/issuing_dyndata_linux/roles/
cd /root/pki/SSL/issuing_dyndata_linux/roles/

On créé le rôle « frontal » :

vault write issuing_dyndata_linux/roles/frontal \
organization="DYNDATA" \
key_type=rsa \
key_bits=3072 \
ttl=366 \
allow_any_name=true \ 
allowed_domains=front.tld \ 
allow_subdomains=true

On génère le SSL final pour le domaine « front.tld » :

issuing_dyndata_linux/issue/frontal \
format=pem_bundle \
common_name="front.tld" \
ttl=8759h \
> /root/pki/SSL/issuing_dyndata_linux/roles/front.tld.pem

Le certificat se trouve à présent ici : /root/certstrap/SSL/issuing_dyndata_linux/roles/front.tld.pem
Il y a dans ce .pem : la clef privée et la chaîne complète.

openssl x509 -in /root/pki/SSL/issuing_dyndata_linux/roles/front.tld.pem -text -noout

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            58:8f:26:65:15:9d:17:2a:a5:1c:8e:e5:b4:c0:93:ef:77:bf:b5:03
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: O = DYNDATA, CN = DYNDATAissuing-Linux
        Validity
            Not Before: Dec 19 12:58:52 2023 GMT
            Not After : Dec 18 11:59:22 2024 GMT
        Subject: CN = front.tld
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
[...]
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment, Key Agreement
            X509v3 Extended Key Usage:
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 Subject Key Identifier:
                03:02:97:89:FF:1F:B9:0D:EF:3D:6A:0C:E7:EE:60:97:5A:2B:33:A2
            X509v3 Authority Key Identifier:
                31:0D:B0:D2:9E:99:C0:3D:96:6B:53:3C:59:34:69:17:65:99:07:1C
            Authority Information Access:
                CA Issuers - URI:https://vault.tld:8200/v1/issuing_dyndata_linux/ca
            X509v3 Subject Alternative Name:
                DNS:front.tld
            X509v3 CRL Distribution Points:
                Full Name:
                  URI:https://vault.tld:8200/v1/issuing_dyndata_linux/crl
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
[...]

Généralement, on récupère la clef privée pour l’injecter dans un fichier front.tld.key, et on laisse la chaîne complète dans le .pem.

Certificats root & intermediaires

Comme nous gérons notre propre PKI, celle-ci n’est pas reconnue comme tiers de confiance par les OS et navigateurs.
Par conséquent, il est nécessaire d’importer les certificats root et intermédiaires sur les OS et navigateurs faisant appel à des domaines dont le certificat SSL est géré par la PKI.

Aller plus loin…

Nous avons installé une PKI très simplement, il existe de multiples options et de manière d’organiser la PKI.
Cependant, nous vous conseillons de lire la documentation officielle.

Comments are closed