Хранилище ключей Java не найдено при переопределении контекста SSL по умолчанию

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

Казалось, что самый естественный способ сделать это — подставить контекст SSL по умолчанию при запуске приложения.

Вот минимальный рабочий пример:

public class ClientAuthentication {
    public static final String SERVER_URL = "https://my.test.server";

    public static void main(String[] args) throws Exception {
        SSLContext defaultContext = SSLContext.getInstance("TLS");
        defaultContext.init(null, new TrustManager[]{new MyX509TrustManager()}, null);
        // defaultContext.init(null, null, null);
        SSLContext.setDefault(defaultContext);
        HttpURLConnection connection = (HttpURLConnection) new URL(SERVER_URL).openConnection();
        System.out.println(connection.getResponseCode());
    }

    private static class MyX509TrustManager implements X509TrustManager {
        @Override
        public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            throw new UnsupportedOperationException("Won't be called by client");
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String s) throws CertificateException {
            if (userAcceptsCertificate(chain)) {
                System.out.format("Certificate '%s' accepted%n", chain[0].getIssuerX500Principal().getName());
            }
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }

        private boolean userAcceptsCertificate(X509Certificate[] x509Certificates) {
            // ...
            return true;
        }
    }
}

К сожалению, это не работает, когда сервер требует аутентификации клиента. Даже если хранилище ключей, содержащее клиентский сертификат, правильно указано с помощью параметра виртуальной машины -Djavax.net.ssl.keyStore, соединение завершается ошибкой с javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure, поскольку клиентский сертификат вообще не был отправлен (отслеживается в Wireshark).

Такое поведение немного удивительно, потому что JavaDoc для SSLContext#init(KeyManager[], TrustManager[], SecureRandom) указывает, что передача null вместо менеджеров ключей или менеджеров доверия означает, что будет выполнена процедура поиска по умолчанию.

И наоборот, если я передам null вместо массива с моим собственным менеджером доверия в качестве второго аргумента для init и укажу хранилище доверия JKS, содержащее сертификат сервера, используя
-Djavax.net.ssl.trustStore параметры виртуальной машины, сертификат сервера будет найден там и успешно принят< /эм>.

Итак, можете ли вы посоветовать способ переопределить контекст SSL по умолчанию, заменив только менеджера доверия и оставив поведение по умолчанию в отношении поиска менеджеров ключей? Или может быть какой-то надежный способ найти диспетчер ключей, указанный в параметрах виртуальной машины.

Заметки

  1. Если я вообще не настраиваю контекст SSL по умолчанию, соединение установлено успешно, т. е. все пути, пароли и типы хранилищ верны в параметрах виртуальной машины.

  2. Я никак не могу вызвать SSLContext.getDefault() или SSLContext.getInstance("Default"), потому что контекст по умолчанию может быть инициализирован только один раз.


person east825    schedule 21.04.2014    source источник


Ответы (1)


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

Javadoc неверен. IBM JSSE ведет себя так, как описано там, а Sun/Oracle JSSE — нет и никогда не было.

person user207421    schedule 21.04.2014