Принять серверный самоподписанный сертификат ssl в Java-клиенте

Он выглядит как стандартный вопрос, но я не мог найти четкие направления в любом месте.

У меня есть 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 script будет лучше.

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

Ответ 1

Здесь у вас есть два основных варианта: добавить самоподписанный сертификат в хранилище доверенных сертификатов 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 или, что еще лучше, используйте на сервере "настоящий" сертификат, подписанный известным центром сертификации.

Ответ 2

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

Итак, чтобы подключиться к интересующему меня домену, у которого был выдан сертификат на 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?

Ответ 3

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-сокет factory, который будет использовать TrustSelfSignedStrategy, регистрирует его с помощью настраиваемого диспетчера соединений, а затем HTTP GET использует этот диспетчер соединений.

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

Ответ 4

Вместо установки установочного сокета factory (какой ИМО плохой) - yhis просто повлияет на текущее соединение, а не на каждое 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();

Ответ 5

Доверяйте всем 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();

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

Ответ 6

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

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

Ответ 7

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

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

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

Ответ 8

Есть лучшая альтернатива доверию всем сертификатам: создайте TrustStore которое определенно доверяет данному сертификату, и используйте его для создания SSLContext из которого можно получить SSLSocketFactory для установки на HttpsURLConnection. Вот полный код:

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

Вы также можете загрузить KeyStore непосредственно из файла или получить сертификат X.509 из любого надежного источника.

Обратите внимание, что с этим кодом сертификаты в cacerts не будут использоваться. Этот конкретный HttpsURLConnection будет доверять только этому конкретному сертификату.