Restlet javax.net.ssl.SSLHandshakeException: null cert chain

vikas27 picture vikas27 · Sep 10, 2015 · Viewed 24.4k times · Source

I am testing SSL communication between client and server locally. So I generated certificate using OpenSSL commands. Added this certificate in cacert file. Also generated .p12 file.

I am using the same .p12 file in server and client. This is the server code

Server server = component.getServers().add(Protocol.HTTPS, port);
Series<Parameter> params = server.getContext().getParameters();

params.add("keystorePath", ".p12 file path");
params.add("keystoreType", "PKCS12");
params.add("needClientAuthentication","true");

component.getDefaultHost().attach("", "/AA"), new AAClass());
component.start();

And this is client code:

Client client = trustAllCerts();
clientResource = new ClientResource(url);
clientResource.setNext(client);
try{
      clientText = clientResource.post"");
 }
 catch(ResourceException e){
    e.printStackTrace();
 }

public Client trustAllCerts() {
    Client client = null;
    try {
        client = new Client(new Context(), Protocol.HTTPS);
        Context context = client.getContext();


        final SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
        context.getAttributes().put("sslContextFactory", new SslContextFactory() {
            public void init(Series<Parameter> parameters) {

            }

            public SSLContext createSslContext() {
                return sslContext;
            }
        });
        TrustManager tm = new X509TrustManager() {
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };
        context.getAttributes().put("hostnameVerifier", new HostnameVerifier() {                
            @Override
            public boolean verify(String arg0, SSLSession arg1) {
                return true;
            }

        });         

        sslContext.init(null, new TrustManager[] { tm }, null);         

    } catch (KeyManagementException e) {
        LOGGER.error("Exception in Key Management" + e);
    } catch (NoSuchAlgorithmException e) {
        LOGGER.error("Exception in Algorithm Used" + e);
    }
    return client;
}

I am getting following exception:

Restlet-1299242, fatal error: 42: null cert chain
javax.net.ssl.SSLHandshakeException: null cert chain
%% Invalidated:  [Session-25, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256]
Restlet-1299242, SEND TLSv1.2 ALERT:  fatal, description = bad_certificate
Restlet-1299242, WRITE: TLSv1.2 Alert, length = 2
Restlet-1299242, fatal: engine already closed.  Rethrowing javax.net.ssl.SSLHandshakeException: null cert chain
Restlet-1299242, called closeInbound()
Restlet-1299242, fatal: engine already closed.  Rethrowing javax.net.ssl.SSLException: Inbound closed before receiving peer's close_notify: possible truncation attack?
Restlet-1299242, called closeOutbound()
Restlet-1299242, closeOutboundInternal()

I tried to add keystore and truststore using System.setProperty() but it didn't work.

Please help. Thanks in advance.

Answer

bhdrkn picture bhdrkn · Sep 14, 2015

First, lets create a JKS formatted keystore. PKCS12 is usually used in browser and on default java applications uses JKS (as far as I know). Java also supports PKCS12 but I do not know exact parameters for it.

Preparing JKS File

Lets look in our PKCS12 file and get the certificate aliases that we want to extract our JKS file.

keytool -list \
        -keystore [*.p12 file] \
        -storepass [password] \
        -storetype PKCS12 \
        -v

Note the aliases you want to export. And now lets create a JKS file.

keytool -keystore [*.jks file path] -genkey -alias client

This will ask bunch of questions. You can fill them as you like. Now, you can export your aliases from *.p12 file to *.jks file.

keytool -importkeystore \
        -srckeystore [*.p12 file path] \
        -srcstoretype pkcs12 \
        -srcalias [alias from first command] \
        -destkeystore [*.jks file path] \
        -deststoretype jks \
        -deststorepass [*.jks file password] \
        -destalias [new alias]

If you do not have any PKCS12 file, or your certificates are in CER, DER or PEM format you can add your certificates to your keystore using the command below.

keytool -import \
        -alias [new alias] \
        -keystore [*.jks file path] \
        -file [*.DER file path]

And please be sure that you imported, your certificate, your certificate provider's certificate (intermediate certificate) and root certificate.

Now you can check that your JKS file contains all the certificates you are needed.

keytool -list \
        -keystore [*.jks file path] \
        -storepass [password] \
        -storetype jks \
        -v

Setting up Server

You can use your JKS file both on client and server side. According to Restlet documentation you can use JKS file like this to provide HTTPS connection.

Server server = component.getServers().add(Protocol.HTTPS, port);  
Series<Parameter> parameters = server.getContext().getParameters();
parameters.add("sslContextFactory","org.restlet.engine.ssl.DefaultSslContextFactory");
parameters.add("keyStorePath", "*.jks file");
parameters.add("keyStorePassword", "password");
parameters.add("keyPassword", "password");
parameters.add("keyStoreType", "JKS");

After that if you check your port from browser you must see a secure sign. Or you can use some online tool(like this one) to check your certificate.

Setting up Client

Now lets look at client side. Since you are developing both side of the application you can use already created JKS file.

Context con = new Context();
Series<Parameter> clParameters = con.getParameters();
clParameters.add("truststorePath", "*.jks file");
clParameters.add("truststorePassword", "password");
clParameters.add("truststoreType", "JKS");
Client restletClient = new Client(con, Protocol.HTTPS);

While testing or in other circumstances, your certificate hostname and your actual hostname may not match. In order to disable hostname checks you can add this block to your application.

static{
    javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
    new javax.net.ssl.HostnameVerifier(){

        public boolean verify(String hostname,
                javax.net.ssl.SSLSession sslSession ) {
            return true ;
        }
    });
}

Some Thoughts

Since I cannot test it on my locale, I am not exactly sure that your client and server JKS file must be the same. You may only need to add your own certificate to your server.jks. SSL and certificates are always tricky for me. I usually get it work after some trial and error. I hope this will help you.

Also, You may also want to consider, using a reverse proxy kind of web server like Apache2 or Nginx. If you want to use them, you must merge your certificates to a single file. If you look at your certificate file you see that each file (your own certificate, intermediate certificate and root certificate) is like this

-----BEGIN CERTIFICATE-----
MIIDfTCCAuagAwIBAgIDErvmMA0GCSqGSIb3DQEBBQUA...
....
-----END CERTIFICATE-----

You need to simply add one to other to create a merged certificate. And than use that certificate to end SSL on Apache2 or Nginx. This is what I usually do. But on client side you still need to create JKS files.