Sunday, November 30, 2014

2 Way SSL with example


This Blog will assist you to configure  2 way SSL in Apache 2 web server which I have hosted in Ubuntu 10.04(LUCID) . I have also tested 2 way SSL with a Java Client . The code can found here .
The certificates I have generated are  self-signed .

Introduction:
Now a days B2B integration requires integrity  and confidentiality of the data transferred. That is where SSL ( Secure Sockets Layer ) comes into picture. To achieve  integrity  and confidentiality of the data you need to enable SSL in your web server .

One-way SSL authentication allows a SSL client to confirm an identity of SSL server. However, SSL server cannot confirm an identity of SSL client. This kind of SSL authentication is used by HTTPS protocol. The SSL client authentication is done on a “application layer” of OSI model by the client entering an authentication credentials such as username and password or by using a grid card.

Two-way SSL authentication also known as mutual SSL authentication allows SSL client to confirm an identity of SSL server and SSL server can also confirm an identity of the SSL client. This type of authentication is called client authentication because SSL client shows its identity to SSL server with a use of the client certificate. Client authentication with a certificate can add yet another layer of security or even completely replace authentication method such us user name and password.

Generate Self-Signed certificate with Open SSL



As first step I have created a directory where I have generated all server and client keys and certificates.
 prasenjit@prasenjit-desktop:~$ mkdir ssl_keys  

Next we need to generate self-signed certificate CA. Once prompted for a set of values we can provide dummy values but relevant to your organisation. I have provided a screen shot below .
 openssl req -newkey rsa:2048 -nodes -keyform PEM -keyout ca.key -x509 -days 3650 -outform PEM -out ca.cer  



In your current directory a file “ca.key” with private key of certificate authority (CA) and ca.cer with its self-signed certificate.

Next step we need to generate private SSL key for the server:
 openssl genrsa -out server.key 2048  

Next we would generate Certificate Signing Request in PKCS#10 format.Once the command is entered we will asked for a set of values . I have provided a screen shot below .
 openssl req -new -key server.key -out server.req  

Next we will issue server certificate with serial number 100 with self-signed certificate authority:
 openssl x509 -req -in server.req -CA ca.cer -CAkey ca.key -set_serial 100 -extensions server -days 365 -outform PEM -out server.cer  


Currently if you list down your files in current directory you should following files generated:
 ca.key  
 ca.cer  
 server.key  
 server.req  
 server.cer  

New file server.key contains server's private key and file server.cer is a certificate itself.

Next for 2 way SSL Generete private key for SSL client:
 openssl genrsa -out client.key 2048  


 For client as we need to generate Certificate Signing Request:
  openssl req -new -key client.key -out client.req  

With the self-signed Certificate Authority that we have generated ,we will issue a client certificate with serial number 101:
  openssl x509 -req -in client.req -CA ca.cer -CAkey ca.key -set_serial 101 -extensions client -days 365 -outform PEM -out client.cer  

Next we will save client's private key and certificate in a PKCS#12 format. This certificate will be secured by a password and this password will be used in the following sections to import the certificate into HTTPS client. I have kept the password as "changeit" .
  openssl pkcs12 -export -inkey client.key -in client.cer -out client.p12  


Configure 2 Way SSL in Apache 2

First thing is copying all the server certificates , keys i.e ca.cer, server.cer, server.key files to "ssl" directory under apache2( /etc/apache2/ssl ).
Once copying is done open "default-ssl" file under "/etc/apache2/sites-available". Search for "SSLCertificateFile" . If it is commented , then uncomment and add the location of the "server.cer" file .


Do the same for "SSLCertificateKeyFile" as above.

Next search for "SSLVerifyClient" in the same file . Once found, replace with the below configuration.

 #  Client Authentication (Type):  
     #  Client certificate verification type and depth. Types are  
     #  none, optional, require and optional_no_ca. Depth is a  
     #  number which specifies how deeply to verify the certificate  
     #  issuer chain before deciding the certificate is not valid.  
     SSLVerifyClient require  
     SSLVerifyDepth 1  
     SSLCACertificateFile /etc/apache2/ssl/ca.cer  

Then save the file and restart apache web server.

 /etc/init.d/apache2 restart  

Test 2 Way SSL

