Java SSL подключиться, добавить серверный сертификат в хранилище ключей программно

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

SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket("localhost", 23467); try{ sslsocket.startHandshake(); } catch (IOException e) { //here I want to get the peer's certificate, conditionally add to local key store, then reauthenticate successfully } 

Существует много вещей о пользовательских SocketFactory, TrustManager, SSLContext и т. Д., И я не совсем понимаю, как они все подходят друг другу или что будет самым коротким путем для моей цели.

Это можно реализовать с помощью X509TrustManager .

Получите SSLContext с

 SSLContext ctx = SSLContext.getInstance("TLS"); 

Затем инициализируйте его своим пользовательским X509TrustManager с помощью SSLContext # init . SecureRandom и KeyManager [] могут быть пустыми. Последнее полезно, только если вы выполняете аутентификацию клиента, если в вашем сценарии только для проверки подлинности сервера вам не нужно его устанавливать.

Из этого SSLContext получите ваш SSLSocketFactory с помощью SSLContext # getSocketFactory и продолжите, как планировалось.

Что касается реализации X509TrustManager, это может выглядеть так:

 TrustManager tm = new X509TrustManager() { public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { //do nothing, you're the client } public X509Certificate[] getAcceptedIssuers() { //also only relevant for servers } public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { /* chain[chain.length -1] is the candidate for the * root certificate. * Look it up to see whether it's in your list. * If not, ask the user for permission to add it. * If not granted, reject. * Validate the chain using CertPathValidator and * your list of trusted roots. */ } }; 

Редактировать:

Райан был прав, я забыл объяснить, как добавить новый корень в существующие. Предположим, что ваш текущий KeyStore доверенных корней был получен из cacerts («хранилище доверия по умолчанию Java», которое поставляется вместе с вашим JDK, расположенным под jre / lib / security). Я предполагаю, что вы загрузили это хранилище ключей (оно находится в формате JKS) с загрузкой KeyStore # (InputStream, char []).

 KeyStore ks = KeyStore.getInstance("JKS"); FileInputStream in = new FileInputStream(" 

Пароль по умолчанию для cacerts является «changeit», если вы этого не сделали, ну, изменив его.

Затем вы можете добавить дополнительные доверенные корни, используя KeyStore # setEntry . Вы можете опустить ProtectionParameter (т. Е. Null), KeyStore.Entry будет TrustedCertificateEntry, который принимает новый root как параметр в свой конструктор.

 KeyStore.Entry newEntry = new KeyStore.TrustedCertificateEntry(newRoot); ks.setEntry("someAlias", newEntry, null); 

Если в какой-то момент вы хотите сохранить измененное хранилище доверия, вы можете добиться этого с помощью хранилища KeyStore # (OutputStream, char [] .

В API JSSE (часть, которая заботится о SSL / TLS) проверка того, является ли доверенный сертификат, необязательно связана с KeyStore . Это будет иметь место в подавляющем большинстве случаев, но при условии, что всегда будет один неверный. Это делается через TrustManager . Этот TrustManager , скорее всего, будет использовать хранилище KeyStore по умолчанию или значение, указанное через системное свойство javax.net.ssl.trustStore , но это не обязательно так, и этот файл не обязательно равен $JAVA_HOME/jre/lib/security/cacerts (все это зависит от настроек безопасности JRE).

В общем случае вы не можете получить KeyStore который используется управляющим доверием, который вы используете из приложения, тем более, если хранилище доверия по умолчанию используется без каких-либо параметров системного имущества. Даже если бы вы смогли узнать, что такое KeyStore , вы все равно столкнетесь с двумя возможными проблемами (по крайней мере):

  • Возможно, вы не сможете записать этот файл (что не обязательно является проблемой, если вы не сохраняете изменения навсегда).
  • Этот KeyStore может быть даже не основан на файлах (например, это может быть Keychain на OSX), и у вас может и не быть доступа для записи.

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

Это можно сделать, как показано в этом примере (с кратким модульным тестом ), если вы хотите использовать что-то вроде jSSLutils .

Подготовка вашего SSLContext будет выполнена следующим образом:

 KeyStore keyStore = // ... Create and/or load a keystore from a file // if you want it to persist, null otherwise. // In ServerCallbackWrappingTrustManager.CheckServerTrustedCallback CheckServerTrustedCallback callback = new CheckServerTrustedCallback { public boolean checkServerTrusted(X509Certificate[] chain, String authType) { return true; // only if the user wants to accept it. } } // Without arguments, uses the default key managers and trust managers. PKIXSSLContextFactory sslContextFactory = new PKIXSSLContextFactory(); sslContextFactory .setTrustManagerWrapper(new ServerCallbackWrappingTrustManager.Wrapper( callback, keyStore)); SSLContext sslContext = sslContextFactory.buildSSLContext(); SSLSocketFactory sslSocketFactory = sslContext.getSslSocketFactory(); // ... // Use keyStore.store(...) if you want to save the resulting keystore for later use. 

(Разумеется, вам не нужно использовать эту библиотеку и ее SSLContextFactory , но, по вашему усмотрению, реализовать собственный X509TrustManager , «обернуть» или не по умолчанию.)

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

Еще один момент, который следует учитывать при разработке обратного вызова, заключается в том, что диспетчер доверия не знает, какой сокет используется (в отличие от его X509KeyManager ), поэтому должно быть так же мало двусмысленно, какое действие пользователя вызвало всплывающее (или же вы хотите реализовать обратный вызов). Если выполняется несколько подключений, вы не захотите проверять неверный. Кажется возможным решить эту проблему, используя отдельный обратный вызов, SSLContext и SSLSocketFactory для SSLSocket, который должен создать новое соединение, каким-то образом связать SSLSocket и обратный вызов с действием, предпринятым пользователем для запуска этой попытки подключения в первом место.

  • Почему SSLSocket Java отправляет приветствие клиента версии 2?
  • Amazon S3 - HTTPS / SSL - Возможно ли это?
  • как добавить терминологическое имя объекта в ssl certs?
  • Как сделать HTTPS POST с Android?
  • Доверяйте всем сертификатам, используя HttpClient over HTTPS
  • Решение javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed Ошибка?
  • Android-шлюз, подписанный якорем HTTPS, для пути сертификации не найден
  • Как включить поддержку TLS 1.2 в Android-приложении (работает на Android 4.1 JB)
  • как получить закрытый ключ из файла PEM?
  • Проблемы с подключением через HTTPS / SSL через собственный клиент Java
  • Java: sun.security.provider.certpath.SunCertPathBuilderException: невозможно найти допустимый путь сертификации для запрошенной цели
  • Давайте будем гением компьютера.