Je suis récemment retombé sur un problème avec un certificat SSL que je n’avais pas eut le temps d’analyser assez profondemment la première fois mais dont l’explication parait pourtant évidente après coup …
Le Problème
Soit un site web exposé en HTTPS disons sur le nom de domaine mon.domaine.fr avec un certificat tout beau et tout neuf fraichement signé par une autorité de certification plus que reconnue (VeriSign dans mon cas).
Et pourtant lorsque je m’y connecte avec mon application Java, la JVM m’insulte à coup de : javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
L’utilisation de la toute dernière version de la JVM n’y change rien.
Idem côté Firefox : l’accès avec un FireFox 5.0 (donc très récent) fraichement installé génère le warning suivant :
This Connection is Untrusted (…)
mon.domaine.fr uses an invalid security certificate.
The certificate is not trusted because no issuer chain was provided.
(Error code: sec_error_unknown_issuer)
(Bon d’accord, avec cette version de FireFox, le message d’erreur est pourtant plus que significatif si on prend le temps de le comprendre…)
Si on ajoute un exception de sécurité à Firefox, la communication HTTPS fonctionne correctement. Le serveur semble donc être bien configuré pour gérer le SSL …
Pourtant le certificat exposé est bien signé par VeriSign. En particulier, le champ issuer/émetteur du certificat contient :
CN = VeriSign Class 3 International Server CA - G3 OU = Terms of use at https://www.verisign.com/rpa (c)10 OU = VeriSign Trust Network O = "VeriSign, Inc." C = US
Alors quoi ? Les certificats fraichement signés par une autorité reconnue ne fonctionnent pas par défaut ni avec Firefox ni avec Java ? Ca serait inquiétant non ? Surtout vu le prix qu’ils coûtent…
Heureusement il n’en est rien, ces certificats sont bien valides et toutes les versions de Firefox et toutes les versions de Java doivent pouvoir fonctionner avec sans avoir à les configurer au préalable.
Le problème ici est du côté du serveur…
Un peu de background sur les certificats
Un certificat est un document électronique qui contient en autre :
- une identité (dans notre cas l’identité d’un serveur via son nom DNS)
- une clef publique (associée à une clef privée détenue uniquement par le possesseur du certificat )
- l’identité de l’émetteur du certificat (l’autorité de certification ayant signé le certificat)
- une signature électronique garantissant l’intégrité du certificat ainsi que le fait que l’autorité de certification reconnait la véracité des informations du certificat.
La signature peut-être vérifiée à l’aide de la clef publique de l’émetteur.
Cette clef publique est mis à disposition à l’aide d’un certificat propre à l’émetteur. Celui-ci est aussi signé électroniquement éventuellement par une troisième autorité de certification. Et ainsi de suite …
On obtient alors une chaine de certification :
certificat A est signé par B, dont le certificat B est signé par C, etc …
Au bout de la chaîne, on trouve le certificat d’une autorité de certification racine qui est auto-signé (c’est à dire signée avec la clef privée associée à sa propre clef publique)
Il suffit normalement que votre navigateur ou votre application fasse confiance à l’un des certificats de la chaîne pour que le premier certificat soit lui aussi reconnu comme digne de confiance et que la communication SSL puisse s’établir sans erreur ou warning.
Les certificats émis par VeriSign ne sont pas directement signés par leur certificat racine. Ils sont signés par un certificat intermédiaire. (A noter : VeriSign utilise plusieurs certificats racine et plusieurs certificats intermédiaires suivant le niveau de sécurité et les autres options demandées …)
Dans notre cas, on a :
- le certificat de notre domaine mon.domaine.fr signé par “VeriSign Class 3 International Server CA – G3”
- le certificat intermédiaire “VeriSign Class 3 International Server CA – G3” signé par “VeriSign Class 3 Public Primary Certification Authority – G5”
- le certificat racine “VeriSign Class 3 Public Primary Certification Authority – G5”
Avec à la fois Java et Firefox qui font confiance par défaut au certificat racine “VeriSign Class 3 Public Primary Certification Authority – G5” …
( Note : pour la jvm, vous pouvez connaître la liste des autorités de confiance par défaut via cette commande : keytool -list -v -keystore %JDK_HOME%\jre\lib\security\cacerts -storepass changeit )
L’explication
Normalement, on a donc tout bon :
- le serveur nous expose un certificat valide faisant partie d’une chaine de confiance
- nous faisons confiance à la racine de cette chaine
Pourquoi cela ne fonctionne-t-il pas ??
Le problème ici c’est que le serveur ne nous exposait QUE le certificat de mon.domaine.fr …
Il est donc impossible pour la jvm ou pour Firefox de savoir que l’émetteur : “VeriSign Class 3 International Server CA – G3” a quelque part un certificat signé par “VeriSign Class 3 Public Primary Certification Authority – G5” (auquel ils font confiance)
Pour que cela fonctionne, il faut que le serveur envoie non seulement le certificat de mon.domaine.fr mais aussi le certificat intermédiaire associé à “VeriSign Class 3 International Server CA – G3” …
Une fois que l’on sait où chercher, la confirmation du diagnostique est simple :
- pour une application cliente java, on peut activer les debug ssl via l’option “-Djavax.net.debug=ssl”
La chaîne de certificats exposés par le serveur sera alors tracée sur la sortie standard après une ligne “*** Certificate chain” (et juste avant que la jvm vous insulte car elle ne fait pas confiance au certificat 😉 )
Si il y a qu’un seul certificat, on a bien le problème de configuration évoqué ici.
Avec un serveur web correctement configuré, la chaîne contiendra normalement plusieurs certificats. - on peut aussi utiliser cette commande de openssl si il est disponible sur votre système :
openssl s_client -showcerts -connect mon.domaine.fr:443
Elle affichera en particulier l’ensemble des certificats qui sont envoyés par le serveur.
Les solutions
Vous ne pouvez pas modifier le serveur https
Sans avoir la main sur le serveur web mal configuré, pas d’autre choix que de faire avec…
Il faut récupérer le certificat intermédiaire (sur le site de VeriSign) et l’ajouter aux certificats de confiance de votre navigateur et/ou de votre application java cliente.
Vous noterez que seul le certificat intermédiaire, celui ayant signé le certificat du serveur, est nécessaire. C’est généralement une mauvaise idée de mettre directement le certificat du serveur car lorsqu’il expirera il faudra obligatoirement le mettre à jour avec son remplaçant. Alors qu’il y a des chances que son remplaçant soit signé par le même certificat intermédiaire et alors tout continuera à fonctionner sans modification.
Dans une application java cliente, si vous avez une bonne raison, vous pouvez aussi bypasser complètement les contrôles de la JVM comme l’a expliqué Pierre-Alain ici : /2008/10/20/certificats-auto-signe-et-communication-ssl-en-java/
Vous pouvez corriger la configuration du serveur https
Si vous pouvez modifier ou faire modifier le serveur Web posant problème, c’est évidemment la meilleure solution.
Cette page web : http://www.ssl247.fr/support/install/, explique comment configurer correctement de nombreux serveurs ou load-balancer avec en particulier la façon d’installer le certificat intermédiaire pour qu’il soit exposé au client avec le certificat du serveur.
Une bonne nouvelle pour nous qui faisons du Java :
si vous avez généré votre Certificat Signing Request (CSR) à l’aide de keytool (ou un outil encapsulant ces fonctionnalités), il ne vous permettra pas d’importer la réponse avec le certificat signé, si le keystore utilisé ne contient pas déjà l’ensemble de la chaîne de certification. La chaine de certification est donc obligatoirement disponible dans le keystore.
Après cela dépend de votre serveur d’application.
Pour Tomcat par exemple (voir aussi configuration des connecteurs tomcat) :
- avec l’implémention Java du connecteur https, Tomcat expose directement l’ensemble de la chaîne présente dans le keystore
- par contre avec l’implémentation APR du connecteur https, il est possible de mal le configurer, exactement comme pour le module mod_ssl de Apache httpd : dans les deux cas, l’option SSLCertificateChainFile est celle qu’il ne faut pas oublier de configurer.