We can test 2 Way SSL with following ways :
  • Web Browser . It is very easy if you have the client keystore file ( client.p12 ) with PKCS#12 format , just import the file in your browser and you will be able to launch the apache 2 default web page( https://localhost/ ).
  • Java Https Client : Most  B2B applications will use HTTPS clients to connect 2 way ssl enabled server . I have written a sample Java client which connects to my 2 Way ssl enabled server . To test the client we should have the following :
    • Copy the client keystore file ( client.p12 ) from server to a local directory( Lets say in my case I have downloaded to 'D:\ubuntu_apache_keys\apacheKeys' ). 
    • Generate trustStore file  using 'keytool' command and copy the trustStore  from server to a local directory. ( Lets say in my case I have downloaded to 'D:\ubuntu_apache_keys\apacheKeys' ).
  keytool -import -alias myapacheserver -file ca.cer -storepass changeit -keystore myapacheserver.keystore  

Once the  command is entered , it will ask as if we trust the certificate . We should answer with "yes" or "no". I have added yes to add it in my trustore. Below is a screenshot.

Replace the IP address in the code with your server's IP address.
 package com.prasenjit.samples;  
 import java.io.BufferedReader;  
 import java.io.IOException;  
 import java.io.InputStream;  
 import java.io.InputStreamReader;  
 import java.net.MalformedURLException;  
 import java.net.URL;  
 import javax.net.ssl.HostnameVerifier;  
 import javax.net.ssl.HttpsURLConnection;  
 import javax.net.ssl.SSLSession;  
 import javax.net.ssl.SSLSocketFactory;  
 public class TwoWaySSLClient {  
      public static void main(String[] args) {  
           SSLSocketFactory sslsocketfactory =(SSLSocketFactory) SSLSocketFactory.getDefault();  
           try {  
                URL url = new URL("https://192.168.0.102/");  
                HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();  
                conn.setHostnameVerifier(new HostnameVerifier()   
          {      
                     @Override  
                     public boolean verify(String arg0, SSLSession arg1) {  
                          return true;  
                     }   
          });   
                conn.setSSLSocketFactory(sslsocketfactory);  
                InputStream inputstream = conn.getInputStream();  
                InputStreamReader inputstreamreader = new InputStreamReader(inputstream);  
                BufferedReader bufferedreader = new BufferedReader(inputstreamreader);  
                String string = null;  
                while ((string = bufferedreader.readLine()) != null) {  
                  System.out.println("Received " + string);  
                }  
           } catch (MalformedURLException e) {  
                e.printStackTrace();  
           } catch (IOException e) {  
                e.printStackTrace();  
           }  
      }  
 }  

Run the above code with the below arguments.
 -Djavax.net.ssl.keyStoreType=pkcs12  
 -Djavax.net.ssl.trustStoreType=jks  
 -Djavax.net.ssl.keyStore=D:\\ubuntu_apache_keys\\apacheKeys\\client.p12  
 -Djavax.net.ssl.trustStore=D:\\ubuntu_apache_keys\\apacheKeys\\myapacheserver.keystore  
 -Djavax.net.debug=ssl  
 -Djavax.net.ssl.keyStorePassword=changeit  
 -Djavax.net.ssl.trustStorePassword=changeit  

If you have followed the above steps you should be able to connect 2 Way SSL enabled Apache2 web server without any error or exceptions. You can also trace the ssl handshake logs using the above param "-Djavax.net.debug=ssl" .  I will  soon add the Java src file to my GitHub .

Please add valuable feedback/comments about this post .

Thanks.


References:

The below articles and websites are used for reference . Please do visit for more explanation.





7 comments:

  1. AWESOME, thank you very much, very clear!

    ReplyDelete
  2. Hi Prasenjit,

    I'm trying to use java client to connect one of the external server. I can see in my console that ServerHello is made but after that :
    main, WRITE: TLSv1.2 Alert, length = 48
    main, Exception sending alert: java.net.SocketException: Connection reset by peer: socket write error
    main, called closeSocket()
    java.net.SocketException: Connection reset

    Any help will be appreciable

    ReplyDelete
    Replies
    1. HI ,

      This is very minimal information to analyse whats going on . Can you provide more logs (SSL logs) . You can also verify if the SSL handshake is fine or not from your end. Use -Djavax.net.debug=ssl option to output the ssl logs in detail. And please make sure you are using proper certificates and trustStore/keyStore before invoking the client.

      Thanks,
      Prasenjit

      Delete
  3. Hi Prasenjit, I have Jboss server under which multiple applications are deployed. But I would need 2 way SSL only for one application, which is nothing but rest web services.Is it possible to implement only for one application ?

    ReplyDelete
  4. Thanks for sharing this. However I am getting following error.

    javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:154)
    at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:1959)
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1077)
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1312)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1339)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1323)
    at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:563)
    at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1300)
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254)
    at TwoWaySSLClient.main(TwoWaySSLClient.java:25)

    ReplyDelete