5

I am trying to open an SSLServerSocket with custom keystore/truststore and with only TLSv1.2 enabled. Here is my related code for opening such socket:

SSLContext sslContext = null;
ServerSocket serverSocket = null;
KeyManagerFactory kmf = null;
KeyStore keystore = loadKeyStore(KEYSTORE_FILE);
if (keystore == null) {
    // throw exception
}
char[] psw = System.console().readPassword("Enter password for the key materials in file \"%s\":", KEYSTORE_FILE);
try {
    kmf = KeyManagerFactory.getInstance("PKIX");
    kmf.init(keystore, psw);
} catch (NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException e) {
    e.printStackTrace();
    kmf = null;
    // throw exception
}
try {
    sslContext = SSLContext.getInstance("TLSv1.2");
    System.out.println(kmf==null); // prints false
    sslContext.init(kmf==null?null:kmf.getKeyManagers(), null, null);
} catch (NoSuchAlgorithmException | KeyManagementException e) {
    // throw exception
}

try {
    serverSocket = sslContext.getServerSocketFactory().createServerSocket(PORT, BACKLOG, HOST);
    ((SSLServerSocket)serverSocket).setEnabledProtocols(new String[]{"TLSv1.2"});
} catch (IOException e) {
    // throw exception
}

the loadKeyStore function is,

private static KeyStore loadKeyStore(String filename) {
    KeyStore keystore = null;
    FileInputStream fis = null;
    try {
        keystore = KeyStore.getInstance("JKS");
        char[] psw = System.console().readPassword("Enter password for the KeyStore file \"%s\":", filename);
        if (psw != null) {
            fis = new FileInputStream(filename);
            keystore.load(fis, psw);
        }
    } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
        keystore = null;
        LogManager.getLogger().fatal("cannot load KeyStore from file \"" + filename + "\".", e);
    } finally {
        if (fis != null) {
            try {
                fis.close();
            } catch (IOException e) {
                LogManager.getLogger().error("cannot close file " + filename, e);
            }
            fis = null;
        }
    }
    return keystore;
}

I accept connections in a different thread as

while (!stopped) {
    Socket socket = null;
    try {
        socket = serverSocket.accept();
    } catch (IOException e) {
        if (!stopped) {
            logger.error("exception while accepting connections.", e);
        }
        break;
    }
    // start new threads to handle this connection
}

The problem is, when I enter https://HOST:PORT at Firefox, it says:

Firefox cannot guarantee the safety of your data on HOST because it uses SSLv3, a broken security protocol. Advanced info: ssl_error_no_cypher_overlap

How can I open a server socket that accepts only TLSv1.2 connections?

P.S. I have tried changing "TLSv1.2" strings in the code to "TLS", one by one, but nothing changed.

EDIT: I edited the code as follows:

serverSocket = sslContext.getServerSocketFactory().createServerSocket(port, backlog, host);
((SSLServerSocket)serverSocket).setEnabledProtocols(new String[]{"TLSv1.2"});
for (String s: ((SSLServerSocket)serverSocket).getEnabledCipherSuites()) {
    System.out.println(s);
}

and the output is,

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_ECDHE_ECDSA_WITH_RC4_128_SHA TLS_ECDHE_RSA_WITH_RC4_128_SHA SSL_RSA_WITH_RC4_128_SHA TLS_ECDH_ECDSA_WITH_RC4_128_SHA TLS_ECDH_RSA_WITH_RC4_128_SHA SSL_RSA_WITH_RC4_128_MD5 TLS_EMPTY_RENEGOTIATION_INFO_SCSV

I am not sure, but it seems the problem is not about missing enabled cipher suites. Right?

EDIT2: I have tried openssl s_client -connect HOST:PORT, and the result is output

