управление несколькими записями в хранилище доверенных сертификатов java (custom) - PullRequest
2 голосов
/ 01 февраля 2012

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

KeyStore.Entry entry = new KeyStore.TrustedCertificateEntry(cert);
trustKeyStore.setEntry(alias, entry, null);

и затем сохраняет trustStore в файле. Во время регистрации многие пользователи регистрируют свои сертификаты, а затем они подключаются к другому серверу, который запрашивает как аутентификацию клиента, так и сервера, и здесь возникает проблема. Я думал, что этот «другой сервер» может использовать вышеупомянутый trustStore для выполнения SSLHandshake с пользователями, но похоже, что trustStore «запоминает» только последнего зарегистрированного пользователя. Я создаю SSLServerSocket следующим образом:

    tmf.init(trustKeyStore);
    kmf.init(keyStore, password);

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

    // create SSLSocketFactory and SSLSocket
    SSLServerSocketFactory sslSocketFactory =   (SSLServerSocketFactory)sslCtx.getServerSocketFactory();
    sslSocket = (SSLServerSocket)sslSocketFactory.createServerSocket(1234);

Как я упоминал ранее, аутентификация с обеих сторон работает нормально, но только для последней записи в trustStore. Итак, наконец, мой вопрос: нужно ли создавать отдельное хранилище доверенных сертификатов для каждого пользователя? Нужно ли перегружать TrustManager? В принципе, есть ли способ заставить SSLContext / SSLEngine перебрать все мои доверенные сертификаты и пройти через рукопожатие, если он найдет тот, который соответствует?

UPDATE

Думаю, мне нужно кое-что прояснить. Прежде всего, это не веб-приложение, а обычное клиент-серверное Java-приложение. Каждый используемый сертификат (сервер или клиент) самоподписан, но сертификат сервера встроен в клиентское приложение, поэтому, когда клиент подключается к серверу, он может сравнивать сертификаты (работает). Я также попытался решить эту проблему в точности так, как предложил Бруно - после пользовательских регистраций сервер проверяет, верны ли его данные, выдает ли он новый сертификат для клиента, добавляет его в свое хранилище доверенных сертификатов и отправляет его клиенту. Но это не сработало даже для последнего зарегистрированного клиента.

Ответы [ 2 ]

3 голосов
/ 01 февраля 2012

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

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

        KeyStore clientTrustedKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        clientTrustedKeyStore.load(null, "password".toCharArray());
        KeyStore.Entry entry = new KeyStore.TrustedCertificateEntry(cert);
        clientTrustedKeyStore.setEntry("alias", entry, null);
  1. При создании sslsockets на стороне клиента или сервера вам необходимо «передать» SSLContext с помощью вашего keyStore (сервера) или trustStore (клиента) в SSLContext:

    tmf = TrustManagerFactory.getInstance("SunX509");
    kmf = KeyManagerFactory.getInstance("SunX509");
    tmf.init(trustKeyStore);
    kmf.init(keyStore, password);
    
    // create, init SSLContext
        SSLContext sslCtx = SSLContext.getInstance("TLS");
        sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
    

, а затем создайте sslsocketfactory и сокеты.

  1. После создания сокетов (полезно только на стороне сервера) установите режим аутентификации:

    sslSocket.setNeedClientAuth (булево);

В случае настройки проверки с обеих сторон вам необходимо изменить следующее: режим аутентификации (очевидно), добавление сертификатов в trustStores как на стороне клиента, так и на стороне сервера.

Другая проблема с проверкой обеих сторон заключается в том, что приведенный ниже сценарий не будет работать (хотя, по крайней мере, для меня это кажется логичным): 1. Сервер выпускает свой собственный самозаверяющий сертификат, добавляет его в свое хранилище trustStore и затем отправляет его клиенту, чтобы он мог выполнить аутентификацию при последующих подключениях. Почему это не сработает? Потому что, когда клиент получает сертификат, он может добавить его в хранилище ключей только так:

ks.setCertificateEntry(alias,cert);

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

ks.setKeyEntry(alias,keyPair.getPrivate(),keyStorePassword,certChain);

, где certChain - то есть

Certificate[] certChain = {myCert};

Это было бы все, я уверен, что для некоторых людей все это вполне очевидно, но я надеюсь, что это поможет некоторым начинающим SSL, таким как я:)

3 голосов
/ 01 февраля 2012

(Отредактировано после внесения изменений в вопрос.)

Метод 1:

Одним из способов решения этой проблемы является использование СА, возможно, вашего собственного.

Метод 2:

