javaspring-bootsslkeytoolmutual-authentication

How can i solve [Received fatal alert: bad_certificate]?


I've created two servers locally, and I'm going to apply a mutual authentication to their communication. I just don't know what the problem is. I lack understanding of this mechanism, but I also lack understanding of the server itself.

keytool -genkey -alias julie-server-key -keyalg RSA -keypass 11111111 -storepass 11111111 -keystore julie/src/main/resources/julie-server-key.jks -validity 365 -keysize 2048 -dname "CN=localhost,OU=edge,O=edge,L=edge,S=edge,C=KR"
keytool -genkey -alias client-key -keyalg RSA -keypass 11111111 -storepass 11111111 -keystore transmitter/src/main/resources/client-key.jks -validity 365 -keysize 2048 -dname "CN=localhost,OU=edge,O=edge,L=edge,S=edge,C=KR"
keytool -export -alias julie-server-key -keystore julie/src/main/resources/julie-server-key.jks -file server_X509.cer -storepass 11111111
keytool -export -alias client-key -keystore transmitter/src/main/resources/client-key.jks -file client_X509.cer -storepass 11111111
keytool -import -alias client-key -keystore julie/src/main/resources/server.truststore -file transmitter/src/main/resources/client_X509.cer -storepass 11111111
keytool -import -alias julie-server-key -keystore transmitter/src/main/resources/client.truststore -file julie/src/main/resources/server_X509.cer -storepass 11111111
server.port=8443

server.ssl.enabled=true
server.ssl.key-store=/Users/julie/IdeaProjects/cloud/julie/src/main/resources/julie-server-key.jks
server.ssl.key-store-password=11111111
server.ssl.key-store-type=jks
server.ssl.key-alias=julie-server-key
erver.ssl.trust-store=/Users/julie/IdeaProjects/cloud/julie/src/main/resources/server.truststore
server.ssl.trust-store-password=11111111
server.ssl.trust-store-type=jks
server.ssl.client-auth=need
server.port=8080

server.ssl.key-store=/Users/julie/IdeaProjects/cloud/transmitter/src/main/resources/client-key.jks
server.ssl.key-store-password=11111111
server.ssl.trust-store=/Users/julie/IdeaProjects/cloud/transmitter/src/main/resources/client.truststore
server.ssl.trust-store-password=11111111
@SpringBootApplication
public class TransmitterApplication {

    private static String keyStorePath;
    private static String keyStorePassword;

    private static String trustKeyStorePath;
    private static String trustKeyStorePassword;



    @Value("${server.ssl.key-store}")
    public void setKeyStorePath(String keyStorePath) {
        TransmitterApplication.keyStorePath = keyStorePath;
    }

    @Value("${server.ssl.key-store-password}")
    public void setKeyStorePassword(String keyStorePassword) {
        TransmitterApplication.keyStorePassword = keyStorePassword;
    }


    @Value("${server.ssl.trust-store}")
    public void setTrustKeyStorePath(String trustKeyStorePath) {
        TransmitterApplication.trustKeyStorePath = trustKeyStorePath;
    }

    @Value("${server.ssl.trust-store-password}")
    public void setTrustKeyStorePassword(String trustKeyStorePassword) {
        TransmitterApplication.trustKeyStorePassword = trustKeyStorePassword;
    }

    public static void main(String[] args) {

        SpringApplication.run(TransmitterApplication.class, args);

        System.setProperty("javax.net.debug", "all");

        System.setProperty("javax.net.ssl.keyStore", keyStorePath);
        System.setProperty("javax.net.ssl.keyStorePassword", keyStorePassword);

        System.setProperty("javax.net.ssl.trustStore", trustKeyStorePath);
        System.setProperty("javax.net.ssl.trustStorePassword", trustKeyStorePassword);


    }
 String serverUrl = "https://localhost:8443/api/abcd/";

