3

I am attempting to create a secure web server for a web console, and currently I am having problems. This is my code for starting an HTTPS server:

public void startServer()
{
    try
    {
        SSLServerSocketFactory ssf = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
        SSLServerSocket ss = (SSLServerSocket) ssf.createServerSocket(8080);
        for (String s : ss.getEnabledCipherSuites())
        {
            logger.info(s);
        }

        while (true)
        {
            Socket s = ss.accept();
            OutputStream out = s.getOutputStream();
            BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));

            String line = null;
            while (((line = in.readLine()) != null) && (!("".equals(line))))
            {
                System.out.println(line);
            }
            StringBuffer buffer = new StringBuffer();
            buffer.append("<HTML><HEAD><TITLE>HTTPS Server</TITLE></HEAD>\n");
            buffer.append("<BODY>\n<H1>Success!</H1></BODY></HTML>\n");

            String string = buffer.toString();
            byte[] data = string.getBytes();
            out.write("HTTP/1.0 200 OK\n".getBytes());
            out.write(new String("Content-Length: " + data.length + "\n").getBytes());
            out.write("Content-Type: text/html\n\n".getBytes());
            out.write(data);
            out.flush();

            out.close();
            in.close();
            s.close();
        }
    }
    catch (Throwable thrown)
    {
        logger.error(thrown);
    }
}

This prints out the following results as enabled cipher suites:

TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
TLS_RSA_WITH_AES_128_CBC_SHA256
TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256
TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256
TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
TLS_RSA_WITH_AES_128_CBC_SHA
TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA
TLS_ECDH_RSA_WITH_AES_128_CBC_SHA
TLS_DHE_RSA_WITH_AES_128_CBC_SHA
TLS_DHE_DSS_WITH_AES_128_CBC_SHA
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
TLS_RSA_WITH_AES_128_GCM_SHA256
TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256
TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256
TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
TLS_DHE_DSS_WITH_AES_128_GCM_SHA256
TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
SSL_RSA_WITH_3DES_EDE_CBC_SHA
TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA
TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA
SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA
SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA
TLS_EMPTY_RENEGOTIATION_INFO_SCSV

And, when I try to connect to https://localhost:8080/ from my web browser (Chrome), I get the following exception:

javax.net.ssl.SSLHandshakeException: no cipher suites in common
    at sun.security.ssl.Alerts.getSSLException(Unknown Source)
    at sun.security.ssl.SSLSocketImpl.fatal(Unknown Source)
    at sun.security.ssl.Handshaker.fatalSE(Unknown Source)
    at sun.security.ssl.Handshaker.fatalSE(Unknown Source)
    at sun.security.ssl.ServerHandshaker.chooseCipherSuite(Unknown Source)
    at sun.security.ssl.ServerHandshaker.clientHello(Unknown Source)
    at sun.security.ssl.ServerHandshaker.processMessage(Unknown Source)
    at sun.security.ssl.Handshaker.processLoop(Unknown Source)
    at sun.security.ssl.Handshaker.process_record(Unknown Source)
    at sun.security.ssl.SSLSocketImpl.readRecord(Unknown Source)
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(Unknown Source)
    at sun.security.ssl.SSLSocketImpl.readDataRecord(Unknown Source)
    at sun.security.ssl.AppInputStream.read(Unknown Source)
    at sun.nio.cs.StreamDecoder.readBytes(Unknown Source)
    at sun.nio.cs.StreamDecoder.implRead(Unknown Source)
    at sun.nio.cs.StreamDecoder.read(Unknown Source)
    at java.io.InputStreamReader.read(Unknown Source)
    at java.io.BufferedReader.fill(Unknown Source)
    at java.io.BufferedReader.readLine(Unknown Source)
    at java.io.BufferedReader.readLine(Unknown Source)
    at net.jibini.inventory.http.InventoryHttp.startServer(InventoryHttp.java:54)
    at net.jibini.inventory.http.InventoryHttp.start(InventoryHttp.java:33)
    at net.jibini.inventory.server.InventoryServer$1.run(InventoryServer.java:47)
    at java.lang.Thread.run(Unknown Source)
Zach Goethel
  • 131
  • 2
  • 10
  • Can you find out which cipher suites Chrome supports? – user253751 Sep 09 '15 at 23:43
  • 1
    for server VM, use `-Djavax.net.debug=ssl`, and see what's happening. – ZhongYu Sep 09 '15 at 23:44
  • check this question: http://stackoverflow.com/questions/15076820/java-sslhandshakeexception-no-cipher-suites-in-common#15144731 also, where are you specifying the certificate store for your self-signed certificate? do you have it in the system store? – morgano Sep 10 '15 at 00:07
  • Output from `-Djavax.net.debug=ssl`: http://pastebin.com/XicjfPXE – Zach Goethel Sep 10 '15 at 00:14
  • Sound like you missed set the keystore, you need to download the cert file from your CA, import it into the jks file and specify it before you try to have any ssl context –  Sep 10 '15 at 03:22

