javaspring-bootssl-certificatespring-webfluxreactor-netty

WebClient get SSL/TLS certificates


java.net has a simple getServerCertificates in its API (example follows). I was looking for a similar operation in reactor-netty, and if not there, in any other reactive API for spring-boot/webflux/HttpClient.

This operation (client reads certificate) does not seem possible in reactor-netty. Is it? If it isn't is there an alternative method in another spring-boot component to do this?

package com.example.readCertificate.service;

import java.net.URL;
import java.securiiity.cert.Certificate;
import javax.net.ssl.HttpsURLConnection;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class ShowCert {
    private Logger logger = LogManager.getLogger();

    public void showCert(String url) {
        try {
            URL destinationURL = new URL(url);
            HttpsURLConnection connection = (HttpsURLConnection) destinationURL.openConnection();
            connection.connect();
            Certificate[] certificates = connection.getServerCertificates();
            for (Certificate certificate : certificates) {
                logger.info("certificate is:" + certificate);
            }
        } catch (Exception e) {
            logger.error(e);
        }
    }

}

Solution

  • In WebClient from Spring WebFlux we usually use netty as backend. We provide a bean ReactorClientHttpConnector in which we create netty http-client.

    For handling SSL netty uses handler within the channel pipeline.

    Here I'm putting a callback to event doOnConnected() and accesing the SSL handler and SSLSession.

    SSLSession provides methods getPeerCertificates(), getLocalCertificates() , so we can get access to certificates here.

    @Bean
    public ReactorClientHttpConnector reactorClientHttpConnector() {
        return new ReactorClientHttpConnector(
                HttpClient.create()
                        .doOnConnected(connection -> {
                            ChannelPipeline pipeline = connection.channel().pipeline();
                                
                            Optional.ofNullable(pipeline)
                                    .map(p -> p.get(SslHandler.class))
                                    .map(SslHandler::engine)
                                    .map(SSLEngine::getSession)
                                    .ifPresent(sslSession -> {
                                        try {
                                            Certificate[] peerCertificates = sslSession.getPeerCertificates();
                                            if (Objects.nonNull(peerCertificates)) {
                                                Stream.of(peerCertificates)
                                                        .forEach(System.out::println);
                                            }
                                        } catch (Exception e) {
                                            e.printStackTrace();
                                        }
                                    });
                        })
        );
    }
    

    And create your WebClient:

    @Bean
    public WebClient httpsClient() {
        return WebClient.builder()
                .clientConnector(reactorClientHttpConnector())
                .baseUrl("https://secured-resource.com)
                .build();
    }
    

    Then while making http-call with this httpsClient bean you should see the results in your console