Перейти к основному содержимому

Доверие к самозаверяющему сертификату в OkHttp

· 5 мин. чтения

1. Обзор

В этой статье мы увидим, как инициализировать и настроить OkHttpClient для доверия самозаверяющим сертификатам . Для этой цели мы настроим минимальное приложение Spring Boot с поддержкой HTTPS, защищенное самоподписанным сертификатом.

Дополнительные сведения о библиотеке см. в нашей коллекции статей на OkHttp .

2. Основы

Прежде чем мы углубимся в код, отвечающий за эту работу, давайте подведем итоги. Суть SSL заключается в том, что он устанавливает безопасное соединение между любыми двумя сторонами , обычно между клиентом и сервером. Кроме того, это помогает защитить конфиденциальность и целостность данных, передаваемых по сети .

JSSE API , компонент безопасности Java SE, обеспечивает полную поддержку API для протокола SSL/TLS.

Сертификаты SSL, также известные как цифровые сертификаты, играют жизненно важную роль в установлении рукопожатия TLS, способствуя шифрованию и доверию между взаимодействующими сторонами. Самозаверяющие SSL-сертификаты — это сертификаты, которые не выдаются известным и доверенным центром сертификации (ЦС). Они могут быть легко сгенерированы и подписаны разработчиком, чтобы включить HTTPS для своего программного обеспечения.

Поскольку самозаверяющие сертификаты не заслуживают доверия, ни браузеры, ни стандартные HTTPS-клиенты, такие как OkHttp и HTTP-клиент Apache , не доверяют им по умолчанию .

Наконец, мы можем удобно получить сертификат сервера с помощью веб-браузера или утилиты командной строки OpenSSL .

3. Настройка тестовой среды

Чтобы продемонстрировать приложение, принимающее самозаверяющий сертификат и доверяющее ему с помощью OkHttp, давайте быстро настроим и запустим простое приложение Spring Boot с включенным HTTPS (защищенным самозаверяющим сертификатом).

Конфигурация по умолчанию запустит сервер Tomcat, прослушивающий порт 8443, и предоставит безопасный REST API, доступный по адресу «https://localhost:8443/welcome» .

Теперь давайте воспользуемся клиентом OkHttp, чтобы сделать HTTPS-запрос к этому серверу и использовать API «/welcome» .

4. OkHttpClient и SSL

Этот раздел инициализирует OkHttpClient и использует его для подключения к тестовой среде, которую мы только что настроили. Кроме того, мы рассмотрим ошибки, возникающие на нашем пути, и шаг за шагом достигнем нашей конечной цели — доверять самозаверяющему сертификату с помощью OkHttp.

Во-первых, давайте создадим билдер для OkHttpClient :

OkHttpClient.Builder builder = new OkHttpClient.Builder();

Кроме того, давайте объявим URL-адрес HTTPS, который мы будем использовать в этом руководстве:

int SSL_APPLICATION_PORT = 8443;
String HTTPS_WELCOME_URL = "https://localhost:" + SSL_APPLICATION_PORT + "/welcome";

4.1. SSLHandshakeException _ ``

Без настройки OkHttpClient для SSL, если мы попытаемся использовать URL-адрес HTTPS, мы получим исключение безопасности:

@Test(expected = SSLHandshakeException.class)
public void whenHTTPSSelfSignedCertGET_thenException() {
builder.build()
.newCall(new Request.Builder()
.url(HTTPS_WELCOME_URL).build())
.execute();
}

Трассировка стека:

javax.net.ssl.SSLHandshakeException: PKIX path building failed: 
sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target
...

Вышеупомянутая ошибка означает, что сервер использует самозаверяющий сертификат, не подписанный центром сертификации (ЦС).

Таким образом, клиент не смог проверить цепочку доверия до корневого сертификата, поэтому он выдал исключение SSLHandshakeException .

4.2. SSLPeerUnverifiedException _ ``

Теперь давайте настроим OkHttpClient , который доверяет сертификату независимо от его природы — подписанный ЦС или самоподписанный.

Во-первых, нам нужно создать наш собственный TrustManager , который аннулирует проверки сертификатов по умолчанию и переопределяет их с нашей пользовательской реализацией:

TrustManager TRUST_ALL_CERTS = new X509TrustManager() {
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {
}

@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {
}

@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[] {};
}
};

Далее мы будем использовать приведенный выше TrustManager для инициализации SSLContext , а также установим SSLSocketFactory сборщика OkHttpClient :

SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, new TrustManager[] { TRUST_ALL_CERTS }, new java.security.SecureRandom());
builder.sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) TRUST_ALL_CERTS);

Опять же, давайте запустим тест. Нетрудно поверить, что даже после вышеуказанных настроек использование URL-адреса HTTPS вызывает ошибку:

@Test(expected = SSLPeerUnverifiedException.class)
public void givenTrustAllCerts_whenHTTPSSelfSignedCertGET_thenException() {
// initializing the SSLContext and set the sslSocketFactory
builder.build()
.newCall(new Request.Builder()
.url(HTTPS_WELCOME_URL).build())
.execute();
}

Точная ошибка:

javax.net.ssl.SSLPeerUnverifiedException: Hostname localhost not verified:
certificate: sha256/bzdWeeiDwIVjErFX98l+ogWy9OFfBJsTRWZLB/bBxbw=
DN: CN=localhost, OU=localhost, O=localhost, L=localhost, ST=localhost, C=IN
subjectAltNames: []

Это связано с известной проблемой — ошибкой проверки имени хоста . Большинство библиотек HTTP выполняют проверку имени хоста по полю DNS-имени SubjectAlternativeName сертификата , которое недоступно в самозаверяющем сертификате сервера, как показано в подробной трассировке стека выше.

4.3. Переопределение HostnameVerifier

Последним шагом к правильной настройке OkHttpClient является отключение по умолчанию HostnameVerifier и замена его другим, который обходит проверку имени хоста.

Давайте добавим этот последний кусок настройки:

builder.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});

Теперь давайте запустим наш тест в последний раз:

@Test
public void givenTrustAllCertsSkipHostnameVerification_whenHTTPSSelfSignedCertGET_then200OK() {
// initializing the SSLContext and set the sslSocketFactory
// set the custom hostnameVerifier
Response response = builder.build()
.newCall(new Request.Builder()
.url(HTTPS_WELCOME_URL).build())
.execute();
assertEquals(200, response.code());
assertNotNull(response.body());
assertEquals("<h1>Welcome to Secured Site</h1>", response.body()
.string());
}

Наконец, OkHttpClient может успешно использовать URL-адрес HTTPS, защищенный самозаверяющим сертификатом .

5. Вывод

В этом руководстве мы узнали о настройке SSL для OkHttpClient таким образом, чтобы он мог доверять самозаверяющему сертификату и использовать любой URL-адрес HTTPS.

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

Как всегда, мы можем найти полный исходный код на GitHub .