Writing a client/server app that makes use of SSL

To create a client and a server that exchange encrypted messages, you can use the JSSE package that can be downloaded as an optional package or comes standard with JDK1.4. I’ll assume your have correctly downloaded and installed the package or are using JDK1.4.

SSL stands for Secure Socket Layer. It contains a handshake protocol where two parties exchange a series of messages to agree on a symmetric key that will be used to encrypt the rest of the messages that are exchanged throughout the session. The problem is how to let the other party know what key to use. There can always be someone spying on the messages that are sent over the network.

The solution is to use public/private key cryptography. A message encrypted with a private key can be decrypted with the associated public key and vice versa. Only one party holds his own private key and can distribute his public key to anyone, even the spy. The other party encrypts the message with the public key and sends it to the other party, which is the only one that can decrypt the message as he, and only he, holds the private key.

Party A could encrypt the symmetric key information using the public key of party B and sends him this info. Party B decrypts it and obtaines that symmetric key which they both will use during the session.

To ensure that the public key of the Party B actually belongs to him, and is not something made up of a spy claiming he is party B, he can use a public-key certificate. A public-key certificate is issued by a certification authority (CA) which you typically have to pay a couple hundred dollars and provide proof of identity in order to obtain it.

In short, the whole process goes like this.
Assume Party B is the server and party A the client.

  1. Party A and Party B agree on an SSL version and a set of encryption algorithms.
  2. Party B sends Party B the public key/certificate.
  3. Party B may also request Party A to send his public key/certificate if he wants
    to authenticate the client (eg. in banking applications).

  • Party A generates information to create a symmetric key and sends it to Party B,
    but encrypted with the public key of Party B.
  • Party A may also send Party B his public key information if Party B requested for it.
  • Now both parties have received the symmetric key and can start exchanging actual data.

    One more thing: even though messages are send encrypted, a spy may still tamper with the data that flows between A and B. To ensure data integrity, messages are appended with an HMAC before being encrypted. The result of an HMAC (Message Authentication Code) is a small series of bytes that can be used to detect the slightest change in data, even if it’s only one bit. The whole shebang (message + HMAC) are encrypted with the secret symmetric key and is send over. The receiver then decrypts the message, calculates the HMAC again, and compares it with the one that was send to him.

    Writing the server

    Instead of creating a ServerSocket using ServerSocket ss = new ServerSocket(port), we would use the SSLServerSocketFactory that creates a ServerSocket that supports SSL. This SSL ServerSocket takes care of all the gory details

    For more information, check out the excellent JSSE Sun’s User’s Guide here.

    Start of by creating a self-signed certificate that is needed by the server to send back
    to the client during the SSL handshake.

    C:securityssl>keytool -v -genkey -keyalg RSA -keystore .keystore
    Enter keystore password:  esuspass
    What is your first and last name?
      [Unknown]:  Joris Van den Bogaert
    What is the name of your organizational unit?
      [Unknown]:  Esus Team
    What is the name of your organization?
      [Unknown]:  Esus, Inc
    What is the name of your City or Locality?
      [Unknown]:  Meerbeek
    What is the name of your State or Province?
      [Unknown]:
    What is the two-letter country code for this unit?
      [Unknown]:  BE
    Is <CN=Joris Van den Bogaert, OU=Esus Team, O="Esus, Inc", L=Meerbeek, ST=Unkno
    n, C=BE> correct?
      [no]:  yes
    
    Generating 1024 bit RSA key pair and self-signed certificate (MD5WithRSA)
            for: CN=Joris Van den Bogaert, OU=Esus Team, O="Esus, Inc", L=Meerbeek,
    ST=Unknown, C=BE
    Enter key password for <mykey>
            (RETURN if same as keystore password):
    [Saving .keystore]
    
    C:securityssl>dir .keystore
    
     Volume in drive C has no label
     Volume Serial Number is 1380-0FE3
     Directory of C:securityssl
    
    KEYSTO~1             1,379  07-24-01  3:53p .keystore
             1 file(s)          1,379 bytes
             0 dir(s)     146,989,056 bytes free
    

    Here’s the code for SecureServer.java and SecureClient.java:

    SecureServer.java:

    import javax.net.ssl.*;
    import java.net.*;
    import java.io.*;
     
    public class SecureServer {
       private static final int port = 4321;
     
       public static void main(String []args) throws Exception {
          SSLServerSocketFactory ssf = (SSLServerSocketFactory)
                                          SSLServerSocketFactory.getDefault();
          ServerSocket ss = ssf.createServerSocket(port);
     
          System.out.println("Ready to accept messages!");
          Socket s = ss.accept();
     
          System.out.println("A client has connected!");
          DataInputStream dis = new DataInputStream(s.getInputStream());
          DataOutputStream dos = new DataOutputStream(s.getOutputStream());
          String line = null;
          try {
             while (true) {
                line = dis.readUTF();
                System.out.println("Client sent: " + line);
                dos.writeUTF("You send: " + line);
             } 
          }
          finally {
             dis.close();
             dos.close();
             s.close();
          }
       }
    }

    SecureClient.java:

    import javax.net.ssl.*;
    import java.net.*;
    import java.io.*;
     
    public class SecureClient 
    {
       private static final String host = "127.0.0.1";
       private static int port = 4321;
     
       public static void main(String []args) throws Exception {
          SSLSocketFactory sf = (SSLSocketFactory) SSLSocketFactory.getDefault();
          Socket s = sf.createSocket(host, port); 
     
          DataInputStream dis = new DataInputStream(s.getInputStream());
          DataOutputStream dos = new DataOutputStream(s.getOutputStream());
     
          System.out.println("Connected.n");
          System.out.println("Type messages to send to server, exit to end!");
    
          BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 
          String line;
          while ((line = br.readLine()) != null) {
             if (line.equals("exit")) break;
     
             dos.writeUTF(line);
             dos.flush();
     
             String reply = dis.readUTF();
             System.out.println("Server reply: " + reply);
          }
     
          dis.close();
          dos.close(); 
          s.close();
       }
    }
    

    Note: change the IP address (127.0.0.1) in SecureClient.java to the IP address where your server is deployed. To test it, we’ll run it all on the same machine.

    The file .keystore containing the self-signed certificate should be located on the server’s machine. Put both the files SecureServer.class and .keystore in the directory c:\security\ssl\server and SecureClient.class in c:\securitysslclient.
    The server should know where it can find its private key. The keystore can be specified at runtime along with the password in order to access it.
    The client needs to have access to the certificate in order to check it. Certain certificates, issued by a Certificate Authority (CA) are automatically trusted (they are saved in the file /lib/security/cacerts. You can export the certificate from the server’s .keystore into a file and specify its location at the command line when running the client (trustStore). If your key is signed by a CA, then you don’t need to specify it at command line as it is contained in the file /lib/security/cacerts. This is the order in which trustStores are checked:

       1) a trustStore specified in the javax.net.ssl.trustStore property
       2) <i><JRE_HOME>/lib/security/jssecacerts</i>
       3) <i><JRE_HOME>/lib/security/cacerst</i>
    

    I’ll show you how to run the example by specifying the trustStore at command line, and by adding the certificate to the cacerts file.

    First we need to export the certificate:

    C:\security\ssl\server> keytool -export -keystore .keystore -file ssltest.cer
    Enter keystore password:  esuspass
    Certificate stored in file <ssltest.cer>
    

    Running SecureServer and SecureClient by specifying the trustStore

    On the client side, create a truststore .trustStore and import certificate contained in the file ssltest.cer:

    C:\security\ssl\client>copy ..\server ssltest.cer
            1 file(s) copied
    
    C:\security\ssl\client>keytool -import -keystore .trustStore -file ssltest.cer
    Enter keystore password:  esuspass
    Owner: CN=Joris Van den Bogaert, OU=Esus Team, O="Esus, Inc", L=Meerbeek, ST=Unk
    nown, C=BE
    Issuer: CN=Joris Van den Bogaert, OU=Esus Team, O="Esus, Inc", L=Meerbeek, ST=Un
    known, C=BE
    Serial number: 3b5d7dd7
    Valid from: Tue Jul 24 15:53:27 CEST 2001 until: Mon Oct 22 15:53:27 CEST 2001
    Certificate fingerprints:
             MD5:  07:DE:52:94:15:1D:53:88:C5:C5:51:48:0F:AF:83:76
             SHA1: A7:92:CF:29:EF:B9:74:05:6F:05:E5:75:4B:78:42:D1:35:8B:D7:55
    Trust this certificate? [no]:  yes
    Certificate was added to keystore
    

    Open another terminal to start running the server:

    C:\security\ssl\server> c:\jdk1.4\bin\java -Djavax.net.ssl.keyStore=.keystore -Dj
    avax.net.ssl.keyStorePassword=esuspass SecureServer
    

    Now wait a couple seconds until the server shows up Ready to accept messages!.

    Run the client by specifying the trustStore:

    C:\security\ssl\client>c:\jdk1.4\bin\java -Djavax.net.ssl.trustStore=.trustStore SecureClient
    

    This is the result at the server’s side:

    C:\security\ssl\server> c:\jdk1.4\bin\java -Djavax.net.ssl.keyStore=.keystore -Djavax.net.ssl.keyStorePassword=esuspass SecureServer
    Ready to accept messages!
    A client has connected!
    Client sent: whoever spies on this connection
    Client sent: can't see crap
    Client sent: or should i say... only crap
    EOF!

    and the result at the client’s side:

    C:\security\ssl\client> c:\jdk1.4\bin\java -Djavax.net.ssl.trustStore=.trustStore SecureClient
    Connected.
    
    Type messages to send to server, exit to end!
    whoever spies on this connection
    Server reply: You send: whoever spies on this connection
    can't see crap
    Server reply: You send: can't see crap
    or should i say... only crap
    Server reply: You send: or should i say... only crap
    exit
    

    To include our self-signed certificate in the default cacerts file:

    C:\jdk1.4\jre\lib\security> copy c:\securitysslserver ssltest.cer
            1 file(s) copied
     
    C:\jdk1.4\jre\lib\security> keytool -import -keystore cacerts -file ssltest.cer
    Enter keystore password:  changeit
    Owner: CN=Joris Van den Bogaert, OU=Esus Team, O="Esus, Inc", L=Meerbeek, ST=Unk
    nown, C=BE
    Issuer: CN=Joris Van den Bogaert, OU=Esus Team, O="Esus, Inc", L=Meerbeek, ST=Un
    known, C=BE
    Serial number: 3b5d7dd7
    Valid from: Tue Jul 24 15:53:27 CEST 2001 until: Mon Oct 22 15:53:27 CEST 2001
    Certificate fingerprints:
             MD5:  07:DE:52:94:15:1D:53:88:C5:C5:51:48:0F:AF:83:76
             SHA1: A7:92:CF:29:EF:B9:74:05:6F:05:E5:75:4B:78:42:D1:35:8B:D7:55
    Trust this certificate? [no]:  yes
    

    Run the server again. Now run the client without specifying the trustStore and see what happens!