JDK11 HttpClient взаимный tls

Я хочу использовать новый HttpClient, представленный в Java 11. Непонятно, как сделать взаимный TLS (двухсторонняя аутентификация, когда и клиент, и сервер представляют сертификат.)

Может ли кто-нибудь привести пример взаимного TLS с HttpClient?


person fafrd    schedule 21.07.2020    source источник
comment
Вам нужно будет создать SSLContext с вашими сертификатами и настроить SSLParameters, например param = new SSLParameters(); param.setNeedClientAuth​(true); HttpClient.newBuilder(). sslContext​(контекст).sslParameters(параметр)   -  person JEY    schedule 21.07.2020
comment
@JEY Это хорошее начало, но мне также нужно установить сертификат + ключ. Непонятно, как это сделать с помощью SSLParametrs или SSLContext.   -  person fafrd    schedule 22.07.2020


Ответы (1)


Догадаться. Создайте HttpClient, затем передайте объекты SSLContext и SSLParameters.

Загрузите сертификат/ключ в SSLContext:

 // cert+key data. assuming X509 pem format
final byte[] publicData = your_cert_data; // -----BEGIN CERTIFICATE----- ...
final byte[] privateData = your_key_data; // -----BEGIN PRIVATE KEY----- ...

// parse certificate
final CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
final Collection<? extends Certificate> chain = certificateFactory.generateCertificates(
        new ByteArrayInputStream(publicData));

LOG.info("Successfully loaded the client cert certificate chain {}", String.join(" -> ", chain
        .stream()
        .map(certificate -> {
            if (certificate instanceof X509Certificate) {
                final X509Certificate x509Cert = (X509Certificate) certificate;
                return x509Cert.getSubjectDN().toString();
            } else {
                return certificate.getType();
            }
        }).collect(Collectors.toList())));

// parse key
final Key key = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(privateData));

// place cert+key into KeyStore
KeyStore clientKeyStore = KeyStore.getInstance("jks");
final char[] pwdChars = KEYSTORE_PASSWORD.toCharArray(); // use a random string, like from java.security.SecureRandom
clientKeyStore.load(null, null);
clientKeyStore.setKeyEntry(YOUR_SERVICE_NAME, key, pwdChars, chain.toArray(new Certificate[0]));

// initialize KeyManagerFactory
KeyManagerFactory keyMgrFactory = KeyManagerFactory.getInstance("SunX509");
keyMgrFactory.init(clientKeyStore, pwdChars);

// populate SSLContext with key manager
SSLContext sslCtx = SSLContext.getInstance("TLSv1.2");
sslCtx.init(keyMgrFactory.getKeyManagers(), null, null);

Создайте параметры ssl, установите для needClientAuth значение true:

SSLParameters sslParam = new SSLParameters();
sslParam.setNeedClientAuth(true);

наконец, создайте HttpClient:

HttpClient client = HttpClient.newBuilder()
    .sslContext(sslCtx)
    .sslParameters(sslParam)
    .build();
person fafrd    schedule 24.07.2020
comment
сумасшедший! Я должен был сделать это где-то 1/2 дня назад... sslCtx.init(keyMgrFactory.getKeyManagers(), null, null); - вы передаете менеджер нулевого доверия. это действительно то, что вы хотите? - person Eugene; 24.07.2020
comment
@Eugene: SSLContext.init(,trustmgr=null,) использует диспетчер доверия по умолчанию, который, в свою очередь, использует хранилище доверенных сертификатов по умолчанию, которое, если не переопределено, представляет собой файл JRE/lib/security/cacerts, предварительно заполненный «обычные подозреваемые», такие как Digicert GoDaddy LetsEncrypt. Так что обычно да, вы этого хотите. - person dave_thompson_085; 25.07.2020
comment
@ dave_thompson_085 Я так рад, что вы придумали этот комментарий, я видел ваши ответы и комментарии на эту тему. Причина, по которой я спросил, заключалась в том, что мне нужно было создать и очистить TrustManager (в отличие от null/default), иначе я всегда получал бы ошибку рукопожатия. Мои знания в этой области каким-то образом ограничены, поэтому мне было интересно, не могли бы вы пролить свет... Я получил закрытый ключ и сертификат клиента в pem-файле, и единственный способ подключения был через пустой менеджер доверия вместо ноль, который в лучшем случае выглядит так, будто я сделал что-то подозрительное. - person Eugene; 25.07.2020
comment
@Eugene: если вы имеете в виду пустые якоря без доверия, это будет работать только с «анонимными» наборами, которые очень редки (и исчезли в TLS1.3). Если вы имеете в виду часто публикуемый код-заглушку «ничего не проверять/принимать все», то это небезопасно, за исключением среды, где активные атаки полностью предотвращаются другими методами, которые вполне могут считаться ловушкой. В любом случае, это (1) совершенно другая проблема, чем этот вопрос и ответ, и комментарии стека, не связанные с вопросами и ответами, могут быть и часто удаляются и (2) в любом случае слишком сложны для обработки в комментариях. Вы можете задать вопрос. - person dave_thompson_085; 28.07.2020
comment
Количество кода, необходимого для выполнения такого базового запроса, ошеломляет. Я думаю, что должен быть фабричный метод, который позволил бы просто предоставить два файла (один с сертификатом и один с закрытым ключом). - person Krzysztof Krasoń; 03.10.2020
comment
Строка final Key key = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(privateData)); не работает для закодированного ключа X.509, который предполагается в первых строках этого фрагмента. - person Krzysztof Krasoń; 03.10.2020