Skip to content

Certificat HTTPS pour localhost, autorité de certification

Auteur : Philippe Le Van - @plv

Date : 18 novembre 2021

Introduction

En développement, on peut vouloir parcourir un site en xxx.localhost en https. Pour ça, il faut générer un certificat https pour son localhost. Si on ne veut pas d'alerte de sécurité, il faut signer ce certificat.

Ce tuto indique comment créer des certificats HTTPS autosignés, c'est-à-dire signés avec une autorité de certification qu'on aura créé nous même.

  • créer une autorité de certification
  • créer un certificat pour nos domaines (notamment en localhost)
  • Ajouter l'autorité de certification dans son navigateur, son système, son code, ...

Créer une autorité de certification (un root certificate)

En général les certificats HTTPS sont émis par des autorités de certifications connues (Let's encrypt, Gandi, OVH, Verisign, Thawte,...).

Par fois pour différentes raisons, on doit créer des certificats https nous même, par exemple pour le domaine localhost si on veut du https en dev. Dans notre cas, on va créer des certificats pour les développeurs sur les domaines en *.xxx.localhost. On n'a pas besoin spécialement de sécurité.

Pour créer une autorité de certification, il faut créer une suivre plusieurs étapes :

Créer l'arborescence et se mettre dans CA

1
2
3
4
5
# Créer l'arborescence qui va bien
mkdir cert
cd cert
mkdir CA
cd CA

Créer une clé privée pour sa CA

C'est créé avec l'outil openssl genpkey (man openssl-genpkey)

1
2
3
4
5
openssl genpkey -out rootCA.key -algorithm RSA -pkeyopt rsa_keygen_bits:2048

# pour l'algo, on aurait pu mettre RSA, RSA-PSS, EC, X25519, X448, ED25519 and ED448. cf man openssl-genpkey
# pour les pkeyopt, ça dépend de l'algo, cf man openssl-genpkey
# on peut ajoute une passphrase avec l'option `-aes-128-cbc -pass pass:my_pass_phrase`

Créer le certificat racine pour la CA (à partir de la clé privée)

1
2
3
4
5
6
7
8
9
openssl req -x509 -sha256 -new -days 3650 -key rootCA.key -out rootCA.pem -subj "/C=FR/CN=Kibatic-Dev-Root-CA"

# -x509 : indique qu'on veut un certificat autosigné et pas juste un CSR (certificat request)
# -new : génère un nouveau certificat
# -days 3650 : indique que le certificat racine est valable 10 ans
# -key rootCA.key : indique la clé privée à utiliser
# -out rootCA.pem : indique le nom du fichier de certificat généré
# -subj "xxx" : indique le nom de l'autorité de certification
# -sha256 : digest à utiliser

Note : La liste des digests est disponible avec la commande openssl list -digest-commands

convertit le fichier .pem en fichier .crt

Note : c'est juste une histoire de format. Dans la pratique, on va dire qu'on veut un format PEM dans le fichier .crt => les 2 fichiers pem et crt sont indentiques. Mais pédagogiquement ça me paraissait mieux de passer par openssl x509 (man openssl-x509)

1
openssl x509 -outform pem -in rootCA.pem -out rootCA.crt

Résultat intermédiaire :

On doit avoir 3 fichiers : la clé privée de la CA qu'on vient de créer ainsi que 2 formats du certificat racine

1
2
3
4
5
$ ls -rtlh

.rw-------  philippe  philippe  1.6 KB  Thu Nov 18 13:50:55 2021    rootCA.key
.rw-rw-r--  philippe  philippe  1.1 KB  Thu Nov 18 14:06:32 2021    rootCA.pem
.rw-rw-r--  philippe  philippe  1.1 KB  Thu Nov 18 14:12:03 2021    rootCA.crt

Pour lire le contenu du certificat racine généré

1
openssl x509 -in rootCA.crt -noout -text

On obtient quelque chose dans ce goût là

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            62:xxxxxxxxxxxxxxxxxxxxxx:02
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = FR, CN = Kibatic-Dev-Root-CA
        Validity
            Not Before: Nov 18 13:06:32 2021 GMT
            Not After : Oct 25 13:06:32 2121 GMT
        Subject: C = FR, CN = Kibatic-Dev-Root-CA
[...]

Créer un "server/leaf certificate" pour localhost

Maintenant on a notre autorité de certification. Quand on crée un site, il faut créer une clé privée sur le serveur qui héberger le site, puis créer un certificat https pour chaque site qui doit utiliser HTTPS.

Arborescence de répertoires

1
2
3
4
5
# on retourne dans notre répertoire CA
cd cert/CA
# on crée un répertoire localhost
mkdir localhost
cd localhost

Créer la clé privée du serveur

C'est exactement la même commande que pour créer la clé du certificat racine. C'est une clé en général attachée au serveur ou au nom de domaine à protéger et qui va servir à signer les "server certificats".

1
2
3
4
openssl genpkey -out localhost.key -algorithm RSA -pkeyopt rsa_keygen_bits:2048

# pour l'algo, on aurait pu mettre RSA, RSA-PSS, EC, X25519, X448, ED25519 and ED448. cf man openssl-genpkey
# pour les pkeyopt, ça dépend de l'algo, cf man openssl-genpkey

Note : on pourrait ajoute une passphrase à cette clé privée, mais ça veut dire qu'il faut la saisir à chaque fois que vous allez redémarrer votre serveur web. Bref, ici pas de passphrase.

Créer un certificate signing request "CSR"

L'idée du CSR est que le serveur qui veut un certificat n'a pas envie de transmettre sa clée privée à l'autorité de certification. Du coup elle génère un fichier qui s'appelle un CSR et qui permet à la CA de générer un certificat.

1
2
3
openssl req -new -key localhost.key -out localhost.csr -subj "/C=FR/ST=Isere/L=Grenoble/O=Localhost-Certificates/CN=example.localhost"
# -new : crée un nouveau CSR
# -subj : le plus important là dedans, c'est le CN (common name) qui représente le domaine principal du certificat.

on peut relire un CSR généré avec la commande suivante :

1
openssl req -text -noout -verify -in localhost.csr

Créer un fichier de certificate extension

On aura besoin d'extensions pour créer notre certificat pour qu'il protège plusieurs URLs

Créer un fichier localhost.ext avec le contenu suivant

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
authorityKeyIdentifier = keyid,issuer
basicConstraints = CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names

[alt_names]
DNS.1 = localhost
DNS.2 = web.example.localhost
DNS.3 = grafana.example.localhost
DNS.4 = node.example.localhost
IP.1 = 127.0.0.1
  • authorityKeyIdentifier
  • si on a keyid : chercher à copier le subject de l'autorité de certification dans le leaf certificat s'il n'y a pas de subject dans le CSR
  • si on a issuer : cherche à copier le DN de l'autorité de certification dans le leaf certificate si on n'a pas suject dans l'autorité de certification et pas de subject dans le CSR.
  • basicConstraints = CA:FALSE : indique si le certificat à générer est un certificat de CA ou un "server/leaf certificate"
  • keyUsage : indique à quoi doit servir ce certificat
  • subjectAltName = @alt_names : indique qu'on éclate les valeurs dans subjectAltName dans la section en dessous : [alt_names]
  • dans alt_names : on va donner la liste des domaines à couvrir

Créer un "server/leaf certificate" pour localhost

1
2
3
4
openssl x509 -req -in localhost.csr -CA ../rootCA.pem -CAkey ../rootCA.key -CAcreateserial -days 3650 -sha256 -extfile localhost.ext -out localhost.crt

# -CAcreateserial : ajoute un serial number dans un fichier rootCA.srl. Je pense que ça ne sert à rien ici
# -sha256 : indique le digest à utiliser. 

Note : La liste des digests est disponible avec la commande openssl list -digest-commands

Lire le certificat

Pour voir ce qu'il y a dans le certificat, on peut utiliser :

1
openssl x509 -text -noout -in localhost.crt

Installer le certificat dans traefik

Traefik a été installé avec le tutoriel traefik 2 en dev.

Pour installer des certificats custom, il faut :

  • créer un fichier dynamic.yml qui référence les clés et certificats à l'intérieur du container traefik
  • modifier le docker-compose.yml pour ajouter une option dans la commande : --providers.file.filename=/dynamic.yml
  • monter le fichier dynamic.yml dans le container
  • monter les certificats dans les containers.

le fichier dynamic.yml

1
2
3
4
tls:
  certificates:
    - certFile: /certs/localhost.crt
      keyFile: /certs/localhost.key

faire évoluer le docker-compose.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
version: "3.7"

services:

  traefik:
    image: "traefik:latest"
    restart: unless-stopped
    network_mode: host
    command:
      - --entryPoints.http.address=:80
      - --entryPoints.https.address=:443
      - --providers.docker=true
      - --providers.docker.exposedbydefault=true
      - --providers.docker.defaultRule=Host(`{{ index .Labels "com.docker.compose.service" }}.{{ index .Labels "com.docker.compose.project" }}.localhost`)
      - --api.dashboard=true
      - --api.insecure=true
      - --global.sendAnonymousUsage=false
      - --log.level=DEBUG
      - --log.format=common
      - --providers.file.filename=/dynamic.yml
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "/xxx/cert/CA/localhost/localhost.crt:/certs/localhost.crt:ro"
      - "/xxx/cert/CA/localhost/localhost.key:/certs/localhost.key:ro"
      - "./dynamic.yml:/dynamic.yml:ro"

Installer le certificat dans vos navigateurs et votre système

Ce qu'il faut transférer à votre navigateur (ou votre serveur), c'est le certificat racine, c'est-à-dire le rootCA.pem.

dans Firefox :

Dans Firefox, c'est dans Edit > Settings > Privacy & Security > View Certificates... > Import...

dans Chromium

Dans Chromium, c'est dans Settings > Privacy and security > Security > Manager certificates > Authorities > Import

Dans ubuntu (au niveau du serveur, pour curl et PHP par exemple)

Manipulation à faire en root sur une machine debian ou ubuntu

1
2
3
cd  /usr/local/share/ca-certificates/
cp /xxx/cert/CA/rootCA.crt .
update-ca-certificates

Tester le certificat

Pour avoir des infos sur le certificat et vérifier que tout se pase bien, on peut lancer la commande suivante :

1
openssl s_client -connect web.example.localhost:443 -verify_hostname web.example.localhost

Références et tuto utilisés

Pour toute remarque, vous pouvez me contacter sur twitter : @plv