Zach Goethel
Zach Goethel

Reputation: 131

HTTPS Socket "javax.net.ssl.SSLHandshakeException: no cipher suites in common"

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)

Upvotes: 3

Views: 6338

Answers (1)

maxjar10
maxjar10

Reputation: 216

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.

Upvotes: 6

Related Questions