259 votes

Accepter le certificat ssl auto-signé du serveur dans le client Java

Cela ressemble à une question standard, mais je n'ai pas trouvé de directives claires.

J'ai un code java qui essaie de se connecter à un serveur avec un certificat probablement auto-signé (ou expiré). Le code signale l'erreur suivante :

[HttpMethodDirector] I/O exception (javax.net.ssl.SSLHandshakeException) caught 
when processing request: sun.security.validator.ValidatorException: PKIX path 
building failed: sun.security.provider.certpath.SunCertPathBuilderException: 
unable to find valid certification path to requested target

Si je comprends bien, je dois utiliser keytool et dire à Java que c'est bon d'autoriser cette connexion.

Toutes les instructions pour résoudre ce problème supposent que je maîtrise parfaitement keytool, par exemple

générer une clé privée pour le serveur et l'importer dans le keystore

Y a-t-il quelqu'un qui pourrait poster des instructions détaillées ?

Je suis sous unix, donc bash script serait le mieux.

Je ne sais pas si c'est important, mais le code a été exécuté dans Jboss.

2 votes

Voir Comment accepter un certificat auto-signé avec une HttpsURLConnection Java ? . Évidemment, il serait préférable que vous puissiez faire en sorte que le site utilise un certificat valide.

2 votes

Merci pour le lien, je ne l'avais pas vu en cherchant. Mais les deux solutions proposées impliquent un code spécial pour envoyer une requête et j'utilise un code existant (amazon ws client for java). Respectivement, c'est leur site que je connecte et je ne peux pas résoudre ses problèmes de certificat.

5 votes

@MatthewFlaschen - "Évidemment, ce serait mieux si vous pouviez faire en sorte que le site utilise un cert valide..." - Un certificat auto-signé est un certificat valide si le client lui fait confiance. Beaucoup pensent que le fait de confier la confiance au cartel CA/Navigateur est un défaut de sécurité.

344voto

Pascal Thivent Points 295221

Deux options s'offrent à vous : ajouter le certificat auto-signé au truststore de votre JVM ou configurer votre client pour qu'il

Option 1

Exportez le certificat de votre navigateur et importez-le dans votre truststore JVM (pour établir une chaîne de confiance) :

<JAVA_HOME>\bin\keytool -import -v -trustcacerts
-alias server-alias -file server.cer
-keystore cacerts.jks -keypass changeit
-storepass changeit 

Option 2

Désactiver la validation du certificat :

// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] { 
    new X509TrustManager() {     
        public java.security.cert.X509Certificate[] getAcceptedIssuers() { 
            return new X509Certificate[0];
        } 
        public void checkClientTrusted( 
            java.security.cert.X509Certificate[] certs, String authType) {
            } 
        public void checkServerTrusted( 
            java.security.cert.X509Certificate[] certs, String authType) {
        }
    } 
}; 

