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.
- Create each key store
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"
- Export to X.509 to register certificate in Truststore.
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
- Register each other’s certificates in Truststore.
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’s application.properties
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
- client’s application.properties
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
- The application main function of the client was set as follows. server as well
@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); }
- This is the part where the client calls the server’s API to RestTemplate.
String serverUrl = "https://localhost:8443/api/abcd/"; RestTemplate restTemplate = new RestTemplate(); ResponseEntity<String> response = restTemplate.postForEntity(serverUrl, requestEntity, String.class);
- I got a exception from client
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)} )
- and this is server’s exception
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); }
Advertisement
Answer
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; }