Настройка шифрования для вашего приложения, насколько сложно это может быть? Я думал, что это должно быть легко, поскольку вся связь с современными веб-приложениями должна быть зашифрована, верно? Ну, мои ожидания не оправдались … При настройке я столкнулся с парой скрытых трудностей. Например, конфигурация расплывчата, многословна, не проста в настройке, сложна в отладке и не удобна для юнит-тестирования.
В этой статье я предполагаю, что у вас уже есть базовые знания о сертификатах, хранилищах ключей, протоколах шифрования и ssl-handshake. Если нет, я бы порекомендовал пройти через эту статью: Как легко настроить взаимный TLS .
Он будет проходить по следующим темам:
- Нет безопасности.
- Односторонняя аутентификация.
- Двусторонняя аутентификация.
- Двусторонняя аутентификация с доверием центра сертификации.
В нем также объясняется, как создавать хранилища ключей, сертификаты, запросы на подпись сертификатов и как их реализовывать.
Давайте продолжим к следующей части. Я хочу привести несколько примеров, объясняющих скрытые трудности при настройке безопасного соединения с https и сертификатами в простой Java. Для этого примера я буду использовать Apache HttpClient. Если мы хотим использовать клиент без шифрования, следующая настройка выполнит работу:
Джава
xxxxxxxxxx
1
импорт орг . апач . http . HttpResponse ;
2
импорт орг . апач . http . клиент . HttpClient ;
3
импорт орг . апач . http . клиент . методы . HttpGet ;
4
импорт орг . апач . http . импл . клиент . HttpClients ;
5
6
импорт Java . -й . IOException ;
7
8
class App {
9
10
public static void main ( String [] args ) выдает IOException {
11
HttpClient httpClient = HttpClients . createMinimal ();
12
13
HttpGet request = new HttpGet ( "http: // localhost: 8080 / api / hello" );
14
HttpResponse response = httpClient . выполнить ( запрос );
15
}
16
17
}
Для Apache HttpClient требуется настроенный SSLContext
экземпляр для включения безопасного соединения https. Самая минимальная установка выглядела бы как пример ниже:
Джава
xxxxxxxxxx
1
импорт орг . апач . http . HttpResponse ;
2
импорт орг . апач . http . клиент . HttpClient ;
3
импорт орг . апач . http . клиент . методы . HttpGet ;
4
импорт орг . апач . http . подкл . SSL . DefaultHostnameVerifier ;
5
импорт орг . апач . http . импл . клиент . HttpClients ;
6
7
импорт javax . нетто . SSL . SSLContext ;
8
импорт javax . нетто . SSL . TrustManagerFactory ;
9
импорт Java . -й . IOException ;
10
импорт Java . безопасность . KeyManagementException ;
11
импорт Java . безопасность . KeyStore ;
12
импорт Java . безопасность . KeyStoreException ;
13
импорт Java . безопасность . NoSuchAlgorithmException ;
14
15
class App {
16
17
общественный статический недействительный основной ( Строка [] арг ) бросает IOException , NoSuchAlgorithmException , KeyStoreException , KeyManagementException {
18
// в реальном проекте этот экземпляр хранилища ключей скорее всего будет создан из файла хранилища ключей Java
19
// который содержит список доверенных сертификатов
20
KeyStore trustStore = KeyStore . getInstance ( KeyStore . getDefaultType ());
21
22
TrustManagerFactory trustManagerFactory = TrustManagerFactory . getInstance ( TrustManagerFactory . getDefaultAlgorithm ());
23
trustManagerFactory . init ( trustStore );
24
25
SSLContext sslContext = SSLContext . getInstance ( "TLSv1.3" );
26
sslContext . init ( null , trustManagerFactory . getTrustManagers (), null );
27
28
HttpClient httpClient = HttpClients . обычай ()
29
, setSSLContext ( sslContext )
30
, setSSLHostnameVerifier ( новый DefaultHostnameVerifier ())
31
, build ();
32
33
HttpGet request = new HttpGet ( "https: // localhost: 8443 / api / hello" );
34
HttpResponse response = httpClient . выполнить ( запрос );
35
}
36
37
}
Вышеуказанная настройка выглядит легко, но она вносит много дополнительной логики в вашу кодовую базу. Для тех, кто не знаком с библиотекой безопасности Java, может потребоваться много времени, чтобы понять следующее:
- Каков наилучший способ создать хранилище ключей?
- Каков тип хранилища ключей по умолчанию и какие другие опции я могу выбрать?
- Зачем мне нужен TrustManagerFactory? Разве я не могу просто поместить свой экземпляр хранилища ключей с доверенными сертификатами прямо в
SSLContext
? - Каков алгоритм по умолчанию для TrustManagerFactory?
- Достаточно ли хорош алгоритм по умолчанию, или мне нужен другой, и какие еще варианты я могу выбрать?
- Какой протокол шифрования я должен выбрать для создания
SSLContext
? - Почему я могу предоставить значение null в качестве значения параметра при инициализации
SSLContext
, и что получится в результатеSSLContext
для меня?
Ну, это много вопросов, на которые нужно ответить только для базовой настройки. Я не буду давать ответы на вышеуказанные вопросы, так как наше сообщество уже ответило на них. Как разработчик, вы, вероятно, захотите предоставить лучшую конфигурацию для ваших проектов и с наименьшими затратами усилий, что также должно поддерживаться.
Приведенная выше конфигурация станет еще сложнее, если вам также потребуется настроить клиент для взаимодействия по взаимной TLS, также известной как взаимная аутентификация. Также сложно провести модульное тестирование SSLContext
объекта, поскольку вы не можете получить от него никакой информации, которая скажет trustmanager
, действительно ли объект инициализирован правильно и содержит ли он все доверенные сертификаты. Единственный способ действительно проверить ваш код — это написать интеграционный тест, где клиент фактически отправляет запрос на реальный сервер с включенным HTTPS.
Я столкнулся с теми же проблемами, что и мои коллеги. Им также не нравилось настраивать это. Это была просто конфигурация, которую нужно было настроить достаточно хорошо, чтобы выполнить работу, и после этого мы боялись коснуться ее снова.
Некоторые другие HTTP-клиенты даже требуют другой настройки (например, Netty HttpClient, AsyncHttpClient и Dispatch Reboot). Эти клиенты принимают только SSLContext
из библиотеки Netty вместо библиотеки JDK.
Я хотел выручить себя от этой трудной задачи и сделать мою жизнь легче. Я подумала, почему у нас не должно быть чего-то похожего на Властелин Колец — одно кольцо, чтобы править ими всеми. Таким образом, sslcontext-kickstart родился. Одна библиотека для настройки и управления ими всеми! Он должен быть безболезненным, легким в тестировании и отладке и простым в настройке.
Приведенный выше пример можно заменить следующим фрагментом кода:
Джава
xxxxxxxxxx
1
импорт NL . альтиндаг . sslcontext . SSLFactory ;
2
импорт орг . апач . http . HttpResponse ;
3
импорт орг . апач . http . клиент . HttpClient ;
4
импорт орг . апач . http . клиент . методы . HttpGet ;
5
импорт орг . апач . http . импл . клиент . HttpClients ;
6
7
импорт Java . -й . IOException ;
8
импорт Java . безопасность . KeyStore ;
9
импорт Java . безопасность . KeyStoreException ;
10
11
class App {
12
13
public static void main ( String [] args ) выдает IOException , KeyStoreException {
14
String trustStore = "path / to / trustStore.jks" ;
15
16
SSLFactory sslFactory = SSLFactory . строитель ()
17
, withTrustStore ( trustStore , "password" . toCharArray ())
18
, build ();
19
20
HttpClient httpClient = HttpClients . обычай ()
21
, setSSLContext ( sslFactory . getSslContext ())
22
, setSSLHostnameVerifier ( sslFactory . getHostnameVerifier ())
23
, build ();
24
25
HttpGet request = new HttpGet ( "https: // localhost: 8443 / api / hello" );
26
HttpResponse response = httpClient . выполнить ( запрос );
27
}
28
29
}
Возможны и другие конфигурации:
Односторонняя аутентификация с JDK по умолчанию trustStore
:
Джава
xxxxxxxxxx
1
SSLFactory . строитель ()
2
, withDefaultJdkTrustStore ()
3
, build ();
Односторонняя аутентификация, доверяя всем сертификатам без проверки, не рекомендуется использовать на производстве!
Джава
xxxxxxxxxx
1
SSLFactory . строитель ()
2
, withTrustingAllCertificatesWithoutValidation ()
3
, build ();
Односторонняя аутентификация с определенной версией протокола шифрования и возможностью проверки имени хоста в запросе по полю SAN сертификата. Если вы используете Java 11 или новее, то вы также можете использовать TLSv1.3 в качестве протокола шифрования. Просто укажите «TLSv1.3» в качестве аргумента протокола, и он будет работать «из коробки».
Джава
xxxxxxxxxx
1
SSLFactory . строитель ()
2
, withTrustStore ( trustStore , trustStorePassword )
3
, withHostnameVerifierEnabled ( true )
4
, withProtocol ( "TLSv1.2" )
5
, build ();
Двусторонняя аутентификация с пользовательской проверкой trustStore
имени хоста и версией протокола шифрования:
Джава
xxxxxxxxxx
1
SSLFactory . строитель ()
2
, withIdentity ( идентичность , identityPassword )
3
, withTrustStore ( trustStore , trustStorePassword )
4
, withHostnameVerifierEnabled ( true )
5
, withProtocol ( "TLSv1.2" )
6
, build ();
Поддержка использования нескольких идентификаторов и trustStores
:
Джава
xxxxxxxxxx
1
SSLFactory . строитель ()
2
, withIdentity ( identityA , identityPasswordA )
3
, withIdentity ( identityB , identityPasswordB )
4
, withIdentity ( identityC , identityPasswordC )
5
, withTrustStore ( trustStoreA , trustStorePasswordA )
6
, withTrustStore ( trustStoreB , trustStorePasswordB )
7
, withTrustStore ( trustStoreC , trustStorePasswordC )
8
, withTrustStore ( trustStoreD , trustStorePasswordD )
9
, withHostnameVerifierEnabled ( true )
10
, withProtocol ( "TLSv1.2" )
11
, build ();
Преимущества :
- Больше нет необходимости в
SSLContext
настройке низкого уровня . - Никаких знаний не требуется около
SSLContext
,TrustManager
,TrustManagerFactory
,KeyManager
,KeyManagerFactory
, и как создать его. - Все вышеперечисленные классы будут созданы только с указанием идентификатора и a
trustStore
. - Создать
sslcontext
с несколькими личностями иtrustStores
.
Давайте также кратко рассмотрим модульный тест:
Джава
xxxxxxxxxx
1
импорт NL . альтиндаг . sslcontext . SSLFactory ;
2
3
импортировать статическую орг . assertj . ядро . api . Утверждения . * ;
4
5
открытый класс SSLFactoryShould {
6
7
8
общественного недействительный buildSSLFactoryForOneWayAuthenticationWithKeyStore () бросает CertificateException , NoSuchAlgorithmException , KeyStoreException , IOException {
9
String trustStorePath = "/path/to/truststore.jks" ;
10
char [] trustStorepassword = "пароль" . toCharArray ();
11
12
SSLFactory sslFactory = SSLFactory . строитель ()
13
, withTrustStore ( trustStorePath , trustStorepassword )
14
, build ();
15
16
assertThat ( sslFactory . isSecurityEnabled ()). isTrue ();
17
assertThat ( sslFactory . isOneWayAuthenticationEnabled ()). isTrue ();
18
assertThat ( sslFactory . isTwoWayAuthenticationEnabled ()). isFalse ();
19
assertThat ( sslFactory . getSslContext ()). isNotNull ();
20
21
assertThat ( sslFactory . getTrustManager ()). isNotNull ();
22
assertThat ( sslFactory . getTrustedCertificates ()). hasSize ( 3 );
23
assertThat ( sslFactory . getTrustStores ()). isNotEmpty ();
24
assertThat ( sslFactory . getTrustManagerFactory ()). isNotNull ();
25
assertThat ( sslFactory . getHostnameVerifier ()). isNotNull ();
26
27
assertThat ( sslFactory . getKeyManager ()). isNull ();
28
assertThat ( sslFactory . getKeyManagerFactory ()). isNull ();
29
assertThat ( sslFactory . getIdentities ()). isEmpty ();
30
}
31
}
Или см. SSLFactoryShould для всех других возможных случаев модульных тестов.
Ниже приведен список протестированных клиентов для Java и Scala с примерами. Посмотрите класс ClientConfig для подробной конфигурации:
- Apache HttpClient .
- JDK HttpClient .
- Старый JDK HttpClient .
- Spring RestTemplate.
- Spring WebFlux WebClient Netty.
- Spring WebFlux WebClient Jetty.
- OkHttp .
- Джерси Клиент .
- Старый Джерси Клиент.
- Google HttpClient .
- Unirest .
- Переоборудование .
- Finagle .
- Акка Http Клиент .
- Диспетчер перезагрузки Http Client .
- Async Http Client .
Исходный код доступен здесь на Github: SSLContext-Kickstart .
Получите последнюю версию из Maven Central или скопируйте и вставьте следующий фрагмент в свой pom:
XML
xxxxxxxxxx
1
< зависимость >
2
< groupId > io.github.hakky54 </ groupId >
3
< artifactId > sslcontext-kickstart </ artifactId >
4
< версия > 3.0.2 </ версия >
5
</ зависимость >
Удачи и наслаждайтесь включением шифрования в вашем проекте!