            RestTemplate restTemplate = new RestTemplate();
            ResponseEntity<String> response = restTemplate.postForEntity(serverUrl, requestEntity, String.class);

javax.net.ssl|DEBUG|21|scheduling-1|2021-10-01 13:32:25.456 KST|Alert.java:238|Received alert message (
"Alert": {
  "level"      : "fatal",
  "description": "bad_certificate"
}
)
javax.net.ssl|ERROR|21|scheduling-1|2021-10-01 13:32:25.457 KST|TransportContext.java:341|Fatal (BAD_CERTIFICATE): Received fatal alert: bad_certificate (
"throwable" : {
  javax.net.ssl.SSLHandshakeException: Received fatal alert: bad_certificate
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:336)
    at java.base/sun.security.ssl.Alert$AlertConsumer.consume(Alert.java:293)
    at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:185)
    at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:172)
    at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1429)
    at java.base/sun.security.ssl.SSLSocketImpl.readApplicationRecord(SSLSocketImpl.java:1396)
    at java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:985)
    at java.base/java.io.BufferedInputStream.fill(BufferedInputStream.java:252)
    at java.base/java.io.BufferedInputStream.read1(BufferedInputStream.java:292)
    at java.base/java.io.BufferedInputStream.read(BufferedInputStream.java:351)
    at java.base/sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:754)
    at java.base/sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:689)
    at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1615)
    at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1520)
    at java.base/java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:527)
    at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:334)
    at org.springframework.http.client.SimpleClientHttpResponse.getRawStatusCode(SimpleClientHttpResponse.java:55)
    at org.springframework.web.client.DefaultResponseErrorHandler.hasError(DefaultResponseErrorHandler.java:64)
    at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:807)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:777)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:711)
    at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:468)
    at com.penta.transmitter.scheduler.SendingScheduler.sendToEdge(SendingScheduler.java:110)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:84)
    at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:305)
    at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:829)}

)
javax.net.ssl|ALL|21|scheduling-1|2021-10-01 13:32:25.457 KST|SSLSessionImpl.java:784|Invalidated session:  Session(1633062745301|TLS_AES_128_GCM_SHA256)
javax.net.ssl|DEBUG|21|scheduling-1|2021-10-01 13:32:25.457 KST|SSLSocketImpl.java:1656|close the underlying socket
javax.net.ssl|DEBUG|21|scheduling-1|2021-10-01 13:32:25.457 KST|SSLSocketImpl.java:1675|close the SSL connection (initiative)
javax.net.ssl|WARNING|21|scheduling-1|2021-10-01 13:32:25.458 KST|SSLSocketImpl.java:1573|handling exception (
"throwable" : {
  javax.net.ssl.SSLHandshakeException: Received fatal alert: bad_certificate
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:336)
    at java.base/sun.security.ssl.Alert$AlertConsumer.consume(Alert.java:293)
    at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:185)
    at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:172)
    at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1429)
    at java.base/sun.security.ssl.SSLSocketImpl.readApplicationRecord(SSLSocketImpl.java:1396)
    at java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:985)
    at java.base/java.io.BufferedInputStream.fill(BufferedInputStream.java:252)
    at java.base/java.io.BufferedInputStream.read1(BufferedInputStream.java:292)
    at java.base/java.io.BufferedInputStream.read(BufferedInputStream.java:351)
    at java.base/sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:754)
    at java.base/sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:689)
    at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1615)
    at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1520)
    at java.base/java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:527)
    at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:334)
    at org.springframework.http.client.SimpleClientHttpResponse.getRawStatusCode(SimpleClientHttpResponse.java:55)
    at org.springframework.web.client.DefaultResponseErrorHandler.hasError(DefaultResponseErrorHandler.java:64)
    at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:807)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:777)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:711)
    at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:468)
    at com.penta.transmitter.scheduler.SendingScheduler.sendToEdge(SendingScheduler.java:110)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:84)
    at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:305)
    at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:829)}

)
2021-10-01 13:32:25.328 DEBUG 26886 --- [nio-8443-exec-5] o.a.tomcat.util.net.SecureNioChannel     : Handshake failed during wrap

javax.net.ssl.SSLHandshakeException: Empty client certificate chain
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131) ~[na:na]
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117) ~[na:na]
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:336) ~[na:na]
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:292) ~[na:na]
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:283) ~[na:na]
    at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.onConsumeCertificate(CertificateMessage.java:1194) ~[na:na]
    at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.consume(CertificateMessage.java:1181) ~[na:na]
    at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:392) ~[na:na]
    at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:443) ~[na:na]
    at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(SSLEngineImpl.java:1074) ~[na:na]
    at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(SSLEngineImpl.java:1061) ~[na:na]
    at java.base/java.security.AccessController.doPrivileged(Native Method) ~[na:na]
    at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask.run(SSLEngineImpl.java:1008) ~[na:na]
    at org.apache.tomcat.util.net.SecureNioChannel.tasks(SecureNioChannel.java:429) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.tomcat.util.net.SecureNioChannel.handshakeUnwrap(SecureNioChannel.java:493) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.tomcat.util.net.SecureNioChannel.handshake(SecureNioChannel.java:217) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1702) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at java.base/java.lang.Thread.run(Thread.java:829) ~[na:na]

I was able to see answer below and fix this problem.By the way I wrote as follows.I hope to be helpful to people who are experiencing the same problem

    @Bean
    public RestTemplate restTemplate() throws Exception {
            SSLContext sslContext = new SSLContextBuilder()
                    .loadKeyMaterial(keyStorePath, keyStorePassword.toCharArray(), keyStorePassword.toCharArray())
                    .loadTrustMaterial(trustKeyStorePath, trustKeyStorePassword.toCharArray())
                    .build();
            SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext);
            HttpClient httpClient = HttpClients.custom()
                    .setSSLHostnameVerifier((hostname, session)->true)
                    .setSSLSocketFactory(socketFactory)
                    .build();
            HttpComponentsClientHttpRequestFactory factory =
                    new HttpComponentsClientHttpRequestFactory(httpClient);
            return new RestTemplate(factory);

    }

Solution

  • I think creating RestTemplate with new keyword will not send the certificate to the server. Instead, you should wrap the SSL certificate in the Rest template. Please try this:

    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) throws Exception {
       char[] password = "password".toCharArray();
    
       SSLContext sslContext = SSLContextBuilder.create()
            .loadKeyMaterial(keyStore("classpath:cert.jks", password), password)
            .loadTrustMaterial(null, new TrustSelfSignedStrategy()).build();
    
       HttpClient client = HttpClients.custom().setSSLContext(sslContext).build();
       return builder
            .requestFactory(new HttpComponentsClientHttpRequestFactory(client))
            .build();
    } 
    
    private KeyStore keyStore(String file, char[] password) throws Exception {
       KeyStore keyStore = KeyStore.getInstance("PKCS12");
       File key = ResourceUtils.getFile(file);
       try (InputStream in = new FileInputStream(key)) {
           keyStore.load(in, password);
       }
       return keyStore;
    }