2012-02-01 8 views
3

Я прочитал несколько связанных вопросов, но ни один из них не помогает мне. Итак, позвольте мне рассказать вам немного истории: Я пишу систему голосования, в которой у вас есть несколько серверов, которые управляют регистрацией, обменом карточками и голосованием. Во время пользователь регистрации подают его персональные данные, сервер проверяет, если данные совпадают в базе данных, а затем принимает сертификат пользователя и добавляет его доверенные сертификаты, как это:Управление несколькими записями в java (custom) truststore

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

и затем сохраняет доверенное хранилище в файле , Во время регистрации многие пользователи регистрируют свои сертификаты, а затем подключаются к другому серверу, который запрашивает аутентификацию клиентов и серверов, и здесь возникает проблема. Я подумал, что этот «другой сервер» может использовать вышеупомянутый 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 swing для клиент-сервер. Каждый отдельный сертификат (сервер или клиент) самоподписан, но сертификат сервера встроен в клиентское приложение, поэтому, когда клиент подключается к серверу, он может сравнивать сертификаты (работы). Я также попытался решить его точно так же, как предложил Бруно - после регистрации пользователей сервер проверяет, действительно ли его данные действительны, если он выдает новый сертификат для клиента, добавляет его в свою доверенность и отправляет его клиенту. Но это не сработало даже для последнего зарегистрированного клиента.

+0

Используете ли вы * разные * псевдонимы для каждого пользователя, или вы каждый раз переписываете запись доверия? – Borealid

+0

Просто интересно, являются ли эти пользовательские сертификаты самозаверяющими? Как они получают свои сертификаты? – Bruno

+0

, конечно, я использую разные псевдонимы :) Я проверил, и trsutStore.aliases() действительно дает весь список. Все пользовательские сертификаты самоподписаны. – michauwilliam

ответ

3

Я решил проблему. Как это часто бывает, на моей стороне была глупая ошибка - один из серверов переписывал все хранилище ключей. Я потратил много времени на то, как настроить эту связь 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 (сервер) или доверенных (клиента) к 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); 

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

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

где certChain является то

Certificate[] certChain = {myCert}; 

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

+0

Не проверять, что настоящий сертификат находится в приложении, наверняка вызовет у вас много проблем, так как вам не придется сравнивать или ссылаться (даже для аудита). Если вы не завладеете открытым ключом пользователя, вам не будет ничего, чтобы связать любой запрос с каким-либо конкретным пользователем, поэтому вы не будете знать, уже ли они использовали систему. Вы не сможете привязать DN-объекты, которые вы получаете к любому фактическому пользователю разумным способом (все сертификаты самоподписываются), и иногда вы можете столкнуться с такими же DN. – Bruno

+1

Что вы имеете в виду? У меня есть открытый ключ пользователя, он находится в сертификате, который я храню на сервере. В любом случае, в этом проекте пользователь может проголосовать или зарегистрироваться столько раз, сколько он хочет, он просто ничего не сделает во второй раз, у каждого пользователя есть уникальный идентификатор, который он получает через защищенный канал (не задействованы компьютеры), а голосование - основанный на сигнатуре RSA, которая предотвращает множественные голоса. – michauwilliam

+0

Не совсем понятно, какова цель аутентификации на уровне TLS через хранилище доверия. (Я думаю, вы должны что-то делать с ключом cert/pub на уровне приложения после рукопожатия.) Было бы неплохо увидеть более подробную информацию о шагах, в которых вы используете сертификаты, чтобы иметь возможность сказать больше. – Bruno

2

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

Метод 1:

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

Способ 2:

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

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

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

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

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

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

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


(*) 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) из-за отсутствия поддержки пользовательского интерфейса в большинстве браузеров.

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

+0

благодарю вас за ваши усилия :) – michauwilliam