web-dev-qa-db-fra.com

Autoriser la connexion HTTPS non sécurisée pour Java JDK 11 HttpClient

Parfois, il est nécessaire de permettre des connexions HTTPS non sécurisées, par exemple dans certaines applications d'exploration de sites Web qui devraient fonctionner avec n'importe quel site. J'ai utilisé ne de ces solutions avec l'ancienne API HttpsURLConnection qui a été récemment remplacée par nouvelle API HttpClient dans JDK 11. Comment autoriser les connexions HTTPS non sécurisées (auto-signées ou certificat expiré) avec cette nouvelle API?

UPD: Le code que j'ai essayé (dans Kotlin mais mappe directement en Java):

    val trustAllCerts = arrayOf<TrustManager>(object: X509TrustManager {
        override fun getAcceptedIssuers(): Array<X509Certificate>? = null
        override fun checkClientTrusted(certs: Array<X509Certificate>, authType: String) {}
        override fun checkServerTrusted(certs: Array<X509Certificate>, authType: String) {}
    })

    val sslContext = SSLContext.getInstance("SSL")
    sslContext.init(null, trustAllCerts, SecureRandom())

    val sslParams = SSLParameters()
    // This should prevent Host validation
    sslParams.endpointIdentificationAlgorithm = ""

    httpClient = HttpClient.newBuilder()
        .sslContext(sslContext)
        .sslParameters(sslParams)
        .build()

Mais lors de l'envoi, j'ai une exception (essayer sur localhost avec un certificat auto-signé):

Java.io.IOException: No name matching localhost found

L'utilisation de l'adresse IP au lieu de localhost donne l'exception "Aucun autre nom de sujet présent".

Après un débogage de JDK, j'ai trouvé que sslParams sont vraiment ignorés à l'endroit où l'exception est levée et une instance créée localement est utilisée. Un débogage supplémentaire a révélé que la seule façon d'affecter l'algorithme de vérification du nom d'hôte était de définir jdk.internal.httpclient.disableHostnameVerification propriété système à true. Et cela semble être une solution. SSLParameters dans le code ci-dessus n'a aucun effet, cette partie peut donc être supprimée. Le rendre configurable uniquement globalement ressemble à un grave défaut de conception dans la nouvelle API HttpClient.

11
vagran

Avec Java 11, vous pouvez également faire un effort similaire comme mentionné dans la réponse sélectionnée dans le lien partagé avec le HttpClient construit comme:

HttpClient httpClient = HttpClient.newBuilder()
        .connectTimeout(Duration.ofMillis(<timeoutInSeconds> * 1000))
        .sslContext(sc) // SSL context 'sc' initialised as earlier
        .sslParameters(parameters) // ssl parameters if overriden
        .build();

avec une demande d'échantillon

HttpRequest requestBuilder = HttpRequest.newBuilder()
            .uri(URI.create("https://www.example.com/getSomething"))
            .GET()
            .build();

peut être exécuté comme:

httpClient.send(requestBuilder, HttpResponse.BodyHandlers.ofString()); // sends the request

Mise à jour à partir des commentaires, pour désactiver la vérification du nom d'hôte, actuellement on peut utiliser la propriété système:

-Djdk.internal.httpclient.disableHostnameVerification

qui peut être défini par programme comme suit: -

final Properties props = System.getProperties(); 
props.setProperty("jdk.internal.httpclient.disableHostnameVerification", Boolean.TRUE.toString());
4
Naman

Comme déjà suggéré, vous avez besoin d'un SSLContext qui ignore les mauvais certificats. Le code exact qui obtient le SSLContext dans l'un des liens de la question devrait fonctionner en créant essentiellement un TrustManager nul qui ne regarde pas les certificats:

private static 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) {
        }
    }
};

public static  void main (String[] args) throws Exception {
    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(null, trustAllCerts, new SecureRandom());

    HttpClient client = HttpClient.newBuilder()
        .sslContext(sslContext)
        .build();

Le problème avec ce qui précède est évidemment que l'authentification du serveur est complètement désactivée pour tous les sites. S'il n'y avait qu'un seul mauvais certificat, vous pouvez l'importer dans un magasin de clés avec:

keytool -importcert -keystore keystorename -storepass pass -alias cert -file certfile

puis initialisez le SSLContext à l'aide d'un InputStream lisant le fichier de clés comme suit:

char[] passphrase = ..
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(i, passphrase); // i is an InputStream reading the keystore

KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX");
kmf.init(ks, passphrase);

TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
tmf.init(ks);

sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

L'une ou l'autre des solutions ci-dessus fonctionnera pour un certificat auto-signé. Une troisième option est dans le cas où le serveur fournit un certificat valide, non auto-signé mais pour un hôte qui ne correspond à aucun des noms du certificat qu'il fournit, puis une propriété système "jdk.internal.httpclient.disableHostnameVerification" peut être défini sur "true" et cela forcera le certificat à être accepté de la même manière que l'API HostnameVerifier a été utilisée précédemment. Notez que dans les déploiements normaux, aucun de ces mécanismes ne devrait être utilisé, car il devrait être possible de vérifier automatiquement le certificat fourni par tout serveur HTTPS correctement configuré.

6
louisburgh