Сокет Java TLS: доверенный сертификат не найден

Позвольте мне быстро объяснить, что я пытаюсь сделать. Я пытаюсь создать свой собственный сервис push-уведомлений Apple в Java (для тестирования). Этот сервис работает благодаря сокету TLS.

У меня есть Java-клиент для создания сокета TLS для отправки push-уведомлений в APN. Я изменил URL хоста, чтобы перенаправить сокет на localhost:2195. Сейчас я пытаюсь написать сервер сокетов Java для получения запроса на уведомление.

Тем не менее, я получаю исключение во время рукопожатия и не могу найти, как это исправить.

Примечание: я использую один и тот же сертификат с обеих сторон, это стандартный файл.p12, который работает для отправки push-уведомлений в APN.

Вот клиент (упрощенно):

KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(new FileInputStream(certificatePath), password.toCharArray());

KeyManagerFactory kmf = KeyManagerFactory.getInstance("sunx509");
kmf.init(ks, password.toCharArray());

TrustManagerFactory tmf = TrustManagerFactory.getInstance("sunx509"); 
tmf.init((KeyStore)null);

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

SSLSocketFactory ssf = sc.getSocketFactory(); 
SSLSocket socket = (SSLSocket) ssf.createSocket(InetAddress.getLocalHost(), 2195);
socket.startHandshake();

Вот сервер:

KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(new FileInputStream(certificatePath), password.toCharArray());

KeyManagerFactory kmf = KeyManagerFactory.getInstance("sunx509");
kmf.init(ks, password.toCharArray());

SSLContext context = SSLContext.getInstance("TLS");
context.init(kmf.getKeyManagers(), null, null);

SSLServerSocketFactory ssf = context.getServerSocketFactory();
serverSocket = (SSLServerSocket) ssf.createServerSocket(2195);

И вот исключение:

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: No trusted certificate found

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

Какому сертификату нужен сервер, чтобы клиент доверял ему?

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

1 ответ

Решение

РЕДАКТИРОВАТЬ: Я был не прав! tmf.init(null) использует хранилище ключей по умолчанию, как sslctx.init(,null,)! Этим значением по умолчанию обычно является файл cacerts в JRE/lib/security, который ДОЛЖЕН доверять многим установленным ЦС, поэтому теперь я думаю, что мы можем быть уверены, что реальный сервер использует сертификат под установленным ЦС (и поэтому доверяет ваш клиент), в то время как сертификат у тебя p12 видимо нет; но здесь есть две возможности:

  • оно подписано или выпущено неизвестным, неизвестным или недоказанным CA

  • он выдается "настоящим" ЦС в рамках "промежуточного" ЦС, для которого требуется цепной сертификат (или несколько), а у вас нет сертификата (ов) цепи в вашем p12. Обратите внимание, что это все еще может работать для аутентификации клиента на реальном сервере, потому что реальный сервер может легко иметь "предварительно загруженные" цепочечные сертификаты в своем хранилище доверенных сертификатов, даже если они отсутствуют в Java.

Чтобы отличить их, посмотрите на keytool -keystore file -storetype pkcs12 -list -vи посмотрите, какой сертификат или последовательность сертификатов у вас есть.

Тогда может быть несколько подходов к решению:

  1. если вам не хватает цепных сертификатов для установленного ЦС, получите их и добавьте. keytool позволяет заменять только всю цепочку, поэтому вы должны получить все необходимые сертификаты; openssl (если он у вас есть или получен) может извлекать ключ и сертификаты из pkcs12, заменять или добавлять отдельные сертификаты и объединять их вместе.

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

  3. найдите доверительный якорь (из p12 или откуда-то еще, например, из CA) и поместите его в хранилище доверенных сертификатов, который явно загружает клиент. Вы эффективно попробовали это, используя p12 в качестве хранилища доверенных сертификатов и сказали, что не хотите этого.

  4. поместите привязку доверия в хранилище доверенных сертификатов по умолчанию, чтобы клиент продолжал использовать значение по умолчанию. Если вы не возражаете против изменения JRE (и ни один пользователь или приложение в вашей системе не беспокоится), просто добавьте в JRE/lib/security/cacerts. Или, предполагая, что вы можете установить системные свойства, поместить привязку в хранилище или просто оставить ее в p12 и установить javax.net.ssl.trustStore{,Password,Type}, чтобы указать на это хранилище. (Если вы копируете, вы должны взять только сертификат; p12 - это ключ KEY, а не просто сертификат. Не просто -importkeystore; -importcert файл сертификата, созданный с помощью -exportcert, если это необходимо.) (Вы можете использовать System.setProperty. в вашем коде, но это меняет ваш код. Если вы запускаете из командной строки, вы можете использовать "java -Dname=value...". В других случаях YMMV.)

Существует одна возможная проблема типа: если сертификат был выдан с расширением ExtendedKeyUsage, и это значение указывает только TLSclient, а не TLSserver (что может сделать CA), то использование его для сервера, вероятно, не будет работать - это выглядит как JSSE обеспечивает соблюдение ограничений EKU. Но если это проблема, вы получите совсем другое исключение. И вы можете увидеть это также в keytool -list -v выше.

Поскольку вы (справедливо) хотите использовать этот p12 для своего клиента, логике вашего сервера также необходимо доверять ему. (Использование его для исходящей аутентификации автоматически НЕ делает его доверенным для входящей аутентификации.) Но только если / когда clientAuth фактически выполнен, что не является значением по умолчанию; ваш сервер кодирует.setNeedClientAuth(true) на SSLServerSocket перед тем, как принять соединение? Возможные подходы эквивалентны вышеперечисленному, за исключением того, что пропуск #2 неприменим. Если и клиент, и сервер используют один и тот же JRE, это немного облегчает задачу.

Наконец, да, TrustManager PKIX новее и, как правило, более функционально, чем SunX509. Но для базового теста "якорь доверия в нашем хранилище доверенных сертификатов" они эквивалентны.

Извините снова за ввод в заблуждение.

Другие вопросы по тегам