web-dev-qa-db-fra.com

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

Cela ressemble à une question standard, mais je ne trouvais nulle part des instructions claires.

J'ai le code Java qui tente 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 qu'il est correct d'autoriser cette connexion.

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

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

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

Je suis sous unix, le script bash serait donc préférable.

Pas sûr que ce soit important, mais le code est exécuté en jboss.

193
Nikita Rybak

Ici, vous avez essentiellement deux options: ajouter le certificat auto-signé à votre magasin de clés certifiées JVM ou configurer votre client pour

Option 1

Exportez le certificat depuis 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 l'option n ° 2 du tout. La désactivation du gestionnaire de confiance élimine certaines parties de SSL et vous rend vulnérable aux attaques de type "homme dans le milieu". Préférez l’option n ° 1 ou, mieux encore, demandez au serveur d’utiliser un "vrai" certificat signé par une autorité de certification connue.

289
Pascal Thivent

J'ai résolu ce problème auprès d'un fournisseur de certificats ne faisant pas partie des hôtes sécurisés JVM par défaut à partir de JDK 8u74. Le fournisseur est www.identrust.com , mais ce n’était pas le domaine auquel je tentais de me connecter. Ce domaine avait obtenu son certificat de ce fournisseur. Voir Est-ce que la couverture de racine transversale fera confiance à la liste par défaut du JDK/JRE? - lit quelques entrées. Voir également Quels navigateurs et systèmes d’exploitation prennent en charge Let’s Encrypt .

Ainsi, afin de me connecter au domaine qui m'intéressait, qui avait un certificat émis par identrust.com, j'ai effectué les étapes suivantes. En gros, il fallait que le certificat identrust.com (DST Root CA X3) soit approuvé par la machine virtuelle. J'ai été capable de faire cela en utilisant Apache HttpComponents 4.5 comme ceci:

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

2: enregistrez 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 de clés Java, cacerts.jks, à l'aide de 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 fichier de clés cacerts.jks obtenu dans le répertoire de ressources de votre application Java/(maven).

5: Utilisez le code suivant pour charger ce fichier et le joindre à Apache 4.5 HttpClient. Cela résoudra le problème pour tous les domaines pour lesquels des certificats ont été émis à partir de indetrust.com util. Oracle inclut le certificat dans le fichier de clés par défaut de 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 chemin de classe et chargé à partir de là. Pour le moment, je n'ai pas testé d'autres sites SSL, mais si le code ci-dessus "enchaîne" ce certificat, ils fonctionneront également, mais encore une fois, je ne le sais pas.

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

8
K.Nicholas

Apache HttpClient 4.5 prend en charge l’acceptation des 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);

Cela crée une fabrique de sockets SSL qui utilisera le TrustSelfSignedStrategy, l'enregistre auprès d'un gestionnaire de connexions personnalisé, puis effectue un HTTP GET à l'aide de ce gestionnaire de connexions.

Je suis d'accord avec ceux qui scandent "ne le faites pas en production", mais il existe des cas d'utilisation permettant d'accepter des certificats autosignés en dehors de la production; nous les utilisons dans des tests d'intégration automatisés, de sorte que nous utilisons SSL (comme en production) même lorsque nous n'exécutons pas le matériel de production.

8
spiffy

Plutôt que de définir la fabrique de socket par défaut (ce qui est une mauvaise chose pour OMI), cela affectera uniquement la connexion actuelle plutôt que toutes les connexions 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();
4
Jon Daniel

Faites confiance à tous les certificats SSL: - Vous pouvez contourner SSL si vous souhaitez effectuer un test sur le serveur de test. Mais n'utilisez pas ce code pour la production.

public static class NukeSSLCerts {
protected static final String TAG = "NukeSSLCerts";

public static void nuke() {
    try {
        TrustManager[] trustAllCerts = new TrustManager[] { 
            new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    X509Certificate[] myTrustedAnchors = new X509Certificate[0];  
                    return myTrustedAnchors;
                }

                @Override
                public void checkClientTrusted(X509Certificate[] certs, String authType) {}

                @Override
                public void checkServerTrusted(X509Certificate[] certs, String authType) {}
            }
        };

        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, new SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String arg0, SSLSession arg1) {
                return true;
            }
        });
    } catch (Exception e) { 
    }
}

}

Veuillez appeler cette fonction dans la fonction onCreate () dans Activity ou dans votre classe d'application.

NukeSSLCerts.nuke();

Ceci peut être utilisé pour Volley sous Android.

1
Ashish Saini

Il existe une meilleure alternative que de faire confiance à tous les certificats: créez une TrustStore approuvant spécifiquement un certificat donné et utilisez-la pour créer une SSLContext à partir de laquelle obtenir le SSLSocketFactory à définir sur le HttpsURLConnection. Voici le code complet:

File crtFile = new File("server.crt");
Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(new FileInputStream(crtFile));

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 de n'importe quelle source approuvée.

Notez qu'avec ce code, les certificats dans cacerts ne seront pas utilisés. Ce HttpsURLConnection ne fera confiance qu’à ce certificat spécifique.

0
Johannes Brodwall

S'ils utilisent un certificat auto-signé, il leur appartient de prendre les mesures nécessaires pour rendre leur serveur utilisable. Concrètement, cela signifie qu’ils vous fourniront leur certificat hors ligne de manière fiable. Alors demandez-leur de le faire. Vous l'importez ensuite dans votre magasin de clés de confiance à l'aide de l'outil keytool, comme décrit dans le Guide de référence JSSE. Ne pensez même pas à l’insécurité de TrustManager publiée ici.

EDIT Au profit des dix-sept (!) votants et de nombreux commentateurs ci-dessous, qui n'ont clairement pas lu Ce que j'ai écrit ici, c'est pas un jeremiad contre des certificats auto-signés. Il n’ya rien de mal avec les certificats auto-signés s’ils sont correctement implémentés. Mais, la bonne façon de les implémenter est d’avoir le certificat délivré en toute sécurité via un processus hors ligne, plutôt que via le canal non authentifié qu'ils vont utiliser pour s'authentifier. C'est sûrement évident? Cela est certainement évident pour toutes les organisations soucieuses de la sécurité pour lesquelles j'ai travaillé, des banques avec des milliers de succursales à mes propres entreprises. La 'solution' de base de code côté client consistant à faire confiance à tous les certificats , y compris les certificats auto-signés, signés par quiconque, ou par tout organisme arbitral se constituant en tant que CA, est ipso facto non sécurisé. C'est juste jouer à la sécurité. C'est inutile. Vous avez une conversation privée, inviolable, sans réponse ni preuve avec une injection ... avec quelqu'un. N'importe qui. Un homme au milieu. Un imitateur. N'importe qui. Vous pouvez aussi bien utiliser du texte en clair.

0
user207421

La réponse acceptée convient, mais j'aimerais ajouter quelque chose car j'utilisais IntelliJ sur Mac et que je ne pouvais pas le faire fonctionner avec la variable de chemin Java_HOME.

Il s’avère que Java Home était différent lors de l’exécution de l’application à partir d’IntelliJ.

Pour savoir exactement où il se trouve, vous pouvez simplement utiliser System.getProperty("Java.home"), à partir de là, à partir duquel les certificats sécurisés sont lus.

0