Search code examples
javaspring-bootsslcertificateresttemplate

How to use RestTemplate with certificate authentication?


I have an endpoint which requires SSL authentication. I'm able to successfully post a request on that endpoint with:

curl --location --request POST 'https://someurl.click' --header 'some headers' --cert my_cert.pem

I need to create a Spring Boot application which POSTs a request to that endpoint using that certificate with RestTemplate. I've read that PEM certificates are not valid and I need to use p12 or JKS.

So I converted my certificate to p12 with:

openssl pkcs12 -export -in my_cert.pem -out my_cert.p12

And used it as a Trust Store in my Bean as suggested by several references (where trustStore points to the converted p12 certificate):

    @Value("${trust-store}")
    private Resource trustStore;

    @Value("${trust-store-password}")
    private String trustStorePassword;

    @Bean
    public RestTemplate getRestTemplate(RestTemplateBuilder builder) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
        SSLContext sslContext = new SSLContextBuilder()
        .loadTrustMaterial(trustStore.getURL(), trustStorePassword.toCharArray())
        .build();
    SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext);

    HttpClient httpClient = HttpClients.custom()
        .setSSLSocketFactory(socketFactory)
        .build();

    return builder
        .requestFactory(() -> new HttpComponentsClientHttpRequestFactory(httpClient))
        .build();
    }

When using this RestTemplate I'm having SSL handshake errors.

Code:

String response = restTemplate.postForObject(myEndpoint, request, String.class);

Output:

2021-08-04 12:06:41.926 ERROR 129727 --- [nio-8080-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://myurl.click": readHandshakeRecord; nested exception is javax.net.ssl.SSLException: readHandshakeRecord] with root cause

java.net.SocketException: Broken pipe (Write failed)
        at java.base/java.net.SocketOutputStream.socketWrite0(Native Method) ~[na:na]
        at java.base/java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:110) ~[na:na]
        at java.base/java.net.SocketOutputStream.write(SocketOutputStream.java:150) ~[na:na]
        at java.base/sun.security.ssl.SSLSocketOutputRecord.encodeChangeCipherSpec(SSLSocketOutputRecord.java:221) ~[na:na]
        at java.base/sun.security.ssl.OutputRecord.changeWriteCiphers(OutputRecord.java:162) ~[na:na]
        at java.base/sun.security.ssl.ChangeCipherSpec$T10ChangeCipherSpecProducer.produce(ChangeCipherSpec.java:118) ~[na:na]
        at java.base/sun.security.ssl.Finished$T12FinishedProducer.onProduceFinished(Finished.java:395) ~[na:na]
        at java.base/sun.security.ssl.Finished$T12FinishedProducer.produce(Finished.java:379) ~[na:na]...

Any advice is greately appreciated.


Solution

  • I was able to make it work by using a JKS keystore instead of a p12 certificate.

    First, I used the private key and both private and public keys as an input to generate a P12 certificate:

    openssl pkcs12 -export -inkey <private_key>.pem -in <all_keys>.pem -name new_certificate -out certificate.p12
    

    Finally, I converted the P12 certificate into a JKS keystore using keytool:

    keytool -importkeystore -srckeystore certificate.p12 -srcstoretype pkcs12 -destkeystore certificate.jks
    

    Then, I could finally load it in a RestTemplate instance using SSLContext:

    @Value("${trust-store}")
    private String trustStore;
    @Value("${trust-store-password}")
    private String trustStorePassword;
    
    @Bean
    public RestTemplate getRestTemplate(RestTemplateBuilder builder) throws IOException, CertificateException,
                                                                            NoSuchAlgorithmException, KeyStoreException,
                                                                            KeyManagementException, UnrecoverableKeyException {
        Resource trustStoreCertificate = new ClassPathResource(trustStore);
        File certificate = new File("combined.jks");
        FileUtils.copyInputStreamToFile(trustStoreCertificate.getInputStream(), certificate);
        SSLContext sslContext = new SSLContextBuilder()
            .loadKeyMaterial(certificate , trustStorePassword.toCharArray(), trustStorePassword.toCharArray()).build();
        SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext);
        
        HttpClient httpClient = HttpClients.custom()
            .setSSLSocketFactory(socketFactory)
            .build();
        
        certificate.delete();
        
        return builder
            .requestFactory(() -> new HttpComponentsClientHttpRequestFactory(httpClient))
            .build();
    }