Подтвердить что ты не робот

Если в хранилище ключей Jetty имеется более одного сертификата, как он выбирается?

В нашей системе есть некоторый код для автоматического создания самозаверяющих сертификатов в хранилище ключей, которое затем используется Jetty. Если ключ для данного хоста уже существует, ничего не происходит, но если он не существует, мы генерируем новый ключ, например:

public void generateKey(String commonName) {
    X500Name x500Name = new X500Name("CN=" + commonName);
    CertAndKeyGen keyPair = new CertAndKeyGen("DSA", "SHA1withDSA");
    keyPair.generate(1024);
    PrivateKey privateKey = keyPair.getPrivateKey();
    X509Certificate certificate = keyPair.getSelfCertificate(x500Name, 20*365*24*60*60);
    Certificate[] chain = { certificate };
    keyStore.setEntry(commonName, privateKey, "secret".toCharArray(), chain);
}

Все это прекрасно работает, пока в хранилище ключей есть только один ключ и сертификат. Когда у вас есть несколько ключей, при попытке подключения возникают странные вещи:

java.io.IOException: HTTPS hostname wrong:  should be <127.0.0.1>

Это была довольно загадочная ошибка, но я, наконец, смог ее отследить, написав unit test, который подключается к серверу, и утверждает, что CN в сертификате совпадает с именем хоста. То, что я нашел, было довольно интересно - Jetty, по-видимому, произвольно выбирает, какой сертификат предоставлять клиенту, но последовательно.

Например:

  • Если "CN = localhost" и "CN = cheese.mydomain" находятся в хранилище ключей, он всегда выбирает "CN = cheese.mydomain" .
  • Если "CN = 127.0.0.1" и "CN = cheese.mydomain" находятся в хранилище ключей, он всегда выбирает "CN = cheese.mydomain" .
  • Если в хранилище ключей "CN = 192.168.222.100" (cheese.mydomain) и "CN = cheese.mydomain" , он всегда выбирает "CN = 192.168.222.100".

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

Так точно, какие критерии он использует? Первоначально я думал, что localhost был особенным, но тогда третий пример полностью озадачил меня.

Я полагаю, что это почему-то было решено KeyManagerFactory, которое является SunX509 в моем случае.

4b9b3361

Ответ 1

В конечном итоге это определяется KeyManager (обычно получается из KeyManagerFactory).

Хранилище ключей может иметь несколько сертификатов, хранящихся под разными псевдонимами. Если псевдоним явно не настроен с помощью certAlias в конфигурации Jetty, реализация SunX509 будет выбирать первые псевдонимы, которые он находит, это закрытый ключ и ключ нужного типа для выбранного набора шифров (обычно RSA, но, вероятно, DSA в вашем случае здесь). Там немного больше к логике выбора, если вы посмотрите на внедрение поставщика Sun, но вы не должны полагаться на заказ в общем, просто имя псевдонима.

Вы можете, конечно, дать Jetty свой собственный SSLContext своим собственным X509KeyManager, чтобы выбрать псевдоним. Вам нужно будет выполнить:

 chooseServerAlias(String keyType, Principal[] issuers, Socket socket)

К сожалению, кроме keyType и issuers, все, что вы можете сделать, это сам socket. В лучшем случае полезной информацией, которую вы получаете, являются локальный IP-адрес и удаленный.

Если ваш сервер не прослушивает несколько IP-адресов на одном и том же порту, вы всегда будете получать один и тот же локальный IP-адрес. (Здесь, очевидно, у вас есть как минимум два: 127.0.0.1 и 192.168.222.100, но я подозреваю, что вас не интересует локальный хост, кроме ваших собственных тестов.) Вам понадобится поддержка имени сервера (SNI) на сервере чтобы иметь возможность принимать решения на основе запрошенных имен хостов (клиентами, которые его поддерживают). К сожалению, SNI был введен только в Java 7, но только на стороне клиента.

Еще одна проблема, с которой вы столкнетесь здесь, заключается в том, что клиенты Java будут жаловаться на IP-адреса в теме DN CN. Некоторые браузеры будут терпеть это, но это не соответствует спецификации HTTPS (RFC 2818). IP-адреса должны быть в качестве альтернативных имен объекта типа IP-адреса.