Подтверждение SSL с использованием самозаверяющих сертификатов и SSLEngine (JSSE)

Мне было поручено реализовать собственный/автономный веб-сервер Java, который может обрабатывать сообщения SSL и не-SSL на одном и том же порту.

Я реализовал сервер NIO, и он хорошо работает для запросов без SSL. У меня чертовски много времени с частью SSL, и мне действительно может понадобиться некоторое руководство.

Вот что я сделал до сих пор.

Чтобы различать сообщения SSL и не-SSL, я проверяю первый байт входящего запроса, чтобы убедиться, что это сообщение SSL/TLS. Пример:

   byte a = read(buf);
   if (totalBytesRead==1 && (a>19 && a<25)){
       parseTLS(buf);
   }

В методе parseTLS() я создаю экземпляр SSLEngine следующим образом:

   java.security.KeyStore ks = java.security.KeyStore.getInstance("JKS");
   java.security.KeyStore ts = java.security.KeyStore.getInstance("JKS");

   ks.load(new java.io.FileInputStream(keyStoreFile), passphrase);
   ts.load(new java.io.FileInputStream(trustStoreFile), passphrase);

   KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
   kmf.init(ks, passphrase);

   TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
   tmf.init(ts);

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


   SSLEngine serverEngine = sslc.createSSLEngine();
   serverEngine.setUseClientMode(false);
   serverEngine.setEnableSessionCreation(true);
   serverEngine.setWantClientAuth(true);

После создания экземпляра SSLEngine я обрабатываю входящие данные, используя методы unwrap/wrap, используя код прямо из официального Образцы JSSE:

   log("----");

   serverResult = serverEngine.unwrap(inNetData, inAppData);
   log("server unwrap: ", serverResult);
   runDelegatedTasks(serverResult, serverEngine);

   log("----");

   serverResult = serverEngine.wrap(outAppData, outNetData);
   log("server wrap: ", serverResult);
   runDelegatedTasks(serverResult, serverEngine);

Первая часть рукопожатия работает нормально. Клиент отправляет сообщение рукопожатия, а сервер отвечает сообщением с 4 записями:

handshake (22)
- server_hello (2) 
- certificate (11) 
- server_key_exchange (12)
- certificate_request (13) 
- server_hello_done (14)

Далее клиент отправляет сообщение, состоящее из трех частей:

handshake (22)
 - certificate (11)
 - client_key_exchange (16)

change_cipher_spec (20)
 - client_hello (1)

handshake (22)
 *** Encrypted Message ****

SSLEngine разворачивает запрос клиента и анализирует записи, но метод переноса создает 0 байтов со статусом подтверждения OK/NEED_UNWRAP. Другими словами, мне нечего отправлять обратно клиенту, и рукопожатие резко останавливается.

Вот где я застрял.

В отладчике я вижу, что SSLEngine, в частности ServerHandshaker, не находит никаких одноранговых сертификатов. Это довольно очевидно, когда я смотрю на запись сертификата от клиента, которая имеет длину 0 байт. Но почему?

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

Пара других моментов:

  • Хранилище ключей и хранилище доверенных сертификатов, упомянутые в приведенном выше фрагменте кода, были созданы с использованием следующего руководства: http://www.techbrainwave.com/?p=953
  • Я использую Firefox 10 и IE 9 в качестве клиента для тестирования сервера. Одинаковые результаты для обоих веб-клиентов.
  • Я использую Sun/Oracle JDK 6 и Java Secure Socket Extension (JSSE), которые поставляются вместе с ним.

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

Заранее спасибо!


