1

I have an application server running some utility commands, which is programmed in C. I have to connect to the server through Java client program using Java SSL socket with client authentication. The key on the server side was created using:

   openssl req -new -text -out ser.req
   openssl rsa -in privkey.pem -out ser.key
   openssl req -x509 -in ser.req -text -key ser.key -out ser.crt

I have been provided the server key and certificate. I have combined the key and certificate into a PKCS12 format file:

openssl pkcs12 -inkey ser.key -in ser.crt -export -out ser.pkcs12

Then loading the resulting PKCS12 file into a JSSE keystore with keytool:

keytool -importkeystore -srckeystore ser.pkcs12 -srcstoretype PKCS12 -destkeystore ser.keystore

But when I try to connect, I get the following error:

javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at sun.security.ssl.Alert.createSSLException(Alert.java:131)
    at sun.security.ssl.TransportContext.fatal(TransportContext.java:324)
    at sun.security.ssl.TransportContext.fatal(TransportContext.java:267)
    at sun.security.ssl.TransportContext.fatal(TransportContext.java:262)
    at sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:654)
    at sun.security.ssl.CertificateMessage$T12CertificateConsumer.onCertificate(CertificateMessage.java:473)
    at sun.security.ssl.CertificateMessage$T12CertificateConsumer.consume(CertificateMessage.java:369)
    at sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:377)
    at sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:444)
    at sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:422)
    at sun.security.ssl.TransportContext.dispatch(TransportContext.java:182)
    at sun.security.ssl.SSLTransport.decode(SSLTransport.java:149)
    at sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1143)
    at sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1054)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:394)
    at SSLSocketClient.main(SSLSocketClient.java:67)
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:456)
    at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:323)
    at sun.security.validator.Validator.validate(Validator.java:271)
    at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:315)
    at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:223)
    at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:129)
    at sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:638)
    ... 11 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141)
    at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126)
    at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
    at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:451)
    ... 17 more

On the server side log:

SSL open_server: could not accept SSL connection: sslv3 alert certificate unknown

Running command:

java -Djavax.net.ssl.keyStore=/path/to/ser.keystore -Djavax.net.ssl.keyStorePassword=passwd SSLSocketClient <server-ip> <port>

Does anyone know the cause of this problem?

Updated the client source code:

import java.net.*;
import java.io.*;
import javax.net.ssl.*;

import java.security.cert.CertificateFactory;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.security.KeyStore;
import java.security.SecureRandom;
import javax.net.SocketFactory;

public class SSLSocketClient {

   public static void main(String [] args) throws Exception {
      String serverName = args[0];
      int port = Integer.parseInt(args[1]);
      try {

        SSLSocketFactory sf =
                (SSLSocketFactory)SSLSocketFactory.getDefault();

        Socket client = new Socket(serverName, port);

        System.out.println("Connected to " + client.getRemoteSocketAddress());
        OutputStream outToServer = client.getOutputStream();
        DataOutputStream out = new DataOutputStream(new BufferedOutputStream(outToServer));

        writeData(out);
        out.flush();

        InputStream inFromServer = client.getInputStream();
        DataInputStream in = new DataInputStream(inFromServer);

        
        readData(in);
        outToServer = client.getOutputStream();
        out = new DataOutputStream(new BufferedOutputStream(outToServer));
        writeData2(out);
        out.flush();
        
        Socket newClient = sf.createSocket(client, serverName, port, false);

        client.close();
      } catch (IOException e) {
         e.printStackTrace();
      }
   }

    private static void writeData(DataOutputStream out) throws IOException {
         char CMD_CHAR_U = 'U';
         byte b = (byte) (0x00ff & CMD_CHAR_U);

         out.writeByte(b);          // <U>
    }

    private static void writeData2(DataOutputStream out) throws IOException {
         char CMD_CHAR_S = 'S';
         byte b = (byte) (0x00ff & CMD_CHAR_S);

         out.writeByte(b);          // <S>
    }

    private static void readData(DataInputStream in) throws IOException {
        char sChar = (char) in.readByte(); 
        System.out.println("<S>\t\t" + sChar);
    }
}

Now creating the truststore as shown in the link: https://jdbc.postgresql.org/documentation/head/ssl-client.html

Steps to create:

openssl x509 -in server.crt -out server.crt.der -outform der
keytool -keystore mystore -alias clientstore -import -file server.crt.der
java -Djavax.net.ssl.trustStore=mystore -Djavax.net.ssl.trustStorePassword=mypassword com.mycompany.MyApp

Note - The server side is using TLSv1 protocol

But still not able to make it through. What am I doing wrong? What I want is the server to authenticate the crt of the client.

The login protocol with server; the SSL we use is only to authenticate not to secure the transmission:

    -------------------------------------------------------------
    client                                            server 
    -------------------------------------------------------------

    sock = connect()                                 sock = accept()
                      <U><LOGIN_SSL=501>
                 --------------------------------->
                       'S'|'E'
                 <---------------------------------
                       'S'
                 --------------------------------->
    SSL_connect(sock)                               SSL_accept(sock)

                      <R><LOGIN_SSL>
                 <---------------------------------
