2

I am trying to apply security to a simple chat application with Java SSLSockets.

I have created a self-signed CA and with it I signed two certificates (all used RSA keys), one for the server, and one for a client. After that, I imported the certificates to a keystore for the server and to another for the client.

CA : openssl genrsa -out ca.key 1024 -rsa openssl req -new -key ca.key -out ca.csr openssl x509 -req -days 365 -in ca.csr -out ca.crt -signkey ca.key SERVER CERTIFICATE: openssl genrsa -out server.key 1024 -rsa openssl req -new -key server.key -out server.csr openssl ca -in server.csr -cert ca.crt -keyfile ca.key -out server.crt CLIENT CERTIFICATE : openssl genrsa -out client.key 1024 -rsa openssl req -new -key client.key -out client.csr openssl ca -in client.csr -cert ca.crt -keyfile ca.key -out client.crt KEYSTORES: keytool -import -keystore serverkeystore -file ca.crt -alias theCARoot keytool -import -keystore serverkeystore -file server.crt -alias servercrt keytool -import -keystore serverkeystore -file client.crt -alias clientcrt keytool -import -keystore clientkeystore -file ca.crt -alias theCARoot keytool -import -keystore clientkeystore -file server.crt -alias servercrt keytool -import -keystore clientkeystore -file client.crt -alias clientcrt 

I want to use a specific cipher, but apparently none of the supported ciphers works.