Ramazan
  • 989
  • 3
  • 14
  • 26
  • Which version of Java exactly are you using? In the latest update (Java 8 update 31) SSLv3 is disabled by default (see [release notes Java 8u31](http://www.oracle.com/technetwork/java/javase/8u31-relnotes-2389094.html)). – Jesper Feb 26 '15 at 13:23
  • Maybe you shouls explicit set enabled cipher suites? For example ` TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256` – user1516873 Feb 26 '15 at 13:39
  • @Jesper 1.8.0_25. Even if SSLv3 is supported by JRE, shouldn't it use TLS, as I configure accordingly? – Ramazan Feb 26 '15 at 13:40
  • It looks like you set protocol, but not set ciphersuites, so firefox cannot negotiate cipersuite on handshake and fallback to SSLv3. You can check your server with openssl s_client? – user1516873 Feb 26 '15 at 13:45
  • @user1516873 I have checked enabled suites, see edit. – Ramazan Feb 26 '15 at 14:10
  • can you connect to host with openssl and check ? `openssl s_client -connect host:port` also, if your host is available in Internet, you can check it with https://www.ssllabs.com/ – user1516873 Feb 26 '15 at 14:42
  • Make sure the version of openssl you are using is 1.0.1 or higher. Anything lower does not support TLSv1.2 or even 1.1. To check run `openssl version`. – dave_thompson_085 Feb 26 '15 at 16:39
  • @dave_thompson_085 openssl's version is 1.0.1f – Ramazan Feb 26 '15 at 16:55
  • Then I'm not sure why s_client handshake is failing. Does the server take the exception/logger.error branch and if so (exactly) what is logged? – dave_thompson_085 Feb 26 '15 at 17:54
  • I would not deal with specifying TLSv1.2 as a context. It is just not very well defined. I would request the default SSLContect instead and use setProtocol to enable the protocols I want. This works better with different providers. – eckes Feb 26 '15 at 18:21
  • @dave_thompson_085 no exception occurs at server side. – Ramazan Feb 27 '15 at 17:03

1 Answers1

9

(There's a very similar question which I've answered here.)

Essentially, SSLContext.getInstance("TLSv1.2") can return an instance that supports other protocols too.

If you want to use a specific set of protocols, you need to use setEnabledProtocols(...), which is what you've done following your first edit. You now get some cipher suites with a name starting with SSL_, but that's just the name, these are still valid for TLS 1.2. As the Java Cryptography Architecture Standard Algorithm Name Documentation for JDK 8 says:

Some JSSE cipher suite names were defined before TLSv1.0 was finalized, and were therefore given the SSL_ prefix. The names mentioned in the TLS RFCs prefixed with TLS_ are functionally equivalent to the JSSE cipher suites prefixed with SSL_.

Your last problem ("no peer certificate available", along with the handshake failure) seems to suggest there is no certificate (with its private key) found in the keystore you're trying to use.

Indeed, although the cipher suites you're mentioning are enabled, they will be automatically disabled if they cannot be used. All are either RSA or DSS cipher suites, meaning they need a certificate with an RSA or DSA key with its private key to be usable. If such a certificate with private key entry cannot be found in the keystore, it won't be usable by the KeyManager and SSLContext. Hence, they will be disabled when the handshake is actually attempted. This would typically result in an exception being thrown in the middle of the handshake on the server side ("javax.net.ssl.SSLHandshakeException: no cipher suites in common"), and the error messages you get on the client side via OpenSSL.

Community
  • 1
  • 1
Bruno
  • 119,590
  • 31
  • 270
  • 376
  • The "no peer cert" from openssl s_client is because the handshake failed before the Server Cert message; similarly "Cipher (NONE)" means there was no ServerHello specifying the negotiated cipher. s_client will authenticate i.e. *send* a cert *only* if you explicitly specify `-cert` and (unless combined) `-key`; if not and the server asks with CertReq s_client just sends an empty list meaning "yes, I have no bananas^Wcert" as per rfc5246 7.4.6. – dave_thompson_085 Feb 26 '15 at 17:50
  • 1
    @dave_thompson_085 Indeed, but that comes from the fact the handshake would have aborted with "`javax.net.ssl.SSLHandshakeException: no cipher suites in common`" on the server side, due to all RSA/DSS cipher suites being disabled, due to a missing server cert (and private key). (More details [here](http://stackoverflow.com/a/15406581/372643) and [here](http://stackoverflow.com/a/15144731/372643).) – Bruno Feb 26 '15 at 18:12
  • Oops. You're right; somehow I thought you said client side (and responded to that) but you didn't. As a small correction, the OP's list does include some ECDSA so that key+cert also would work, but you're very right at least one is needed. And knowing what error occurs on the server would very likely help. (JSSE does support anonymous suites that don't need any key+cert, but they are disabled by default, and quite rightly so.) – dave_thompson_085 Feb 26 '15 at 20:37
  • Although the corresponding cert+key is located in the keystore, you guided me to the right direction: I was entering the keystore password when it prompts, and was entering empty string when it asks for privatekey, since I did not enter a seperate password for the privatekey while generating it via `keytool`. When I enter the keystore password, again, as the privatekey password, now it is able to find the correct keypair. I can see the certificate with `openssl s_client` now. Thanks for the hint. – Ramazan Feb 27 '15 at 17:03