// Install the all-trusting trust manager
try {
    SSLContext sc = SSLContext.getInstance("SSL"); 
    sc.init(null, trustAllCerts, new java.security.SecureRandom()); 
    HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (GeneralSecurityException e) {
} 
// Now you can access an https URL without having the certificate in the truststore
try { 
    URL url = new URL("https://hostname/index.html"); 
} catch (MalformedURLException e) {
} 

Notez que Je ne recommande pas du tout l'option 2. . La désactivation du gestionnaire de confiance met en échec certaines parties de SSL et vous rend vulnérable aux attaques de type "man in the middle". Préférez l'option 1 ou, mieux encore, demandez au serveur d'utiliser un "vrai" certificat signé par une autorité de certification bien connue.

1 votes

J'en ai essayé 2 (pour tester), mais ça ne semble pas fonctionner. Peut-être, parce que le client amazon ws utilise les classes apache HttpClient et HttpMethod pour faire un appel. J'ai exécuté le code juste avant cet appel. Par ailleurs, voulez-vous dire dans la dernière phrase que le fait que notre serveur ait un mauvais certificat peut empêcher la connexion ? Je pensais qu'il s'agissait uniquement de vérifier le certificat du serveur cible. Merci, et je vais voir ce que je peux faire avec keytool.

8 votes

Pas seulement l'attaque MIM. Elle vous rend vulnérable si vous vous connectez au mauvais site. Elle n'est absolument pas sécurisée. Voir RFC 2246. Je suis opposé à l'affichage de ce TrustManager à tout moment. Il n'est même pas correct au regard de ses propres spécifications.

15 votes

@EJP Je ne recommande vraiment pas la deuxième option (j'ai mis à jour ma réponse pour que ce soit clair). Cependant, ne pas la publier ne résoudra rien (il s'agit d'une information publique) et ne mérite pas, à mon avis, un downvote.

43voto

Johannes Brodwall Points 3469

Il existe une meilleure alternative que de faire confiance à tous les certificats : Créez un TrustStore qui fait spécifiquement confiance à un certificat donné et l'utiliser pour créer une SSLContext à partir duquel on peut obtenir le SSLSocketFactory à régler sur le HttpsURLConnection . Voici le code complet :

File crtFile = new File("server.crt");
Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(new FileInputStream(crtFile));
// Or if the crt-file is packaged into a jar file:
// CertificateFactory.getInstance("X.509").generateCertificate(this.class.getClassLoader().getResourceAsStream("server.crt"));

KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry("server", certificate);

TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);

HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
connection.setSSLSocketFactory(sslContext.getSocketFactory());

Vous pouvez également charger le KeyStore directement à partir d'un fichier ou récupérer le certificat X.509 à partir d'une source fiable.

Notez qu'avec ce code, les certificats dans cacerts ne sera pas utilisé. Cette HttpsURLConnection ne fera confiance qu'à ce certificat spécifique.

0 votes

La [documentation Android] ( developer.Android.com/training/articles/security-ssl#SelfSigned ) donne en gros votre explication. Il donne juste un peu plus d'explications, de code et d'avertissements.

6 votes

À mon avis, cela devrait être la réponse acceptée ! L'avantage de cette méthode est que vous faites tout par programme tout en maintenant la sécurité et que vous ne modifiez pas le magasin de confiance du JRE. L'option n°2 de la réponse acceptée est complètement fausse du point de vue de la sécurité. Merci d'avoir fourni cette solution !

1 votes

Si quelqu'un a l'intention d'emballer le certificat dans un fichier jar, alors remplacez InputStream dans la méthode generateCertificate à ça : this.class.getClassLoader().getResourceAsStream("server.crt"‌​)

17voto

spiffy Points 201

Apache HttpClient 4.5 accepte les certificats auto-signés :

SSLContext sslContext = SSLContexts.custom()
    .loadTrustMaterial(new TrustSelfSignedStrategy())
    .build();
SSLConnectionSocketFactory socketFactory =
    new SSLConnectionSocketFactory(sslContext);
Registry<ConnectionSocketFactory> reg =
    RegistryBuilder.<ConnectionSocketFactory>create()
    .register("https", socketFactory)
    .build();
HttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(reg);        
CloseableHttpClient httpClient = HttpClients.custom()
    .setConnectionManager(cm)
    .build();
HttpGet httpGet = new HttpGet(url);
CloseableHttpResponse sslResponse = httpClient.execute(httpGet);

Ceci construit une usine de socket SSL qui utilisera la fonction TrustSelfSignedStrategy Il l'enregistre auprès d'un gestionnaire de connexions personnalisé, puis effectue un GET HTTP en utilisant ce gestionnaire de connexions.

Je suis d'accord avec ceux qui disent "ne faites pas ça en production", mais il y a des cas d'utilisation pour accepter des certificats auto-signés en dehors de la production ; nous les utilisons dans les tests d'intégration automatisés, de sorte que nous utilisons SSL (comme en production) même lorsque nous ne fonctionnons pas sur le matériel de production.

11voto

Nicholas Points 78

Je suis tombé sur un fournisseur de certificats qui ne fait pas partie des hôtes de confiance par défaut de la JVM à partir de la date d'entrée en vigueur de l'accord. JDK 8u74 . Le fournisseur est www.identrust.com mais ce n'était pas le domaine auquel j'essayais de me connecter. Ce domaine avait obtenu son certificat auprès de ce fournisseur. Voir La couverture de la racine croisée sera-t-elle assurée par la liste par défaut du JDK/JRE ? -- lire en bas quelques entrées. Voir aussi Quels sont les navigateurs et les systèmes d'exploitation compatibles avec Let's Encrypt ? .

Ainsi, afin de me connecter au domaine qui m'intéressait, qui avait un certificat émis par identrust.com J'ai suivi les étapes suivantes. En gros, j'ai dû récupérer l'adresse identrust.com ( DST Root CA X3 ) pour que la JVM lui fasse confiance. J'ai pu le faire en utilisant Apache HttpComponents 4.5 comme suit :

1 : Obtenez le certificat auprès d'indettrust à l'adresse suivante Instructions de téléchargement de la chaîne de certificats . Cliquez sur le DST Root CA X3 lien.

2 : Sauvegardez la chaîne dans un fichier nommé "DST Root CA X3.pem". Assurez-vous d'ajouter les lignes "-----BEGIN CERTIFICATE-----" et "-----END CERTIFICATE-----" dans le fichier au début et à la fin.

3 : Créez un fichier keystore java, cacerts.jks avec la commande suivante :

keytool -import -v -trustcacerts -alias IdenTrust -keypass yourpassword -file dst_root_ca_x3.pem -keystore cacerts.jks -storepass yourpassword

4 : Copiez le keystore cacerts.jks résultant dans le répertoire des ressources de votre application java/(maven).

5 : Utilisez le code suivant pour charger ce fichier et le joindre au HttpClient d'Apache 4.5. Ceci résoudra le problème pour tous les domaines qui ont des certificats émis par indetrust.com util oracle inclut le certificat dans le keystore par défaut du JRE.

SSLContext sslcontext = SSLContexts.custom()
        .loadTrustMaterial(new File(CalRestClient.class.getResource("/cacerts.jks").getFile()), "yourpasword".toCharArray(),
                new TrustSelfSignedStrategy())
        .build();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
        sslcontext,
        new String[] { "TLSv1" },
        null,
        SSLConnectionSocketFactory.getDefaultHostnameVerifier());
CloseableHttpClient httpclient = HttpClients.custom()
        .setSSLSocketFactory(sslsf)
        .build();

Lorsque le projet est construit, le fichier cacerts.jks est copié dans le classpath et chargé à partir de là. Je n'ai pas, à ce stade, testé contre d'autres sites ssl, mais si le code ci-dessus "enchaîne" dans ce certificat alors ils fonctionneront aussi, mais encore une fois, je ne sais pas.

Référence : Contexte SSL personnalisé et Comment accepter un certificat auto-signé avec une HttpsURLConnection Java ?

0 votes

La question porte sur un certificat auto-signé. Cette réponse ne l'est pas.

6 votes

Lisez la question (et la réponse) d'un peu plus près.

6voto

Jon Daniel Points 85

Plutôt que de définir l'usine de socket par défaut (ce qui, selon l'OMI, n'est pas une bonne chose), cela n'affectera que la connexion en cours et non chaque connexion SSL que vous essayez d'ouvrir :

URLConnection connection = url.openConnection();
    // JMD - this is a better way to do it that doesn't override the default SSL factory.
    if (connection instanceof HttpsURLConnection)
    {
        HttpsURLConnection conHttps = (HttpsURLConnection) connection;
        // Set up a Trust all manager
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager()
        {

            public java.security.cert.X509Certificate[] getAcceptedIssuers()
            {
                return null;
            }

            public void checkClientTrusted(
                java.security.cert.X509Certificate[] certs, String authType)
            {
            }

            public void checkServerTrusted(
                java.security.cert.X509Certificate[] certs, String authType)
            {
            }
        } };

        // Get a new SSL context
        SSLContext sc = SSLContext.getInstance("TLSv1.2");
        sc.init(null, trustAllCerts, new java.security.SecureRandom());
        // Set our connection to use this SSL context, with the "Trust all" manager in place.
        conHttps.setSSLSocketFactory(sc.getSocketFactory());
        // Also force it to trust all hosts
        HostnameVerifier allHostsValid = new HostnameVerifier() {
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };
        // and set the hostname verifier.
        conHttps.setHostnameVerifier(allHostsValid);
    }
InputStream stream = connection.getInputStream();

2 votes

Votre checkServerTrusted n'implémente pas la logique nécessaire pour faire confiance au certificat. y s'assurer que les certificats non fiables sont rejetés. Cela pourrait faire une bonne lecture : Le code le plus dangereux du monde : la validation des certificats SSL dans les logiciels non-browsers .

1 votes

Votre getAcceptedIssuers() n'est pas conforme à la spécification, et cette "solution" reste radicalement non sécurisée.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X