devd
  • 370
  • 10
  • 28
  • It says first error is `unable to find valid certification path to requested target`. Maybe this link can help? https://stackoverflow.com/questions/9210514/unable-to-find-valid-certification-path-to-requested-target-error-even-after-c – Abkarino Jan 20 '21 at 14:32
  • I would recommend using full path for the keystore. – Abkarino Jan 20 '21 at 14:33
  • Can you provide how you configured your client, so the actual code snippet – Hakan54 Jan 21 '21 at 01:19
  • @Hakan54 updated the post with code. – devd Jan 21 '21 at 06:43
  • @Abkarino I had already checked that link; tried providing the full path. – devd Jan 21 '21 at 06:44
  • Check that you have the servers Root-CA-Certificate in your truststore and intermediates. Use javax.net.debug to debug the TLS connection. – beat Jan 21 '21 at 08:30
  • @beat how to do that and what intermediaries? – devd Jan 21 '21 at 10:56
  • Can you also add your code snippet of how you read the `ser.keystore` file and load it into your client? It will give us a better understanding of how you configure ssl and supply it to the client – Hakan54 Jan 21 '21 at 11:29
  • @Hakan54 I'm using the code given in the post as it is. There's no ```ser.keystore```, I have only server.key and ```server.crt```. – devd Jan 21 '21 at 12:02
  • see https://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/ReadDebug.html for howto use javax.net.debug – beat Jan 21 '21 at 15:54
  • just out of curiosity why are you using socket and sslsocketfactory? Why not JDK HttpClient or the HttpsUrlConnection or some other http client? – Hakan54 Jan 21 '21 at 18:49
  • @beat I am already doing it. – devd Jan 22 '21 at 05:27
  • @Hakan54 the server application is non http. – devd Jan 22 '21 at 05:27

1 Answers1

6

I think you have several problems with your setup.

To configure properly the SSL connection with JSSE you need several things depending if you need to authenticate the server, the client, or to perform mutual authentication.

Let's suppose the later and more complete use case of mutual authentication.

The objective is to configure a SSLSocketFactory that you can use to contact your server.

To configure a SSLSocketFactory, you need a SSLContext.

This element in turn with require at least two elements for the mutual authentication use case, a KeyManagerFactory, required for client side SSL authentication, i.e., the server to trust the client, and TrustManagerFactory, required for configuring the client to trust the server.

Both KeyManagerFactory and TrustManagerFactory require a properly configured keystore with the necessary cryptographic material.

So, the first step will consist on generating this cryptographic material.

You already created a keystore with the server certificate:

keytool -keystore serverpublic.keystore -alias clientstore -import -file server.crt.der -storepass yourserverpublickeystorepassword

Please, be aware that, in a similar way as in the server case, you also need to create a public and private key pair for your client, of course, different than the server one.

The related code you provided with OpenSSL and keytool looks appropriate. Please, repeat the process for the client side:

openssl req -new -text -out client.csr
openssl rsa -in clientpriv.pem -out client.key
openssl req -x509 -in client.csr -text -key client.key -out client.crt
// You can use PKCS12 also with Java but it is also ok on this way
openssl pkcs12 -inkey client.key -in client.crt -export -out client.pkcs12
// Do not bother yourself and, in this use case, use always the same password for the key and keystore
keytool -importkeystore -srckeystore client.pkcs12 -srcstoretype PKCS12 -destkeystore client.keystore -storepass "yourclientkeystorepassword"

With the right keystores in place, try something like the following to interact with your server:

// First, let's configure the SSL for client authentication
KeyStore clientKeyStore = KeyStore.getInstance("JKS");
clientKeyStore.load(
  new FileInputStream("/path/to/client.keystore"),
  "yourclientkeystorepassword".toCharArray()
);

KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); // SunX509
kmf.init(clientKeyStore, "yourclientkeystorepassword".toCharArray());
KeyManager[] keyManagers = kmf.getKeyManagers();

// Now, let's configure the client to trust the server
KeyStore serverKeyStore = KeyStore.getInstance("JKS");
serverKeyStore.load(
  new FileInputStream("/path/to/serverpublic.keystore"),
  "yourserverpublickeystorepassword".toCharArray()
);

TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); // SunX509
tmf.init(serverKeyStore);
TrustManager[] trustManagers = tmf.getTrustManagers();

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null); // You can provide SecureRandom also if you wish

// Create the SSL socket factory and establish the connection
SSLSocketFactory sf = sslContext.getSocketFactory();
SSLSocket socket = (SSLSocket)sf.createSocket(serverName, port);

