Java https request ssl

Java: HTTPS requests with certificates using HttpClient (two-way authentication)

The problem is that the server requires a certificate (two-way authentication). I was provided with two files: client.cert.pem and client.key.pem , from which I obtained keystore.jks and keystore.p12 using keytool and openssl.

I use curl I am successfully getting data from the server

curl --cert client.cert.pem --key client.key.pem . 

But in the code I can’t pass the certificate correctly.

By studying many examples on the Internet I came up with this code:

final char[] JKS_PASSWORD = "password".toCharArray(); SSLContext sslContext = SSLContexts.custom().loadTrustMaterial( new File("ssl/keystore.jks"), JKS_PASSWORD, new TrustSelfSignedStrategy()).build(); HttpClient httpsClient = HttpClients.custom().setSSLContext(sslContext).build(); HttpResponse rResponse = httpsClient.execute(new HttpGet(MY_URL)); 

In this case y I get an exception:

java.lang.NoSuchMethodError: org.apache.http.impl.client.HttpClientBuilder.setSSLContext(Ljavax/net/ssl/SSLContext;)Lorg/apache/http/impl/client/HttpClientBuilder; 

If you use SSLConnectionSocketFactory :

. SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory( sslContext, new String[] < "TLSv1", "TLSv1.1", "TLSv1.2" >, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier()); HttpClient httpsClient = HttpClients.custom().setSSLSocketFactory(sslSocketFactory).build(); HttpResponse rResponse = httpsClient.execute(new HttpGet(MY_URL)); 
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target 

I obviously messed up something, but I can’t figure out what. Any help is welcome!

Источник

От HTTP до HTTPS

Java-университет

В современном мире без веб-приложений никак. И начнём мы с небольшого эксперимента. В детстве я помню, как во всех ларьках продавалась такая газета, как «Аргументы и факты». Вспомнил я о них потому, что по моему личному восприятию из детства, эти газеты выглядели всегда странно. И решил, а не зайти ли нам на их сайт:

От HTTP до HTTPS - 2

Если перейти в справку Google Chrome, то мы прочитаем, что данный сайт не использует защищённое соединение и информация, которой вы обмениваетесь с сайтом, может быть доступна посторонним. Давайте проверим какие-нибудь другие новости, например новости Санкт-Петербурга от «Фонтанки», электронного СМИ:

От HTTP до HTTPS - 3

Как видно, у сайта Фонтанки с безопасностью по этим данным проблем нет. Получается, что веб-ресурсы могут быть безопасными, а могут и не быть. Так же видим, что обращение к не защищённым ресурсам происходит по протоколу HTTP. А если ресурс защищён, то обмен данными осуществляется по протоколу HTTPS, где S на конце обозначает «Secure». Протокол HTTPS описан в спецификации rfc2818: «HTTP Over TLS». Давайте попробуем создать своё веб-приложение и сами увидеть, как это работает. И попутно будем разбираясь в терминах.

От HTTP до HTTPS - 4

Веб-приложение на Java

Итак, нам нужно создать самое простое веб-приложение на Java. Для начала, нам нужно само приложение на Java. Для этого воспользуемся системой автоматической сборки проекта Gradle. Это нам позволит не создавать вручную нужную структуру каталогов + Gradle за нас будет управлять всеми необходимыми для проекта библиотеками и обеспечивать, чтобы они были доступны при выполнении кода. Подробнее про Gradle можно прочитать в небольшом обзоре: «Краткое знакомство с Gradle». Воспользуемся Gradle Init Plugin’ом и выполним команду:

 gradle init --type java-application 

После этого откроем билд скрипт build.gradle , в котором описано, из каких библиотек состоит наш проект, которые Gradle нам предоставит. Добавим туда зависимость от веб-сервера, на котором мы будем экспериментировать:

Читайте также:  Python redirect stdout to file

