Search code examples
javatomcatssljsse

how to write java client and server applications that uses mutual ssl authentication between them?


I'm building two java applications which have to communicate using SSL two way authentication, I used instructions from here to create client and server certificates.
then I built a webService in the server application like this:

@RequestMapping(value="/testssl", method = RequestMethod.POST, produces="application/json", consumes="application/json")
@ResponseBody
public String testSSl(@RequestBody String name) {       
    return = successfully tested;
}

and I edited tomcat server.xml file to enable SSL like this:

<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
           maxThreads="150" scheme="https" secure="true"
           clientAuth="true" sslProtocol="TLS" 
           keystoreFile="keyStore/server.jks"
           keystorePass="server123" />

and put server.jks in Tomcat/Jenkins-Tomcat/keystore/server.jks
then I built the client application to invoke this web service through SSL using spring RestTemplate like this:

Properties props = new Properties();
props.put("javax.net.ssl.trustStore", "D:\\test\\server.jks");
props.put("javax.net.ssl.trustStoreType", "jks");
props.put("javax.net.ssl.trustStorePassword", "server123");

props.put("javax.net.ssl.keyStore", "D:\\test\\client.jks");
props.put("javax.net.ssl.keyStoreType", "jks");
props.put("javax.net.ssl.keyStorePassword", "client123");

props.put("java.protocol.handler.pkgs", "com.sun.net.ssl.internal.www.protocol");
props.put("ws.ssl.loose.disableHostnameVerification", "true");

System.setProperties(props);
SSLContext sslcontext = SSLContexts.createDefault();

SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext,
    new String[] { "TLSv1" }, null, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

HttpsURLConnection.setDefaultSSLSocketFactory(sslcontext.getSocketFactory());
CloseableHttpClient httpClient = HttpClientBuilder.create()
    .setSSLSocketFactory(sslsf).build();

org.springframework.http.client.HttpComponentsClientHttpRequestFactory cc = new HttpComponentsClientHttpRequestFactory(httpClient);
RestTemplate rest = new RestTemplate(cc);
String resp = rest.postForObject("https://10.141.0.77:8443/Monim/testssl", "monim", String.class);
System.err.println(resp);

but when I run this application I get unable to find valid certification path to requested target exception. I tried to create the certificates again but the error still present. when I request the url from firefox browser I get An error occurred during a connection to 10.141.0.77:8443. SSL peer cannot verify your certificate. (Error code: ssl_error_bad_cert_alert) and when using chrome to access the url I get

Error code: ERR_BAD_SSL_CLIENT_AUTH_CERT

here is the stacktrace of the client application

org.springframework.web.client.ResourceAccessException: I/O error on POST request for "https://10.141.0.77:8443/Monim/testssl":sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target; nested exception is javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:503)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:452)
at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:302)
at com.ebs.csh.sva.handler.SOAPMessage.SendingSoapPostMsg(SOAPMessage.java:135)
at com.ebs.csh.sva.handler.SVAHandler.creditSVA(SVAHandler.java:136)
at com.ebs.csh.sva.services.SVAService.creditSVA(SVAService.java:73)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at com.ebs.commons.services.CommonService.processRequest(CommonService.java:276)
at com.ebs.csh.commons.services.CSHService.processRequest(CSHService.java:52)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:96)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:260)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
at $Proxy16.processRequest(Unknown Source)
at com.ebs.commons.webServices.CommonWebService.processRequest(CommonWebService.java:80)
at com.ebs.csh.commons.webServices.CSHWebService.processRequest(CSHWebService.java:63)
at com.ebs.csh.sva.webServices.SVAWebService.creditSVA(SVAWebService.java:59)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:219)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:745)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:686)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:936)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:838)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:641)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:812)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:306)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:240)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:161)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:164)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:541)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:383)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:243)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:188)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:166)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:288)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
at java.lang.Thread.run(Thread.java:722)
Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1836)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:276)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:270)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1337)
at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:154)
at sun.security.ssl.Handshaker.processLoop(Handshaker.java:868)
at sun.security.ssl.Handshaker.process_record(Handshaker.java:804)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:966)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1262)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1289)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1273)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:261)
at org.apache.http.impl.conn.HttpClientConnectionOperator.connect(HttpClientConnectionOperator.java:118)
at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:314)
at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:357)
at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:218)
at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:194)
at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:85)
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:108)
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:186)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:57)
at org.springframework.http.client.HttpComponentsClientHttpRequest.executeInternal(HttpComponentsClientHttpRequest.java:88)
at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:46)
at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:49)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:488)
... 60 more
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:385)
at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:292)
at sun.security.validator.Validator.validate(Validator.java:260)
at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:326)
at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:231)
at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:126)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1319)
... 82 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:196)
at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:268)
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:380)
... 88 more

how to fix this error. thanks


Solution

  • after spending a long time to make this work, the solution is as follows:
    first when setting the properties using System.put("key", "value") I noticed after many tries it is not setting the KeyStore value it is does not appear in the ssl log , also tried to set the properties as parameters in the tomcat configuration using -Djavax.net.ssl.keyStore="" this also didn't work.
    so I read the KeyStore from an InputStream like this:

        KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
        FileInputStream io = new FileInputStream("path/to/jks/file.jks");
        try{
            keystore.load(io, "pass".toCharArray());
        } finally {
            io.close();
        }
    

    and use the same previous code to read the TrustStore like :

        KeyStore trustStore= KeyStore.getInstance(KeyStore.getDefaultType());
        FileInputStream stream = new FileInputStream("path/to/truststore/file.jks");
        try{
            keystore.load(stream, "trusted".toCharArray());
        } finally {
            stream.close();
        }
    

    after that define the SSLContext :

    SSLContext sslcontext = SSLContexts.custom()
                .loadKeyMaterial(keystore, "keyPassword".toCharArray())
                .loadTrustMaterial(truststore, new TrustSelfSignedStrategy())
                .build();  
    

    loadKeyMaterial used to load the keyStore it takes the password of your privateKey "keyPassword" as a parameter, so you must protect the private key using a password, when I tried to use a plain private key not encrypted "protected" with a password, I get exception which says: java.security.UnrecoverableKeyException: Cannot recover key, by default this is the same as the keystore password unless you changed it.
    then create the SSLConnectionSocketFactory and pass it as a parameter to the HttpClient .. this worked for me :)