How to avoid javax.net.ssl.SSLPeerUnverifiedException: Hostname test.server.com not verified

Viewed 2861

I have a spring-boot app (version 2.3.7) that uses okhttp3 and I'm trying to call an api in https://test.server.com:8888/api . The certificate on the server is self-signed so I have updated my carets to trust this certificate. When I m running the SSLPoke.class like this java SSLPoke test.server.com 8888 I m getting Successfully Connected. But when the request is made from my app I m getting the javax.net.ssl.SSLPeerUnverifiedException: Hostname test.server.com not verified: error. Since the cacerts is updated do I need to write extra code to verify the hostname ?

To be more specific I have the following snippet. If I pass the truststore in the VM options -Djavax.net.ssl.trustStore=client-truststore.jks -Djavax.net.ssl.trustStorePassword=changeit the call with the okhttp fails, but with rest template succeeds.

@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();
}

The output of the above snippet is this

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"]>

2 Answers

I suspect that it is because you are generating an invalid certificates, CN shouldn't be used anymore for hostname validation. subjectAltNames is empty in your certificate. This changed in 3.10.0

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

New: Don’t fall back to common name (CN) verification for hostnames. This behavior was deprecated with RFC 2818 in May 2000 and was recently dropped from major web browsers.

If you just want to white list one devserver you can use the following

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()

Apparently the only way to do it is to make OkHttp to ignore hostname verification

 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();
Related