Чтобы веб-приложение работало, нам обязательно нужен веб-сервер, где будет размещено наше приложение. Веб серверов существует огромное множество, но основные это: Tomcat, Jetty, Undertow. Мы с Вами выберем на этот раз Undertow. Чтобы понять, как нам работать с этим нашим веб-сервером перейдём на официальный сайт Undertow и перейдём в раздел документации. Мы с Вами подключили зависимость от Undertow Core, поэтому нам интересует раздел про этот самый Core, то есть ядро, основу веб-сервера. Самым простым способом является использование Builder API для Undertow:

 public static void main(String[] args) < Undertow server = Undertow.builder() .addHttpListener(8080, "localhost") .setHandler(new HttpHandler() < @Override public void handleRequest(final HttpServerExchange exchange) throws Exception < exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain"); exchange.getResponseSender().send("Hello World"); >>).build(); server.start(); > 

От HTTP до HTTPS - 5

Работает это просто. Благодаря Undertow Builder API мы добавляем HTTP слушателя на адрес localhost и порт 8080. Этот слушатель получает запросы от веб-браузера и возвращает в ответ строку «Hello World». Отличное веб-приложение. Но как мы видим, мы используем протокол HTTP, т.е. такой обмен данными небезопасен. Давайте разбираться, как же осуществляют обмен по протоколу HTTPS.

От HTTP до HTTPS - 6

Требования для HTTPS

От HTTP до HTTPS - 7

Но чтобы его добавить нам нужен SSLContext. Интересно, что SSLContext — это класс не из Undertow, а javax.net.ssl.SSLContext . Класс SSLContext входит в так называемый «Java Secure Socket Extension» (JSSE) — расширение Java для обеспечения безопасности интернет соединения. Данное расширение описано в документе «Java Secure Socket Extension (JSSE) Reference Guide». Как видно из вступительной части документации, JSSE предоставляет фрэймворк и Java реализацию протоколов SSL и TLS. Как же нам получить SSLContext? Открываем JavaDoc SSLContext и находим метод getInstance. Как видно, для получения SSLContext нам нужно указать название «Secure Socket Protocol». В описании параметров дано указание, что эти названия можно посмотреть в «Java Cryptography Architecture Standard Algorithm Name Documentation». Поэтому, последуем указанию и идём в документацию. И видим, что мы можем выбрать между SSL и TLS:

От HTTP до HTTPS - 8

 public SSLContext getSSLContext() < // 1. Получаем контекст, в рамках которого будем работать по TLS протоколу SSLContext context = null; try < context = SSLContext.getInstance("TLS"); >catch (NoSuchAlgorithmException e) < throw new IllegalStateException(e); >return context; > 

Создав новый контекст вспоминаем, что SSLContext описывался в «Java Secure Socket Extension (JSSE) Reference Guide». Читаем и видим, что «A newly created SSLContext should be initialized by calling the init method». То есть создать контекст — мало. Его нужно инициализировать. И это логично, т.к. про безопасность мы рассказали только то, что мы хотим использовать протокол TLS. Чтобы инициализировать SSLContext нам нужно предоставить три вещи: KeyManager, TrustManager, SecureRandom.

Читайте также:  Как получить длину массива java

От HTTP до HTTPS - 9

KeyManager

KeyManager — это менеджер ключей. Он отвечает за то, какой «authentication credential» предоставить тому, кто к нам обратиться. Credential можно перевести, как удостоверение. Удостоверение нужно, чтобы клиент был уверен что сервер тот, за кого себя выдаёт и ему можно доверять. Что будет использовано в качестве удостоверения? Как мы помним, Server Identity проверяется по цифровому сертификату сервера. Этот процесс можно представить следующим образом:

От HTTP до HTTPS - 10

  • alias: Псевдоним или просто имя, под которым будет сохранена запись в Keystore keyalg: Алгоритм шифрования ключей. Выберем алгоритм RSA, который является по сути стандартным решением для нашей цели.
  • keysize: Размер ключа (в битах). Минимальный рекомендуемый размер 2048, т.к. размер меньше уже взламывался. Подробнее можно прочитать здесь: «a ssl certificate in 2048 bit».
  • dname: Distinguished Name, отличительное имя.
  • validity: Продолжительность в днях, в течении которых генерируемый сертификат валиден, т.е. действителен.
  • ext: Certificate Extension, указанные в «Named Extensions».
  • -ext san:critical=dns:localhost,ip:127.0.0.1 > для выполнения subject matching по SubjectAlternativeName
  • -ext bc=ca:false > чтобы указать, что данный сертификат не используется для подписи других сертификатов
 keytool -genkeypair -alias ssl -keyalg RSA -keysize 2048 -dname "CN=localhost,OU=IT,O=Javarush,L=SaintPetersburg,C=RU,email=contact@email.com" -validity 90 -keystore C:/keystore.jks -storepass passw0rd -keypass passw0rd -ext san:critical=dns:localhost,ip:127.0.0.1 -ext bc=ca:false 

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

От HTTP до HTTPS - 11

Тут нам говорят, что JKS — проприетарный формат. Проприетарный — значит является частной собственностью авторов и предназначен для использования только в Java. При работе со сторонними утилитами может возникнуть конфликт, поэтому нас и предупреждают. Кроме того, мы можем получить ошибку: The destination pkcs12 keystore has different storepass and keypass . Эта ошибка возникает из-за того, что используются разные пароли от записи в Keystore и от самого keystore. Как сказано в документации к keytool, «For example, most third-party tools require storepass and keypass in a PKCS #12 keystore to be the same». Мы можем указать сами ключ (например, -destkeypass entrypassw). Но лучше не нарушать требований и задать одинаковый пароль. Итак, импорт может выглядеть следующим образом:

 keytool -importkeystore -srckeystore C:/keystore.jks -destkeystore C:/keystore.jks -deststoretype pkcs12 

От HTTP до HTTPS - 12

 keytool -export -alias ssl -storepass passw0rd -file C:/server.cer -keystore C:/keystore.jks 
 keytool -list -v -keystore C:/keystore.jks -storepass passw0rd 
 public KeyStore getKeyStore() < // Согласно https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyStore try(FileInputStream fis = new FileInputStream("C:/keystore.jks"))< KeyStore keyStore = KeyStore.getInstance("pkcs12"); keyStore.load(fis, "passw0rd".toCharArray()); return keyStore; >catch (IOException ioe) < throw new IllegalStateException(ioe); >catch (KeyStoreException | NoSuchAlgorithmException | CertificateException e) < throw new IllegalStateException(e); >> 
 public KeyManager[] getKeyManagers(KeyStore keyStore) < String keyManagerAlgo = KeyManagerFactory.getDefaultAlgorithm(); KeyManagerFactory keyManagerFactory = null; try < keyManagerFactory = KeyManagerFactory.getInstance(keyManagerAlgo); keyManagerFactory.init(keyStore, "passw0rd".toCharArray()); return keyManagerFactory.getKeyManagers(); >catch (NoSuchAlgorithmException e) < throw new IllegalStateException(e); >catch (UnrecoverableKeyException | KeyStoreException e) < throw new IllegalStateException(e); >> 

Наша первая цель достигнута. Осталось разобраться, что такое TrustManager. TrustManager описан в документации JSSE в разделе «The TrustManager Interface». Он очень похож на KeyManager, но его цель проверить, можно ли доверять тому, кто запрашивает соединение. Если совсем грубо, то это KeyManager наоборот =) У нас нет необходимости в TrustManager’е, поэтому передадим null. Тогда будет создан TrustManager по умолчанию, не проверяющий конечного пользователя, который выполняет запросы на наш сервер. В документации так и сказано: «default implementation will be used». Аналогично с SecureRandom. Если мы укажем null, то будет использована реализация по умолчанию. Вспомним только, что SecureRandom — это класс, относящийся к JCA и описанный в документации JCA в разделе «The SecureRandom Class». Итого, подготовка с учётом всех вышеописанных методов может выглядеть следующим образом:

 public static void main(String[] args) < // 1. Подготавливаем приложение к работе по HTTPS App app = new App(); SSLContext sslContext = app.getSSLContext(); KeyStore keyStore = app.getKeyStore(); KeyManager[] keyManagers = app.getKeyManagers(keyStore); try < sslContext.init(keyManagers, null, null); >catch (KeyManagementException e)
 // 2. Поднимаем сервер int httpsPort = 443; Undertow server = Undertow.builder() .addHttpsListener(httpsPort, "localhost", sslContext) .setHandler(new HttpHandler() < @Override public void handleRequest(final HttpServerExchange exchange) throws Exception < exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain"); exchange.getResponseSender().send("Hello World"); >>).build(); server.start(); > 