person Peter    schedule 07.03.2012    source источник
comment
Просто глупый вопрос: вы создали и установили клиентские сертификаты, например. Firefox при подключении к вашему веб-серверу через HTTPS?   -  person Robert    schedule 07.03.2012
comment
да. FF предложил мне принять/отклонить сертификат. Я нажал «Я принимаю технические риски», и я вижу сертификат в разделе «Инструменты» -> «Параметры» -> «Дополнительно» -> «Шифрование» -> «Просмотр сертификатов» -> «Сервер». На самом деле без этого FF не отправит второе сообщение рукопожатия.   -  person Peter    schedule 07.03.2012
comment
Это не то, о чем я спрашивал. Я просил сертификат клиента. Аутентификация клиента SSL не является обязательной и в основном используется в корпоративной среде, часто в сочетании с чип-картами. Если у клиента не установлен сертификат (раздел Firefox Ваш сертификат), он его не отправит.   -  person Robert    schedule 08.03.2012
comment
Проверьте этот вопрос StackOverflow: stackoverflow.com/questions/14093546 /   -  person rdalmeida    schedule 10.09.2015


Ответы (1)


У вас есть NEED_UNWRAP, так что сделайте развертку. Это, в свою очередь, может дать вам BUFFER_UNDERFLOW, что означает, что вам нужно выполнить чтение и повторить попытку распаковки.

Точно так же, когда вы получаете NEED_WRAP, выполните перенос: это, в свою очередь, может дать вам BUFFER_OVERFLOW, что означает, что вам нужно выполнить запись и повторить перенос.

Это обертывание или развертывание может, в свою очередь, сказать вам, что нужно выполнить другую операцию: обернуть или развернуть.

Просто делайте то, что он говорит вам делать.

