The Daily Shaarli
Résumé : si t'utilises un Cisco ASA pour fournir un service de VPN TLS et que des utilisateurs GNU/Linux qui utilisent openconnect rencontrent l'erreur « Server certificate verify failed: signer not found », vérifie que ton ASA envoie bien le certificat x509 intermédiaire en sus de son certificat client / feuille au client lors de l'échange TLS.
Nous utilisons notre Cisco ASA pour fournir un VPN TLS. Il se présente auprès des clients VPN avec un certificat x509. Le nôtre expire dans 24 jours. Donc on le renouvelle. Sauf que ça ne fonctionne pas : impossible de charger ce certificat dans ASDM. Aucun message d'erreur explicite.
Nous achetons nos certificats x509 en passant par un groupement d'organisations. Ce dernier a fait le choix de changer d'autorité de certification. Le certificat x509 de la nouvelle autorité encapsule une clé RSA de 3072 bits (contre 2048 bits pour l'ancienne) et il est signé en utilisant la fonction de condensation SHA-384 (contre SHA-256 pour l'ancien). J'imagine que l'une de ces propriétés ne convient pas à notre Cisco ASA dont le logiciel n'est plus à jour (son remplacement est en cours, mais ça traîne au-delà de nos prévisions pessimistes qui nous ont conduit à ne plus renouveler le support de l'ASA).
Pour dépanner, nous décidons d'utiliser un certificat Let's Encrypt. Car, au niveau technique c'est ce qu'on a toujours eu : RSA 2048 bits + SHA-256. De plus, c'est la manière la plus simple et rapide (pas de procédure d'achat à déclencher, on tire un certificat, on teste et on voit direct le résultat). Notre ASA accepte le certificat. \o/ Victoire ?
Sur un système GNU/Linux, nous conseillons le client VPN openconnect, une implémentation libre pour plusieurs VPN TLS (Cisco, Juniper, etc.) qui s'intègre à GNOME network-manager. Depuis le changement de certificat x509, nos utilisateurs reçoivent une erreur :
$ sudo openconnect vpn.monorganisation.example
POST https://vpn.monorganisation.example/
Connected to 192.0.2.1:443
Négociation SSL avec vpn.monorganisation.example
Server certificate verify failed: signer not found
Certificate from VPN server "vpn.monorganisation.example" failed verification.
Reason: signer not found
To trust this server in future, perhaps add this to your command line:
--servercert pin-sha256:xAj8efLob9rYcspJ8p+2J+K9qTJlgsW1m300Ls+VyCs=
Enter 'oui' to accept, 'non' to abort; anything else to view: oui
Même erreur avec openconnect sur Android. Aucune erreur avec winwin + AnyConnect. Aucune erreur sur Apple (Mac OS, IOS), mais c'est normal : nous préconisons l'utilisation d'IPSec.
Qu'est-ce qui ne va pas ?
$ openssl s_client vpn.monorganisation.example:443
CONNECTED(00000003)
depth=0 CN = vpn.monorganisation.example
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 CN = vpn.monorganisation.example
verify error:num=21:unable to verify the first certificate
verify return:1
---
Certificate chain
0 s:CN = vpn.monorganisation.example
i:C = US, O = Let's Encrypt, CN = R3
---
Hum. Le VPN envoie uniquement son certificat (dit aussi certificat client ou certificat feuille). Celui-ci est signé par un certificat intermédiaire (dans le cas de Let's Encrypt, et jusqu'en 2025, il se nomme « R3 ») qui n'est pas communiqué par le serveur. Or, un certificat intermédiaire est jamais livré dans un catalogue / magasin des autorités de certification, car ce n'est pas son rôle. Donc, la bibliothèque TLS (OpenSSL pour openssl s_client et GnuTLS pour openconnect) échoue sur la vérification du certificat feuille.
Pour que le VPN ASA envoie aussi le certificat intermédiaire, il suffit d'ajouter ce dernier dans ASDM => « Configuration » => « Device Management » => « Certificate Management => CA Certificates ».
Plus d'erreur. \o/
$ openssl s_client vpn.monorganisation.example:443
CONNECTED(00000003)
depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = R3
verify return:1
depth=0 CN = vpn.monorganisation.example
verify return:1
---
Certificate chain
0 s:CN = vpn.monorganisation.example
i:C = US, O = Let's Encrypt, CN = R3
1 s:C = US, O = Let's Encrypt, CN = R3
i:O = Digital Signature Trust Co., CN = DST Root CA X3
Pourquoi ça fonctionnait avec AnyConnect ? Peut-être que celui-ci désactive la vérification du pair lors de l'échange TLS ou qu'il est moins stricte, ce qui est une très mauvaise idée dans tous les cas.
Résumé : Let's Encrypt a remplacé son vieux certificat intermédiaire nommé « Let's Encrypt Authority X3 » par un nouveau nommé « R3 ». Si tu utilises une implémentation minimaliste d'ACME (genre acme-tiny) qui récupère uniquement le certificat de ton site web auprès de Let's Encrypt, il faudra changer toi-même le certificat intermédiaire dans tes serveurs TLS, sans quoi une partie des clients web ne pourra plus accéder à ton service.
Depuis plusieurs semaines, plusieurs flux RSS sont en erreur « 60 SSL certificate problem: unable to get local issuer certificate » dans mon agrégateur RSS.
Pourtant, j'accède sans problèmes à ces sites web avec Firefox.
J'essaye d'accéder aux sites web depuis le serveur qui héberge mon lecteur de flux RSS :
$ wget https://exegetes.eu.org
--2021-01-18 22:30:45-- https://exegetes.eu.org/
Résolution de exegetes.eu.org (exegetes.eu.org)… 2001:910:800::82, 80.67.169.82
Connexion à exegetes.eu.org (exegetes.eu.org)|2001:910:800::82|:443… connecté.
Erreur : le certificat de « exegetes.eu.org » n’est pas de confiance.
Erreur: The certificate of « exegetes.eu.org » doesn't have a known issuer.
Même problème avec cURL :
$ curl https://exegetes.eu.org
curl: (60) SSL certificate problem: unable to get local issuer certificate
Au moins, le problème ne dépend pas d'une bibliothèque TLS en particulier car wget utilise GnuTLS alors que curl utilise OpenSSL.
Ce n'est plus une erreur relative à PHP comme à l'époque où mon chroot Apache httpd empêchait PHP d'utiliser le magasin des autorités de certification du système. Chroot que j'ai viré depuis afin d'activer HTTP/2.
Sur mon ordinateur de bureau, wget génère la même erreur.
Tous les sites web concernés utilisent Let's Encrypt malgré le danger que constitue sa force de frappe.
Creusons un peu :
$ openssl s_client -connect exegetes.eu.org:443
CONNECTED(00000003)
depth=0 CN = exegetes.eu.org
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 CN = exegetes.eu.org
verify error:num=21:unable to verify the first certificate
verify return:1
---
Certificate chain
0 s:CN = exegetes.eu.org
i:C = US, O = Let's Encrypt, CN = R3
1 s:C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
i:O = Digital Signature Trust Co., CN = DST Root CA X3
$ openssl s_client -connect jonathan.michalon.eu:443
CONNECTED(00000003)
depth=0 CN = jonathan.michalon.eu
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 CN = jonathan.michalon.eu
verify error:num=21:unable to verify the first certificate
verify return:1
---
Certificate chain
0 s:CN = jonathan.michalon.eu
i:C = US, O = Let's Encrypt, CN = R3
1 s:C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
i:O = Digital Signature Trust Co., CN = DST Root CA X3
$ openssl s_client -connect www.codelyoko.fr:443
CONNECTED(00000003)
depth=0 CN = www.codelyoko.fr
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 CN = www.codelyoko.fr
verify error:num=21:unable to verify the first certificate
verify return:1
---
Certificate chain
0 s:CN = www.codelyoko.fr
i:C = US, O = Let's Encrypt, CN = R3
Le testeur TLS de SSLLabs affiche « This server's certificate chain is incomplete […] Chain issues Incomplete, Extra certs ».
Les deux premiers cas sont identiques : le serveur présente le certificat x509 du site web (dit certificat feuille), signé par le certificat intermédiaire R3 de Let's Encrypt ainsi que le certificat X3 de Let's Encrypt signé par IdenTrust (DST). La chaîne de certification est cassée : site web - R3 !- X3 - DST. Comme le certificat intermédiaire R3 n'est pas disponible dans le magasin de certificats de Debian (et c'est normal, aucun certificat intermédiaire l'est), c'est game over.
Le dernier cas est encore plus simple : le certificat intermédiaire n'est pas communiqué par le serveur, seul le certificat feuille l'est. Comme R3 n'est pas présent dans le magasin des certificats des autorités de certification de Debian, c'est l'échec.
Que s'est-il passé ? Depuis le 20 décembre 2020, Let's Encrypt signe les nouveaux certificats de ses """"clients"""" avec son nouveau certificat intermédiaire nommé R3. Ce dernier remplace Let's Encrypt Authority X3 qui expire en 2021 (il a une durée de vie de 5 ans).
Tu vas me dire "ben oui mais l'API de Let's Encrypt retourne, en plus du certificat client, le nouveau certificat intermédiaire (R3), donc tout devrait fonctionner". Oui, sauf si un site web utilise un outil minimaliste de renouvellement automatique des certificats. Exemple : jonathan.michalon.eu utilise acme-tiny qui récupère uniquement le certificat client, charge à l'utilisateur de configurer un certificat intermédiaire indépendemment.
Pourquoi cela fonctionne avec Firefox et Chromium, alors ? Quand tu accèdes à un site web avec une chaîne valide, Firefox enregistre le certificat R3 et, du coup, est capable de le resortir pour compléter le maillon manquant quand un site web a une chaîne incomplète. Cela se fait sans que le certificat R3 apparaisse forcément dans le catalogue des certificats… Pour valider cette hypothèse, il suffit d'utiliser un vieux profil qui a jamais connu R3 et d'aller directement sur un site web défectueux : une erreur s'affiche. En allant sur un site web qui utilise R3 dans une chaîne complète puis en retournant sur le site web défectueux, plus aucune erreur s'affiche.
