Принять самоподписанный ssl-сертификат сервера в клиенте Java - PullRequest
185 голосов
/ 24 мая 2010

Это похоже на стандартный вопрос, но я нигде не смог найти четких указаний.

У меня есть код Java, пытающийся подключиться к серверу с, вероятно, самозаверяющим (или просроченным) сертификатом. Код сообщает о следующей ошибке:

[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

Насколько я понимаю, я должен использовать keytool и сказать java, что это нормально, разрешить это соединение.

Все инструкции по устранению этой проблемы предполагают, что я полностью владею keytool, например

создать закрытый ключ для сервера и импортировать его в хранилище ключей

Есть ли кто-нибудь, кто мог бы опубликовать подробные инструкции?

Я использую Unix, поэтому лучше использовать bash-скрипт.

Не уверен, что это важно, но код выполняется в jboss.

Ответы [ 7 ]

278 голосов
/ 24 мая 2010

У вас есть два основных варианта: добавить самозаверяющий сертификат в хранилище доверенных сертификатов JVM или настроить клиент на

Вариант 1

Экспортируйте сертификат из вашего браузера и импортируйте его в доверенное хранилище JVM (для создания цепочки доверия):

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

Вариант 2

Отключить проверку сертификата:

// 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) {
} 

Обратите внимание, что Я вообще не рекомендую вариант № 2 . Отключение диспетчера доверия разрушает некоторые части SSL и делает вас уязвимым для атак посредников. Предпочитайте вариант № 1 или, что еще лучше, используйте на сервере «настоящий» сертификат, подписанный известным центром сертификации.

8 голосов
/ 19 апреля 2016

Я преследовал эту проблему до поставщика сертификатов, который не является частью доверенных хостов JVM по умолчанию с JDK 8u74. Поставщик www.identrust.com , но это не тот домен, к которому я пытался подключиться. Этот домен получил свой сертификат от этого провайдера. См. Будет ли доверять перекрестному корню список по умолчанию в JDK / JRE? - прочитать пару записей. Также см. Какие браузеры и операционные системы поддерживают Let Encrypt .

Итак, чтобы подключиться к интересующему меня домену, у которого был выдан сертификат identrust.com, я сделал следующие шаги. По сути, мне нужно было получить сертификат identrust.com (DST Root CA X3), которому JVM доверяла. Я смог сделать это с помощью Apache HttpComponents 4.5 примерно так:

1: Получить сертификат от недоверенного на Инструкции по загрузке цепочки сертификатов . Нажмите на ссылку DST Root CA X3 .

2: Сохранить строку в файл с именем «DST Root CA X3.pem». Не забудьте добавить в файл строки «----- BEGIN CERTIFICATE -----» и «----- END CERTIFICATE -----» в начале и конце файла.

3: Создайте файл хранилища ключей Java, cacerts.jks, с помощью следующей команды:

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

4: Скопируйте полученное хранилище ключей cacerts.jks в каталог ресурсов вашего приложения java / (maven).

5: используйте следующий код, чтобы загрузить этот файл и прикрепить его к Apache 4.5 HttpClient. Это решит проблему для всех доменов, сертификаты которых выданы indetrust.com. Утилита Oracle включает сертификат в хранилище ключей 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();

Когда проект будет собран, cacerts.jks будет скопирован в путь к классам и загружен оттуда. В данный момент я не тестировал другие ssl-сайты, но если приведенный выше код «цепочек» в этом сертификате, они тоже будут работать, но, опять же, я не знаю.

Ссылка: Пользовательский контекст SSL и Как принять самозаверяющий сертификат с Java HttpsURLConnection?

7 голосов
/ 03 ноября 2017

Apache HttpClient 4.5 поддерживает принятие самозаверяющих сертификатов:

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

Это создает фабрику сокетов SSL, которая будет использовать TrustSelfSignedStrategy, регистрирует ее с помощью настраиваемого диспетчера соединений, а затем выполняет HTTP GET с использованием этого диспетчера соединений.

Я согласен с теми, кто повторяет «не делай этого на производстве», однако есть варианты использования для принятия самозаверяющих сертификатов вне производства; мы используем их в автоматизированных интеграционных тестах, поэтому мы используем SSL (как в рабочей среде), даже если он не работает на производственном оборудовании.

4 голосов
/ 11 ноября 2016

Вместо того, чтобы устанавливать фабрику сокетов по умолчанию (что для IMO плохо) - это повлияет только на текущее соединение, а не на каждое соединение SSL, которое вы пытаетесь открыть:

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();
1 голос
/ 04 июля 2017

Доверяйте всем SSL-сертификатам: - Вы можете обойти SSL, если хотите провести тестирование на тестовом сервере.Но не используйте этот код для производства.

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) { 
    }
}

}

Пожалуйста, вызовите эту функцию в функции onCreate () в Activity или в вашем классе приложения.

NukeSSLCerts.nuke();

Это можно использовать для залпа в Android.

0 голосов
/ 18 июня 2019

Принятый ответ - хорошо, но я хотел бы добавить кое-что к этому, поскольку я использовал IntelliJ на Mac и не мог заставить его работать с помощью переменной пути JAVA_HOME.

Оказывается, Java Home отличался при запуске приложения из IntelliJ.

Чтобы точно выяснить, где он находится, вы можете просто сделать System.getProperty("java.home"), поскольку именно здесь считываются доверенные сертификаты.

0 голосов
/ 24 мая 2010

Если «они» используют самозаверяющий сертификат, они сами должны предпринять шаги, необходимые для того, чтобы сделать их сервер пригодным для использования. В частности, это означает предоставление вам сертификата в автономном режиме надежным способом. Так что заставь их сделать это. Затем вы импортируете это в ваше хранилище доверенных сертификатов, используя keytool, как описано в Справочном руководстве JSSE. Даже не думайте о небезопасном TrustManager, размещенном здесь.

РЕДАКТИРОВАТЬ В интересах семнадцати (!) И многочисленных комментаторов ниже, которые явно не читали то, что я здесь написал, это не иерема против самозаверяющих сертификатов. Нет ничего плохого в том, что самоподписанные сертификаты при правильной реализации. Но, правильный способ их реализации - безопасная доставка сертификата через автономный процесс, а не через неаутентифицированный канал, который они будут использовать для аутентификации. Конечно, это очевидно? Это, безусловно, очевидно для каждой организации, в которой я работаю в сфере безопасности, от банков с тысячами отделений до моих собственных компаний. Клиентское кодовое «решение» для доверия всем сертификатам, включая самозаверяющие сертификаты, подписанные абсолютно кем-либо, или любым арбитражным органом, настроенным как CA, является ipso facto не безопасно. Это просто игра в безопасности. Это бессмысленно. У вас частная беседа, защищенная от взлома, ответа и инъекции, с кем-то. Кто-нибудь. Человек посередине. Имитатор Кто-нибудь. Вы можете также использовать простой текст.

...