// Interact with your server. Place your code here
// Please, consider the following link for alternatives approaches on how to 
// interchange information with the server:
// https://web.mit.edu/java_v1.5.0_22/distrib/share/docs/guide/security/jsse/samples/sockets/client/SSLSocketClient.java
// It also suggest the use of startHandshake explicitly if your are using PrintWriter for the reason explained in the example an in the docs:
// https://docs.oracle.com/en/java/javase/11/docs/api/java.base/javax/net/ssl/SSLSocket.html

//...

// Close the socket
socket.close();

The described approach can be extended to use, instead of sockets, higher level of abstraction components like HttpsURLConnection and HTTP clients - with the exception of Apache HttpClient that handles SSL differently - like OkHttp which, under the hood, use SSLSocketFactory and related stuff.

Please, also consider review this great article from IBM's DeveloperWorks, in addition to explain many of the point aforementioned will provide you great guidance with the generation of keystores for your client an server if necessary.

Please, also be aware that, depending on your server code, you may need to configure it to trust the provided client certificate.

According to your comments you are using a server side code similar to the one provided by Postgresql 8.1. Please, see the relevant documentation for configuring SSL in that database, if you are using some similar server side code it maybe could be of help.

Probably the best approach will be to generate a client certificate derived from the root certificate trusted by your server instead of using a self signed one.

I think that it will be also relevant for your server side SSL certificate an associated private key: first, create a root self signed certificate, your CA certificate, configure your server side C code to trust it, and then derive both client and server side SSL cryptographic material from that CA: probably it will simplify your setup and make everything work properly.

jccampanero
  • 50,989
  • 3
  • 20
  • 49
  • The use case is I have a web application which talks to a server process (coded in C) which in which the authentication was username/password based, now the server side has introduced key/certificate based authentication, so I have to do the same. If I am not wrong I guess I have to do client authentication, right. The code which you have written in that what change I have to do. – devd Jan 22 '21 at 05:52
  • Sorry for the late reply @devd. Yes, I understand that the requirement is being able to authenticate your client. Please, follow the example I provided. I will update the answer to make it clear. At a first step, you need to create a keystore for your client cryptographic material, with public an private key. According to your question it seems that you haven't one now. Please, can you create it? – jccampanero Jan 22 '21 at 11:18
  • I have already done it. Now, I'm getting this error on server side SSL open_server: could not accept SSL connection: certificate verify failed. For client code and log, visit and client code: https://gist.github.com/dipdeb/61f71e79b1e884e65863962a86266888 and https://gist.github.com/dipdeb/f58efa8d810fc3cfd709fcb42758abf4 – devd Jan 22 '21 at 11:49
  • Hi @devd. It seems that, for some reason, the server does not accept the provided client certificate. Did you configured the client keystore properly? Is it possible that you need to configure some trust relationship in your server side code as well, so it trusts the client certificate? Please, also, can you try, instead of manually starting the handshake, write some information to the server using the SSLSocket? – jccampanero Jan 22 '21 at 12:43
  • What you're saying maybe correct, as I also doubt the server side. Our server side code follows the ssl connection procedure of Postgresql 8.1 and I have tried jdbc connect to Postgres8.1 in ssl mode, imilar fashion, which works fine. – devd Jan 22 '21 at 12:53
  • Hi @devd. Sorry for the late reply. I understand. Please, be aware that when you connect to [postgresql with SSL](https://jdbc.postgresql.org/documentation/head/ssl-client.html), probably you are not using mutual authentication, you are not authenticating the client, you are only validating the server identity. If you are using a similar code like the one provided with postgresql, instead of creating a self signed certificate like in the example, perhaps it should be appropriate to create a client certificate issued by a trusted certificate authority. See next comment – jccampanero Jan 22 '21 at 22:10
  • Please, see the relevant [docs](https://www.postgresql.org/docs/10/ssl-tcp.html). Please, see also this links [1](https://www.postgresql.org/docs/10/auth-methods.html#AUTH-CERT) and [2](https://smallstep.com/hello-mtls/doc/server/postgresql), I think they can also be relevant. Please, review your server side C code, and look for any kind of a similar configuration. If you consider it appropriate, post the relevant parts, I will be glad to review them also. – jccampanero Jan 22 '21 at 22:16
  • @dev Just a clarification: when I said _create a client certificate issued by a trusted certificate authority_ I mean a postgresql trusted ca according with the product configuration. Honestly I do not now, but probably you can also configure your server code to trust even the self signed client certificate. – jccampanero Jan 23 '21 at 11:02
  • I'm still not able to make it work. Thanks, for your effort anyways. – devd Jan 27 '21 at 15:10
  • The server is 3rd party and they won't change anything. – devd Jan 27 '21 at 15:11
  • I am very sorry to hear that @devd. I think the error has nothing to do with client socket now, but with the relation of trust between your client certificate and the server requiring it. Please, request more information from the 3rd party server, they should provide you some kind of guidance about why they are rejecting your client certificate. The most likely reason is that it is self signed, and instead it should depend on any way with the certs they trust, but there can be another reasons. Check their logs. Please, if you manage to find that information,do not hesitate to contact me again – jccampanero Jan 27 '21 at 16:59