Примите любой клиент сертификат на уровне рукопожатия.Хотя принятие любого серверного сертификата с точки зрения клиента является плохой идеей, так как оно вводит возможность атаки MITM (см. здесь и здесь , дляпример), принятие любого клиента сертификата во время рукопожатия не представляет тех же проблем.

В конце рукопожатия, при условии, что сертификат сервера все еще проверен, вы будете знать, что:

  • Связь между клиентом и сервером устанавливается безопасно, как это было бы с HTTPS без аутентификации сертификата клиента.
  • У клиента есть закрытый ключ для сертификата, который он имеетпредставлены во время рукопожатия.Это гарантируется сообщением TLS CertificateVerify, в котором закрытый ключ клиента используется для подписи всех сообщений, которыми обмениваются во время рукопожатия, включая сертификат сервера и сертификат клиента.На самом деле это не зависит от (доверия) проверки самого сертификата клиента и будет работать только в том случае, если открытый ключ в сертификате клиента может проверить подпись в сообщении TLS CertificateVerify.

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

Это позволит вам управлять своими пользователями и способомони используют свои сертификаты легче.Вы можете просто сохранить и / или найти сертификат текущего пользователя в базе данных, общей для ваших серверов.Вы можете получить доступ к сертификату, посмотрев на SSLSession в вашем приложении.Вы найдете сертификат пользователя в позиции 0 массива, возвращенного getPeerCertificates().Затем вы можете просто сравнить учетные данные, которые вы видите (опять же, потому что двустороннее рукопожатие прошло успешно) с теми, которые вы видели ранее, с целью аутентификации (и авторизации) в вашем приложении.

Обратите внимание, чтодаже если вы только что приняли самозаверяющие сертификаты, которые вы продолжаете добавлять, вам все равно придется отслеживать эту информацию открытого ключа как часть вашей системы, в дополнение к DN субъекта, поскольку у вас не будет контроля над субъектами DN (два пользователя могутвыберите то же самое, намеренно или нет).

Чтобы реализовать это, вам нужно настроить сервер на SSLContext, который использует X509TrustManager, который не выбрасываетлюбое исключение в его методе checkClientTrusted.(Если вы используете тот же SSLContext для других целей, я получу PKIX * ​​1051 * по умолчанию и делегирую ему вызовы checkServerTrusted.) Поскольку вы, вероятно, не будете знать, что такое Субъект / ЭмитентDN этих самозаверяющих сертификатов будет, вам следует отправить пустой список принятых CA (*), вернув пустой массив в getAcceptedIssuers() (см. Пример здесь ).


(*) TLS 1.0 молчит на эту тему, но TLS 1.1 явно разрешает пустые списки (с неопределенным поведением).На практике это будет работать с большинством браузеров, даже с SSLv3 / TLSv1.0: браузер предоставит полный список сертификатов на выбор в этом случае (или выберет первый, который он обнаружит, он настроен для автоматического выбора одного).


(я оставляю то, что более конкретно о HTTP, здесь ... Немного вне контекста, но это может представлять интерес для других.)

Метод1:

Вы можете интегрировать выдачу сертификата как часть вашей первоначальной регистрации.Когда пользователи регистрируют свои данные, они также просят сертификат, который сразу же выдается вашим регистрационным сервером в их браузер.(Если вы не знакомы с этим, есть способы выдачи сертификата в браузере, используя <keygen />, CRMF (в Firefox) или элементы управления ActiveX в IE, который зависит от версииWindows.)

Метод 2:

В среде сервлета вы можете получить его в запросе сервлета javax.servlet.request.X509Certificate атрибут

Кроме того, этоимеет тенденцию улучшать пользовательский опыт, так как отклонение сертификата клиента не обязательно прерывает соединение.Вы по-прежнему могли бы обслуживать веб-страницу (возможно, со статусом HTTP 401, хотя технически это должно сопровождаться механизмом вызова, а для сертификатов его нет), по крайней мере, сообщая своему пользователю, что что-то не так.Сбои рукопожатия (которые могут быть вызваны проверкой сертификата клиента в рамках рукопожатия в случае возникновения проблемы) могут быть очень запутанными для пользователей, которые действительно не знают о сертификатах.Недостатком является то, что довольно трудно «выйти» (аналогично аутентификации HTTP Basic) из-за отсутствия поддержки пользовательского интерфейса в большинстве браузеров.

Для реализации, если вы используете Apache Tomcat, вывозможно, вас заинтересует этот SSLImplementation или этот ответ , если вы используете Apache Tomcat 6.0.33 или выше.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...