Разрешить небезопасное соединение HTTPS для Java JDK 11 HttpClient - PullRequest
0 голосов
/ 25 октября 2018

Иногда необходимо разрешить небезопасные HTTPS-соединения, например, в некоторых приложениях для сканирования веб-сайтов, которые должны работать с любым сайтом.Я использовал одно такое решение со старым HttpsURLConnection API, который недавно был заменен новым HttpClient API в JDK 11. Как разрешить небезопасные соединения HTTPS (самозаверяющий или устаревший сертификат)) с этим новым API?

UPD: код, который я пробовал (на Kotlin, но отображается непосредственно на 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()

Но при отправке у меня возникла исключительная ситуация (при попытке на localhost с self-подписанный сертификат):

java.io.IOException: No name matching localhost found

Использование IP-адреса вместо localhost дает исключение "Альтернативные имена субъектов отсутствуют".

После некоторой отладки JDK я обнаружил, что sslParams действительно игнорируется вместо, где выдается исключение, и используется локально созданный экземпляр.Дальнейшая отладка показала, что единственный способ повлиять на алгоритм проверки имени хоста - установить для системного свойства jdk.internal.httpclient.disableHostnameVerification значение true.И это, кажется, решение.SSLParameters в приведенном выше коде не имеет никакого эффекта, поэтому эту часть можно отбросить.Создание новой конфигурации только в глобальном масштабе выглядит серьезным недостатком дизайна в новом HttpClient API.

Ответы [ 2 ]

0 голосов
/ 31 октября 2018

Как уже предлагалось, вам нужен SSLContext, который игнорирует плохие сертификаты.Точный код, который получает SSLContext по одной из ссылок в вопросе, должен работать, в основном, создав нулевой TrustManager, который не смотрит на сертификаты:

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();

Проблема с вышесказанным заключается в том, что сервераутентификация полностью отключена для всех сайтов.Если был только один неверный сертификат, вы могли бы импортировать его в хранилище ключей с помощью:

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

и затем инициализировать SSLContext, используя InputStream, считывающий хранилище ключей следующим образом:

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);

Любой из нихИз вышеперечисленных решений будет работать для самоподписанного сертификата.Третий вариант - в случае, когда сервер предоставляет действительный, не самозаверяющий сертификат, но для хоста, который не соответствует ни одному из имен в сертификате, который он предоставляет, тогда системное свойство "jdk.internal.httpclient.disableHostnameVerification"может быть установлено в "true", и это заставит сертификат быть принятым так же, как API HostnameVerifier использовался ранее.Обратите внимание, что в обычных развертываниях не ожидается, что какой-либо из этих механизмов будет использоваться, так как должна быть возможность автоматической проверки сертификата, предоставленного любым правильно настроенным сервером HTTPS.

0 голосов
/ 25 октября 2018

В Java 11 вы также можете сделать то же самое , как указано в выбранном ответе в общей ссылке с HttpClient, построенной как:

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

сПример запроса

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

может быть выполнен как:

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

Обновление из комментариев, чтобы отключить проверку имени хоста, в настоящее время можно использовать системное свойство:

-Djdk.internal.httpclient.disableHostnameVerification

, который может быть установленным программно следующим образом: -

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