Как избежать javax.net.ssl.SSLPeerUnverifiedException: Имя хоста test.server.com не проверено

#java #spring-boot #ssl #okhttp

#java #пружинный загрузчик #ssl #okhttp

Вопрос:

У меня есть приложение с весенней загрузкой (версия 2.3.7), которое использует okhttp3, и я пытаюсь вызвать api https://test.server.com:8888/api . Сертификат на сервере самоподписан, поэтому я обновил свои каретки, чтобы доверять этому сертификату. Когда я запускаю SSLPoke.class вот так java SSLPoke test.server.com 8888 Я получаю Successfully Connected . Но когда запрос выполняется из моего приложения, я получаю сообщение об javax.net.ssl.SSLPeerUnverifiedException: Hostname test.server.com not verified: ошибке. Поскольку cacerts обновлен, нужно ли мне писать дополнительный код для проверки имени хоста?

Чтобы быть более конкретным, у меня есть следующий фрагмент. Если я передам truststore в параметрах виртуальной -Djavax.net.ssl.trustStore=client-truststore.jks -Djavax.net.ssl.trustStorePassword=changeit машины, вызов с okhttp завершится неудачно, но с шаблоном rest завершится успешно.

 @SpringBootApplication
public class DemoOkhttpClientApplication implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.run(DemoOkhttpClientApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        try {
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl("https://localhost:8443/")
                    .addConverterFactory(MoshiConverterFactory.create())
                    .build();
            TestClient testClient = retrofit.create(TestClient.class);
            System.out.println(String.format("OkHttp Response %s", testClient.callTestEndpoint().execute().body()));
        } catch (Exception ex){
            System.err.println();
            System.err.println("Ok Http error "   ex.getMessage());
        }

        try {
            RestTemplate restTemplate = new RestTemplate();
            System.out.println(String.format("Rest Template Response %s",
                    restTemplate.exchange("https://localhost:8443/test", HttpMethod.GET, null, String.class)));
        } catch (Exception ex){
            System.err.println("Rest Template error"   ex.getMessage());
        }
    }
}

interface TestClient {
    @GET("/test")
    Call<String> callTestEndpoint();
}

 

Вывод приведенного выше фрагмента выглядит следующим образом

 Ok Http error Hostname localhost not verified:
    certificate: sha256/INkKXJiMFIGNnvE5ga1Ye0KjxjP5jO9hIrNvQs4wuU0=
    DN: CN=localhost, OU=PC, O=PC, L=Marousi, ST=Athens, C=GR
    subjectAltNames: []
Rest Template Response <200,Ok,[Content-Type:"text/plain;charset=UTF-8", Content-Length:"2", Date:"Thu, 11 Feb 2021 17:09:55 GMT", Keep-Alive:"timeout=60", Connection:"keep-alive"]>

 

Ответ №1:

Я подозреваю, что это связано с тем, что вы генерируете недопустимые сертификаты, CN больше не следует использовать для проверки имени хоста. subjectAltNames пусто в вашем сертификате. Это изменилось в 3.10.0

https://square.github.io/okhttp/changelog_3x/#version-3100

Новое: не возвращайтесь к проверке общих имен (CN) для имен хостов. Это поведение устарело с RFC 2818 в мае 2000 года и недавно было удалено из основных веб-браузеров.

Если вы просто хотите внести в белый список один сервер разработки, вы можете использовать следующее

https://square.github.io/okhttp/changelog/#version-470

 val clientCertificates = HandshakeCertificates.Builder()
    .addPlatformTrustedCertificates()
    .addInsecureHost("localhost")
    .build()

val client = OkHttpClient.Builder()
    .sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager)
    .build()
 

Комментарии:

1. Сертификат действительно не имеет никаких subjectAltNames и с самой старой версией OkHttp работал нормально. Большое спасибо @yuri.

Ответ №2:

По-видимому, единственный способ сделать это — заставить OkHttp игнорировать проверку имени хоста

  OkHttpClient okHttpClient = new OkHttpClient.Builder().hostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            }).build();
            Retrofit retrofit = new Retrofit.Builder()
                    .client(okHttpClient)
                    .baseUrl("https://localhost:8443/")
                    .addConverterFactory(MoshiConverterFactory.create())
                    .build();
 

Комментарии:

1. Это способ сделать это, если (как и я) вы используете старую версию okhttp, старше 4.7.0