10

I am using the spring webflux webclient tool to call the API. The API server address is HTTPS, and it is an IP address without a domain name. I need to disable the hostname validation in webclient. The exception now is as follows

Caused by: java.security.cert.CertificateException: No subject alternative names matching IP address 180.101.147.89 found
    at sun.security.util.HostnameChecker.matchIP(HostnameChecker.java:168) ~[na:1.8.0_211]
    at sun.security.util.HostnameChecker.match(HostnameChecker.java:94) ~[na:1.8.0_211]
    at sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:461) ~[na:1.8.0_211]
    at sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:442) ~[na:1.8.0_211]
    at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:260) ~[na:1.8.0_211]
    at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:144) ~[na:1.8.0_211]
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1626) ~[na:1.8.0_211]
    ... 28 common frames omitted
@Bean
    public WebClient telcomWebclient(WebClient.Builder webClientBuilder,
                                     @Value("${telcom.api.host}") String telcomApiHost,
                                     @Value("${telcom.api.certificate-name}") String telcomApiCertificateName,
                                     @Value("${telcom.api.certificate-store-pass}") String telcomApiCertificateStorePass) {
        try {
            KeyStore selfCert = KeyStore.getInstance("pkcs12");
            selfCert.load(getClass().getResourceAsStream("/cert/outgoing.CertwithKey.pkcs12"), "IoM@1234".toCharArray());
            KeyManagerFactory kmf = KeyManagerFactory.getInstance("sunx509");
            kmf.init(selfCert, "IoM@1234".toCharArray());

            KeyStore caCert = KeyStore.getInstance("jks");
            caCert.load(getClass().getResourceAsStream("/cert/" + telcomApiCertificateName), telcomApiCertificateStorePass.toCharArray());
            TrustManagerFactory tmf = TrustManagerFactory.getInstance("sunx509");
            tmf.init(caCert);

            SslContext sslContext = SslContextBuilder.forClient()
                    .keyManager(kmf)
                    .trustManager(tmf)
                    .build();
            HttpClient httpClient = HttpClient.create().create().secure(sslContextSpec -> sslContextSpec.sslContext(sslContext));
            ClientHttpConnector clientHttpConnector = new ReactorClientHttpConnector(httpClient);
            return webClientBuilder.clientConnector(clientHttpConnector).baseUrl(telcomApiHost).build();
        } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException | UnrecoverableKeyException e) {
            log.error("Config webclient,error occurs", e);
            System.exit(-1);
        }
        return null;
    }
wangyongjun
  • 145
  • 1
  • 1
  • 9

2 Answers2

11

Aside from disabling SSL verification entirely, (WHICH I DON'T RECOMMEND) by passing in InsecureTrustManagerFactory.INSTANCE like this:

SslContext sslContext = SslContextBuilder.forClient()
                    .keyManager(kmf)
                    .trustManager(InsecureTrustManagerFactory.INSTANCE)
                    .build();

You can configure the HttpClient to essentially override the hostname verification by configuring a custom SNIMatcher as below:

HttpClient.create().create().secure(sslContextSpec -> sslContextSpec
    .sslContext(sslContext)
    .handlerConfigurator(sslHandler -> 
        SSLEngine engine = handler.engine();
        //engine.setNeedClientAuth(true);
        SSLParameters params = new SSLParameters();
        List<SNIMatcher> matchers = new LinkedList<>();

        SNIMatcher matcher = new SNIMatcher(0) {
            @Override
            public boolean matches(SNIServerName serverName) {
                return true;
            }
        };

        matchers.add(matcher);
        params.setSNIMatchers(matchers);
        engine.setSSLParameters(params);
);

I have tested this and verified it worked. I hope this helps!

This was inspired by the answer here: Configure HostnameVerifier with reactor netty for spring-webflux WebClient

James
  • 126
  • 2
  • 4
  • 1
    Great!Thank you vert much! – wangyongjun Jul 22 '19 at 05:46
  • @James It works fine and bypass hostname verification for all in webclient. Do we have any solution if we want to bypass hostname verification only for few host and rest should still verified? similar to X509Hostnameverifier while calling rest template we can add check for what domain we want to bypass. – parag mangal Aug 02 '21 at 13:04
10

Actually SNIMatcher is for server side only. This can be verified by putting a break point on the return of the matches() method. Execution never stops at that break point.
The actual reason the host name verification is skipped because the newly params.identificationAlgorithm value is null instead of "HTTPS" when the SSLParameters is overriden.
Here is a simpler code which works as well as above.

           HttpClient.create().secure(sslContextSpec -> sslContextSpec
                   .sslContext(sslContext)
                   .handlerConfigurator(sslHandler -> {
                       SSLEngine engine = handler.engine();
                       SSLParameters params = new SSLParameters(engine.getSSLParameters().getCipherSuites(), engine.getSSLParameters().getProtocols());
                       // With java update null value is no longer sufficient to skip host name verification
                       params.setEndpointIdentificationAlgorithm("");
                       engine.setSSLParameters(params);
                   }));
hung nguyen
  • 101
  • 1
  • 3
  • Works for me, Thanks! Slight simplification. ```java SSLEngine engine = sslHandler.engine(); SSLParameters params = engine.getSSLParameters(); // With java update null value is no longer sufficient to skip host name verification params.setEndpointIdentificationAlgorithm(""); engine.setSSLParameters(params); ``` – coldkreap Aug 25 '22 at 21:08
  • 1
    Lambda param is `sslHandler` but param in block is `handler`, just an fyi. – coldkreap Aug 25 '22 at 21:12