На этот раз наш сервер будет доступен по адресу https://localhost:443 Однако, мы по прежнему получим ошибку, что нельзя доверять данному ресурсу:

Читайте также:  Css прозрачный фон при наведении

От HTTP до HTTPS - 13

От HTTP до HTTPS - 14

Управление сертификатами

От HTTP до HTTPS - 15

Причина в том, что данный сертификат является самоподписанным (Self-signed Certificate). Под самоподписанным SSL сертификатом понимают сертификат открытого ключа, изданный и подписанный тем же лицом, которое он идентифицирует. То есть его не выдавал никакой уважаемый центр сертификации (CA, он же Certificate Authority). Центр сертификации (Certificate Authority) выступает как доверенное лицо и похож на нотариуса в обычной жизни. Он заверяет, что выданные им сертификаты надёжны. Услуга выдачи сертификатов такими CA является платной, поэтому утеря доверия и репутационные риски никому не нужны. По умолчанию есть несколько центров сертификации, которым доверяют. Этот список доступен для редактирования. И управление списком центров сертификации в каждой операционной системе свой. Например, управление данным списком в Windows можно прочитать здесь: «Manage Trusted Root Certificates in Windows». Давайте добавим сертификат в доверенные, как указано в сообщении об ошибке. Для этого, сначала, скачаем сертификат:

От HTTP до HTTPS - 16

В OS Windows нажмём Win+R и выполним mmc для вызова консоли управления. Далее нажмём Ctrl+M для добавления раздела «Сертификаты» в текущую консоль. Далее в подразделе «Доверенные корневые центры сертификации» выполним Действия / Все задачи / Импорт . Выполним импорт файла, скачанного ранее в файл. Браузер мог запомнить прошлое состояние доверия к сертификату. Поэтому, перед открытием страницы нужно выполнить рестарт браузера. Например, в Google Chrome в адресной строке необходимо выполнить chrome://restart . В OS Windows для просмотра сертификатов так же можно воспользоваться утилитой certmgr.msc :

Источник

Оцените статью