1 Answers1

6

As others have mentioned using the -Djavax.net.debug=ssl will allow you to diagnose what is actually happening at a network level. The problem in this case is likely that you don't have a keystore specified so you are going to end up with no certificate/key so you won't be able to negotiate a cipher suite (e.g. TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA would require a DSA key).

So generate a key:

keytool -genkey -keystore mySrvKeystore -keyalg RSA

Then this needs to be passed either as a system property (-Djavax.net.ssl.keyStore ...) or provided using set property calls:

System.setProperty("javax.net.ssl.keyStore","mySrvKeystore");
System.setProperty("javax.net.ssl.keyStorePassword","1234567");

You can see what cipher suites are being sent by the client in the -Djavax.net.debug=all as supported but your server won't support any of them without a key/cert and will return the rather cryptic message about no cipher suites being shared.

UPDATE

As a more complete example refer to the following:

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;

import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;

public class TestSSL
{    
    public static void main( String args[] )
    {
        try
        {
            System.setProperty("javax.net.ssl.keyStore","mySrvKeystore");
            System.setProperty("javax.net.ssl.keyStorePassword","1234567");
            SSLServerSocketFactory ssf = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
            SSLServerSocket ss = (SSLServerSocket) ssf.createServerSocket(9001);
            ss.setEnabledProtocols( new String[]{"TLSv1","TLSv1.1","TLSv1.2"} );
            for (String s : ss.getEnabledCipherSuites())
            {
                System.out.println( s );
            }

            while (true)
            {
                Socket s = ss.accept();

                OutputStream out = s.getOutputStream();
                BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));

                String line = null;
                while (((line = in.readLine()) != null) && (!("".equals(line))))
                {
                    System.out.println(line);
                }

                if( line != null )
                {
                    StringBuffer buffer = new StringBuffer();
                    buffer.append("<HTML><HEAD><TITLE>HTTPS Server</TITLE></HEAD>\n");
                    buffer.append("<BODY>\n<H1>Success!</H1></BODY></HTML>\n");

                    String string = buffer.toString();
                    byte[] data = string.getBytes();

                    out.write("HTTP/1.0 200 OK\n".getBytes());
                    out.write(new String("Content-Length: " + data.length + "\n").getBytes());
                    out.write("Content-Type: text/html\n\n".getBytes());
                    out.write(data);
                    out.flush();
                }

                out.close();
                in.close();
                s.close();
            }
        }
        catch (Throwable thrown)
        {
            thrown.printStackTrace();
        }
    }
}

Note the following key differences from your previously posted example:

  1. The two System.setProperty calls to reference the keystore and the keystore password. This was generated given the command I gave above. Nothing fancy about generating the self-signed certificate. It should be in the working directory from where java is being launched. Please note that browsers that you connect to this will show a warning about the certificate not being "trusted" so you'll need to ignore these if you are just playing around with this.
  2. I set the enabled protocols to restrict to TLS (SSL is dead and frankly TLSv1 and possibly TLSv1.1 are next from a PCI perspective).
  3. The if( line != null ) block may seem unnecessary but if you are testing with Chrome you will see that Chrome actually makes several "speculative" connections to your server when you pull that page and so your server will die if before it's able to serve up the page content since the first connection where it sources your certificate will kill it and the next connection will be refused.
maxjar10
  • 216
  • 1
  • 4
  • Where would I put the keystore? – Zach Goethel Sep 10 '15 at 01:27
  • This is another code snippet I tried with a file called "keystore" in the root src directory. http://pastebin.com/423TR9X1 I created the file with `keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000` – Zach Goethel Sep 10 '15 at 01:30
  • I didn't pull down your alternative snippet since your first snippet is in the actual question and wanted to be sure I addressed that one. Please see my update for a working example and further details. – maxjar10 Sep 10 '15 at 06:56
  • Nitpick: ECHDE_ECDSA keyexchange requires an **EC** key, and a cert that allows ECDSA. ECDSA and ECDH algorithms use the same key format, see `java.security.interfaces.EC{,Private,Public}Key`, which is significantly different from the key (and cert) for classic-DSA and for that matter classic-DH. But concur your main point that the server needs a keystore with a privatekey&cert(chain). – dave_thompson_085 Jun 19 '16 at 08:43
  • Have a look here to check out what cipher suites your browser supports: https://cc.dcsec.uni-hannover.de/. Also check out the JSSE documentation for your Java version to see what suites you support on the server side http://docs.oracle.com/javase/8/docs/technotes/guides/security/SunProviders.html - Cipher suites. – reim Jul 27 '16 at 07:36