person user207421    schedule 07.03.2012
comment
Да, я попытался развернуть, увидев NEED_UNWRAP, но больше нечего читать. Я вижу, как второе клиентское рукопожатие полностью проходит по сети, и SSLEngine анализирует его полностью. К сожалению, он просто не производит никакого вывода, который я мог бы отправить обратно клиенту. Клиент немного ждет и в конце концов вешает трубку. - person Peter; 08.03.2012
comment
@Peter Может быть, нечего читать, но все же может быть что развернуть. Сделайте это так, как я сказал, пусть он скажет вам, когда читать, а не предполагать, что каждая развертка нуждается в чтении. Обычно первый ответ сервера в рукопожатии состоит, например, из трех отдельных сообщений, поэтому вы получите 3 NEED_WRAP подряд: и наоборот, клиент, вероятно, прочитает все это за одно чтение, но получит три последовательных NEED_UNWRAPS. То же самое относится ко всему рукопожатию. - person user207421; 08.03.2012
comment
Окей круто. Благодаря EJP и еще немного отладке я понимаю, что сейчас происходит. SSLEngine разворачивает одну запись за раз. Таким образом, во втором рукопожатии клиента на самом деле есть 3 записи: рукопожатие, change_cipher_spec и еще одно рукопожатие. Я вызывал unwrap только один раз, поэтому SSLEngine проанализировал только первую запись. Ему нужно было обработать все записи, прежде чем генерировать какие-либо байты для отправки обратно клиенту. Наконец-то я получаю данные приложения! - person Peter; 08.03.2012
comment
@Питер очень хорошо. Убедитесь, что вы продолжаете применять этот принцип к каждой обертке и развертке, чтобы вы могли обрабатывать повторные рукопожатия, как частичные, так и полные, происходящие во время разговора. Большинство реализаций, которые я видел, не прошли этот тест, включая некоторые известные. Мой проходит ;-) - person user207421; 09.03.2012
comment
@EJP, что происходит, когда вы получаете результат с NEED_UNWRAP и BUFFER_OVERFLOW? - person Paul; 31.03.2012
comment
@Paul Это означает, что вы не выполнили get() из буфера приема приложения в свое приложение. - person user207421; 31.03.2012
comment
? Я не вижу вызова get() ни в одном из рассмотренных мной примеров; когда ты звонишь? - person Paul; 31.03.2012
comment
@Paul Вы называете это, когда хотите прочитать некоторые данные в свое приложение. - person user207421; 31.03.2012
comment
Пробовал это как до, так и после вызова unwrap(), и ни в одном случае это ничего не изменило. - person Paul; 01.04.2012
comment
пытался вызвать get() как до, так и после unwrap(), но в обоих случаях он по-прежнему имел состояние, которое я описал вам выше. Я открыл более общий вопрос о SSLEngine, который не включает это, но дает представление о том, с чем я борюсь: stackoverflow.com/questions/9949806/android-sslengine-example - person Paul; 02.04.2012
comment
@Paul, пожалуйста, давайте придерживаться этой темы. NEED_UNWRAP в сочетании с BUFFER_OVERFLOW означает, что движок хочет, чтобы вы вызвали unwrap(), но в целевом буфере не было места для любой операции, которую вы пытались выполнить, будь то wrap() или unwrap(). Если это было wrap(), это означает, что вы должны сделать флип/запись/сжатие в сетевом буфере отправки. Если это было unwrap(), вам нужно выполнить flip/get/compact в буфере приема приложения. - person user207421; 02.04.2012
comment
@EJP› со мной все в порядке, но, честно говоря, это не помогает мне понять, как это должно работать. Есть ли пример или учебник, на который вы можете указать? До сих пор мой google-fu был сорван. - person Paul; 03.04.2012
comment
@ Пол, какую часть моего последнего комментария ты не понял? - person user207421; 03.04.2012
comment
@EJP У меня нет контекста; есть что-то, что я просто не понимаю в общем потоке кода, потребляющего SSLEngine. Я смог найти не так много примеров, и я думаю, что я просто упускаю общую картину. На данный момент, однако, я пытаюсь выпустить приложение за дверь, поэтому я просто делаю это сам. - person Paul; 04.04.2012
comment
@Paul Единственная «общая картина» заключается в том, что это конечный автомат, и вы должны делать то, что он вам говорит. Он знает протокол SSL, но не раскрывает вам его, он просто предоставляет вам серию запросов (NEED_*) и ответов (BUFFER_*FLOW). - person user207421; 06.04.2012
comment
Хорошо, но из-за отсутствия хороших руководств или документов я не понимаю, что представляют собой состояния, и что я должен делать, чтобы с ними справиться. Я ценю фрагменты, которые вы дали, но я не понимаю, как применить их в реальном приложении. - person Paul; 10.04.2012
comment
@Paul, это состояния NEED_WRAP, NEED_UNWRAP, BUFFER_UNDERFLOW и BUFFER_OVERFLOW, и с ними нужно обращаться, как я уже описал. На самом деле больше ничего нет. - person user207421; 10.04.2012
comment
Я сдаюсь. Я просто буду придерживаться того, что сам скрутил. - person Paul; 10.04.2012
comment
@Paul NEED_WRAP: вызовите wrap() из appSendBuffer в netSendBuffer. NEED_UNWRAP: вызовите unwrap() из netRecvBuffer в appRecvBuffer. BUFFER_UNDERFLOW при обёртке: в appSendHuffer оборачивать было нечего, чего быть не должно; при развертывании это означает, что вам нужно вызвать read() в netRecvBuffer и повторить развертывание, когда есть данные. BUFFER_OVERFLOW при переносе: вызовите write() в netSendBuffer и повторите перенос, когда он станет пустым; при развертывании: приложению необходимо использовать appRecvBuffer перед повторением развертывания, чего также не должно происходить. - person user207421; 22.07.2012
comment
Я написал что-то, что скрывает сложность SSLEngine и упрощает его использование. Это может быть полезно для некоторых людей, посещающих эту тему - github.com/kashifrazzaqui/sslfacade - person keios; 11.08.2013
comment
@kashif Это не имеет большого практического значения, если оно, откровенно говоря, не поддерживает возобновление сеанса или повторное рукопожатие, а также клиентские сертификаты. И если это требует изучения еще одного API, на мой взгляд. Можно обернуть все это в SSLSocketChannel,, и я сделал это в коммерческом продукте. Scatter/gather, без которого я могу жить, я не думаю, что это очень полезно реализовано в NIO. - person user207421; 22.10.2013
comment
@EJP У вас есть работающая реализация SSL Engine? - person Peter Penzov; 07.10.2015
comment
@PeterPenzov Действительно, а также рабочие реализации SSLSocketChannel и AsyncSSLSocketChannel. коммерческой лицензии, свяжитесь со мной вне офиса. - person user207421; 11.02.2016
comment
@EJP у тебя есть электронная почта? - person Peter Penzov; 12.02.2016