My code for the client :

 import java.net.*; import java.io.*; import java.security.*; import java.security.cert.CertificateException; import javax.net.ssl.*; public class ChatClient implements Runnable { private SSLSocket socket = null; private Thread thread = null; private DataInputStream console = null; private DataOutputStream streamOut = null; private ChatClientThread client = null; final String[] enabledCipherSuites = {"TLS_RSA_WITH_AES_256_CBC_SHA256"}; final char[] passphrase = "123456".toCharArray(); public ChatClient(String serverName, int serverPort) { System.out.println("Establishing connection to server..."); try { SSLSocketFactory factory = null; SSLContext ctx = SSLContext.getInstance("TLS"); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); KeyStore ks= KeyStore.getInstance("JKS"); ks.load(new FileInputStream("clientkeystore"), passphrase); kmf.init(ks, passphrase); KeyStore serverKey = KeyStore.getInstance("JKS"); serverKey.load(new FileInputStream("serverkeystore"),passphrase); TrustManagerFactory trustManager = TrustManagerFactory.getInstance("SunX509"); trustManager.init(serverKey); ctx.init(kmf.getKeyManagers(), trustManager.getTrustManagers(), null); factory = ctx.getSocketFactory(); socket = (SSLSocket)factory.createSocket(serverName, serverPort); socket.setEnabledCipherSuites(enabledCipherSuites); start(); } catch(UnknownHostException uhe) { // Host unkwnown System.out.println("Error establishing connection - host unknown: " + uhe.getMessage()); } catch(IOException ioexception) { // Other error establishing connection System.out.println("Error establishing connection - unexpected exception: " + ioexception.getMessage()); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (KeyStoreException e) { e.printStackTrace(); } catch (CertificateException e) { e.printStackTrace(); } catch (UnrecoverableKeyException e) { e.printStackTrace(); } catch (KeyManagementException e) { e.printStackTrace(); } } public void run() { while (thread != null) { try { // Sends message from console to server streamOut.writeUTF(console.readLine()); streamOut.flush(); } catch(IOException ioexception) { System.out.println("Error sending string to server: " + ioexception.getMessage()); stop(); } } } public void handle(String msg) { // Receives message from server if (msg.equals(".quit")) { // Leaving, quit command System.out.println("Exiting...Please press RETURN to exit ..."); stop(); } else // else, writes message received from server to console System.out.println(msg); } // Inits new client thread public void start() throws IOException { console = new DataInputStream(System.in); streamOut = new DataOutputStream(socket.getOutputStream()); if (thread == null) { client = new ChatClientThread(this, socket); thread = new Thread(this); thread.start(); } } // Stops client thread public void stop() { if (thread != null) { thread.stop(); thread = null; } try { if (console != null) console.close(); if (streamOut != null) streamOut.close(); if (socket != null) socket.close(); } catch(IOException ioe) { System.out.println("Error closing thread..."); } client.close(); client.stop(); } public static void main(String args[]) { ChatClient client = null; if (args.length != 2) // Displays correct usage syntax on stdout System.out.println("Usage: java ChatClient host port"); else // Calls new client client = new ChatClient(args[0], Integer.parseInt(args[1])); } } class ChatClientThread extends Thread { private SSLSocket socket = null; private ChatClient client = null; private DataInputStream streamIn = null; public ChatClientThread(ChatClient _client, SSLSocket _socket) { client = _client; socket = _socket; open(); start(); } public void open() { try { streamIn = new DataInputStream(socket.getInputStream()); } catch(IOException ioe) { System.out.println("Error getting input stream: " + ioe); client.stop(); } } public void close() { try { if (streamIn != null) streamIn.close(); } catch(IOException ioe) { System.out.println("Error closing input stream: " + ioe); } } public void run() { while (true) { try { client.handle(streamIn.readUTF()); } catch(IOException ioe) { System.out.println("Listening error: " + ioe.getMessage()); client.stop(); } } } } 

And for the server :

import java.net.*; import java.io.*; import java.security.*; import java.security.cert.CertificateException; import java.util.Arrays; import javax.net.ServerSocketFactory; import javax.net.ssl.*; public class ChatServer implements Runnable { private ChatServerThread clients[] = new ChatServerThread[20]; private SSLServerSocket server_socket = null; private Thread thread = null; private int clientCount = 0; final String[] enabledCipherSuites = {"TLS_RSA_WITH_AES_256_CBC_SHA256"}; final char[] passphrase = "123456".toCharArray(); public ChatServer(int port) { try { // Binds to port and starts server System.out.println("Binding to port " + port); SSLContext ctx = SSLContext.getInstance("TLS");; KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); KeyStore ks = KeyStore.getInstance("JKS"); ks.load(new FileInputStream("serverkeystore"), passphrase); kmf.init(ks, passphrase); KeyStore serverKey = KeyStore.getInstance("JKS"); serverKey.load(new FileInputStream("clientkeystore"),passphrase); TrustManagerFactory trustManager = TrustManagerFactory.getInstance("SunX509"); trustManager.init(serverKey); ctx.init(kmf.getKeyManagers(), trustManager.getTrustManagers(), null); SSLServerSocketFactory ssf = ctx.getServerSocketFactory(); server_socket = (SSLServerSocket) ssf.createServerSocket(port); server_socket.setEnabledCipherSuites(enabledCipherSuites); server_socket.setNeedClientAuth(true); System.out.println("Server started: " + server_socket); start(); } catch(IOException ioexception) { // Error binding to port System.out.println("Binding error (port=" + port + "): " + ioexception.getMessage()); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (KeyStoreException e) { e.printStackTrace(); } catch (CertificateException e) { e.printStackTrace(); } catch (UnrecoverableKeyException e) { e.printStackTrace(); } catch (KeyManagementException e) { e.printStackTrace(); } } public void run() { while (thread != null) { try { // Adds new thread for new client System.out.println("Waiting for a client ..."); addThread((SSLSocket)server_socket.accept()); } catch(IOException ioexception) { System.out.println("Accept error: " + ioexception); stop(); } } } public void start() { if (thread == null) { // Starts new thread for client thread = new Thread(this); thread.start(); } } public void stop() { if (thread != null) { // Stops running thread for client thread.stop(); thread = null; } } private int findClient(int ID) { // Returns client from id for (int i = 0; i < clientCount; i++) if (clients[i].getID() == ID) return i; return -1; } public synchronized void handle(int ID, String input) { if (input.equals(".quit")) { int leaving_id = findClient(ID); // Client exits clients[leaving_id].send(".quit"); // Notify remaing users for (int i = 0; i < clientCount; i++) if (i!=leaving_id) clients[i].send("Client " +ID + " exits.."); remove(ID); } else // Brodcast message for every other client online for (int i = 0; i < clientCount; i++) clients[i].send(ID + ": " + input); } public synchronized void remove(int ID) { int pos = findClient(ID); if (pos >= 0) { // Removes thread for exiting client ChatServerThread toTerminate = clients[pos]; System.out.println("Removing client thread " + ID + " at " + pos); if (pos < clientCount-1) for (int i = pos+1; i < clientCount; i++) clients[i-1] = clients[i]; clientCount--; try { toTerminate.close(); } catch(IOException ioe) { System.out.println("Error closing thread: " + ioe); } toTerminate.stop(); } } private void addThread(SSLSocket socket) { if (clientCount < clients.length) { // Adds thread for new accepted client System.out.println("Client accepted: " + socket); clients[clientCount] = new ChatServerThread(this, socket); try { clients[clientCount].open(); clients[clientCount].start(); clientCount++; } catch(IOException ioe) { System.out.println("Error opening thread: " + ioe); } } else System.out.println("Client refused: maximum " + clients.length + " reached."); } public static void main(String args[]) { ChatServer server = null; if (args.length != 1) // Displays correct usage for server System.out.println("Usage: java ChatServer port"); else // Calls new server server = new ChatServer(Integer.parseInt(args[0])); } } class ChatServerThread extends Thread { private ChatServer server = null; private SSLSocket socket = null; private int ID = -1; private DataInputStream streamIn = null; private DataOutputStream streamOut = null; public ChatServerThread(ChatServer _server, SSLSocket _socket) { super(); server = _server; socket = _socket; ID = socket.getPort(); } // Sends message to client public void send(String msg) { try { streamOut.writeUTF(msg); streamOut.flush(); } catch(IOException ioexception) { System.out.println(ID + " ERROR sending message: " + ioexception.getMessage()); server.remove(ID); stop(); } } // Gets id for client public int getID() { return ID; } // Runs thread public void run() { System.out.println("Server Thread " + ID + " running."); while (true) { try { server.handle(ID, streamIn.readUTF()); } catch(IOException ioe) { System.out.println(ID + " ERROR reading: " + ioe.getMessage()); server.remove(ID); stop(); } } } // Opens thread public void open() throws IOException { streamIn = new DataInputStream(new BufferedInputStream(socket.getInputStream())); streamOut = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream())); } // Closes thread public void close() throws IOException { if (socket != null) socket.close(); if (streamIn != null) streamIn.close(); if (streamOut != null) streamOut.close(); } } 

Sorry if my english is a bit rusty.

My OS is OS X El Capitan and Java version is 1.8.

Here is the server's stack trace: /Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/bin/java -Djavax.net.debug=all -Didea.launcher.port=7536 "-Didea.launcher.bin.path=/Applications/IntelliJ IDEA 15.app/Contents/bin" -Dfile.encoding=UTF-8 -classpath "/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/lib/tools.jar:/Users/joaogoncalves/Dropbox/STI/out/production/STI Assignment 3:/Applications/IntelliJ IDEA 15.app/Contents/lib/idea_rt.jar" com.intellij.rt.execution.application.AppMain ChatServer 5000 Binding to port 5000 adding as trusted cert: Subject: CN=Joao Goncalves, OU=DEQ, O=UC, ST=Coimbra, C=PT Issuer: CN=DEI, OU=DEI, O=UC, L=Coimbra, ST=Coimbra, C=PT Algorithm: RSA; Serial number: 0xc94895f3863a5c36 Valid from Mon May 23 23:43:42 WEST 2016 until Tue May 23 23:43:42 WEST 2017 adding as trusted cert: Subject: CN=www.uc.pt, OU=DEM, O=UC, ST=Coimbra, C=PT Issuer: CN=DEI, OU=DEI, O=UC, L=Coimbra, ST=Coimbra, C=PT Algorithm: RSA; Serial number: 0xc94895f3863a5c35 Valid from Mon May 23 23:42:54 WEST 2016 until Tue May 23 23:42:54 WEST 2017 adding as trusted cert: Subject: CN=DEI, OU=DEI, O=UC, L=Coimbra, ST=Coimbra, C=PT Issuer: CN=DEI, OU=DEI, O=UC, L=Coimbra, ST=Coimbra, C=PT Algorithm: RSA; Serial number: 0xdb931da4e1abec22 Valid from Mon May 23 23:42:03 WEST 2016 until Tue May 23 23:42:03 WEST 2017 trigger seeding of SecureRandom done seeding SecureRandom Server started: [SSL: ServerSocket[addr=0.0.0.0/0.0.0.0,localport=5000]] Waiting for a client ... Allow unsafe renegotiation: false Allow legacy hello messages: true Is initial handshake: true Is secure renegotiation: false Client accepted: 74ce57fc[SSL_NULL_WITH_NULL_NULL: Socket[addr=/127.0.0.1,port=57519,localport=5000]] Waiting for a client ... Server Thread 57519 running. Ignoring unsupported cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA256 for TLSv1 No available cipher suite for TLSv1 Ignoring unsupported cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA256 for TLSv1.1 No available cipher suite for TLSv1.1 [Raw read]: length = 5 0000: 16 03 03 00 52 ....R [Raw read]: length = 82 0000: 01 00 00 4E 03 03 57 44 7B 3B B8 1E 77 88 AF 4E ...N..WD.;..w..N 0010: C7 CA 73 CE AC 38 62 5D 18 BD 9A 16 7E 25 86 25 ..s..8b].....%.% 0020: 36 1C EF F5 B6 FF 00 00 02 00 3D 01 00 00 23 00 6.........=...#. 0030: 0D 00 1A 00 18 06 03 06 01 05 03 05 01 04 03 04 ................ 0040: 01 03 03 03 01 02 03 02 01 02 02 01 01 FF 01 00 ................ 0050: 01 00 .. Thread-1, READ: TLSv1.2 Handshake, length = 82 *** ClientHello, TLSv1.2 RandomCookie: GMT: 1464105787 bytes = { 184, 30, 119, 136, 175, 78, 199, 202, 115, 206, 172, 56, 98, 93, 24, 189, 154, 22, 126, 37, 134, 37, 54, 28, 239, 245, 182, 255 } Session ID: {} Cipher Suites: [TLS_RSA_WITH_AES_256_CBC_SHA256] Compression Methods: { 0 } Extension signature_algorithms, signature_algorithms: SHA512withECDSA, SHA512withRSA, SHA384withECDSA, SHA384withRSA, SHA256withECDSA, SHA256withRSA, SHA224withECDSA, SHA224withRSA, SHA1withECDSA, SHA1withRSA, SHA1withDSA, MD5withRSA Extension renegotiation_info, renegotiated_connection: <empty> *** [read] MD5 and SHA1 hashes: len = 82 0000: 01 00 00 4E 03 03 57 44 7B 3B B8 1E 77 88 AF 4E ...N..WD.;..w..N 0010: C7 CA 73 CE AC 38 62 5D 18 BD 9A 16 7E 25 86 25 ..s..8b].....%.% 0020: 36 1C EF F5 B6 FF 00 00 02 00 3D 01 00 00 23 00 6.........=...#. 0030: 0D 00 1A 00 18 06 03 06 01 05 03 05 01 04 03 04 ................ 0040: 01 03 03 03 01 02 03 02 01 02 02 01 01 FF 01 00 ................ 0050: 01 00 .. %% Initialized: [Session-1, SSL_NULL_WITH_NULL_NULL] %% Invalidated: [Session-1, SSL_NULL_WITH_NULL_NULL] Thread-1, SEND TLSv1.2 ALERT: fatal, description = handshake_failure Thread-1, WRITE: TLSv1.2 Alert, length = 2 [Raw write]: length = 7 0000: 15 03 03 00 02 02 28 ......( Thread-1, called closeSocket() Thread-1, handling exception: javax.net.ssl.SSLHandshakeException: no cipher suites in common 57519 ERROR reading: no cipher suites in common Removing client thread 57519 at 0 Thread-1, called close() Thread-1, called closeInternal(true) Thread-1, called close() Thread-1, called closeInternal(true) Thread-1, called close() Thread-1, called closeInternal(true) Process finished with exit code 130 
2
  • It would help if you could post the entire stack trace so we get an idea about where in the code the error occurs. Commented May 24, 2016 at 13:35
  • I have already updated. Thanks in advance Commented May 24, 2016 at 15:40

2 Answers 2

9

The keystore for each authenticated party, always the server and here the client also because you specified NeedClientAuth, must have the PRIVATE KEY AND certificate(s), not merely the certificate(s). There are two ways to do this:

The latter method (separate entries for ca.crt and privatekey+server.crt) has the advantage this same file can be used as both the keystore and truststore, you don't need to use serverkey as clienttrust and vice versa. If these were real systems, this would be a security benefit.

A final note: you should start using RSA 2048-bit keys. 1024-bit has been prohibited by authorities like NIST and CABforum since 2014, and although Java still accepts them, most browsers and many other tools are already warning for them and likely soon will reject them. For similar reasons you should sign the certificates with at least SHA256 -- this can be set in the config file used by ca or you can just use the commandline flag -md sha256.

Sign up to request clarification or add additional context in comments.

3 Comments

I have both keys and the certificate in the JKS container but encounter the SSLHandshakeException when using a custom X.509 key manager. With the default Java key manager the handshake works. Who decides which cipher suites are in common and which not? Maybe I can change this part of the code...
@MarkusL that sounds like a bug in your keymanager; you might want to ask a Q about that. But: server side ciphersuite negotiation is driven by sun.security.ssl.ServerHandshaker which calls several other things. If you change it to 'accept' a suite for which your keymanager doesn't provide a suitable key&cert, it won't be able to carry out the rest of the handshake and everything will crash. You'll have to design a new protocol that doesn't need key&cert, replace all the serverside code with your new protocol, and replace all the clients also, and it likely won't be secure.
My key manager is fairly simple, I'm looking for the bug for a few days now, any help is appreciated: stackoverflow.com/q/39996178/2878556. Thanks for the hint about ServerHandshaker - but I don't really want to change that.
1

I have never seen the KeyManagerFactory declared the way you have, I typically see it like this:

KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 

I realize that I missed this second link for you:

http://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/JSSERefGuide.html#Debug

You will need to walk through SSL debug. However, you need to explicitly enable certain ciphers (if I recall correctly) and would recommend that path.

4 Comments

Apparently, that change had no effect
I changed the stacktrace with debug = all, and the enabled cipher suites to "TLS_RSA_WITH_AES_256_CBC_SHA256". The error is still the same. I updated the stack trace above
@jprg update TrustManagerFactory in the same way please :)
@jprg if my solution was correct please mark it as so :)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.