I have an app which connects to a server via HTTPS.
The server in question has a weak certificate which utilises RC4 Cipher (default support for which was recently removed from the JDK https://www.java.com/en/download/faq/release_changes.xml)
So following upgrade of the JDK, I am seeing javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
The release notes specify that you should use SSLSocket/SSLEngine.setEnabledCipherSuites()
to specifically enable certain ciphers.
However, using HttpsUrlConnection, or Apache's CloseableHttpClient, I can only find how to specify the SslSocketFactory. Which doesn't seem to provide function .setEnabledCipherSuites.
Found this post: Why does SSLSocketFactory lack setEnabledCipherSuites?
My question is:
Is there a way to get hold of the SSLEngine/Socket on an outbound client HTTP request so I can set the cipher suites before the handshake?
Thanks in advance.
I was facing the same problem and I was able to figure this out.
SecureProtocolSocketFactoryImpl protFactory = new SecureProtocolSocketFactoryImpl();
httpsClient.getHostConfiguration().setHost(host, port, httpsProtocol);
In the "SecureProtocolSocketFactoryImpl" class you have to override the method public Socket createSocket() for SecureProtocolSocketFactory class.
In that method you will get a socket like this
SSLSocket soc = (SSLSocket) getSSLContext().getSocketFactory().createSocket(
socket,
host,
port,
autoClose
);
So there you will be able to do something like below.
ciphersToBeEnabled[0] = "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA";
soc.setEnabledCipherSuites(ciphersToBeEnabled);
hope you get the idea. If you have any problems please comment below. Note that doing this only will not enable RC4 related ciphers. You will need to modify java "java.security" file in jre/lib/security/ file and remove CR4 form the disabled algorithm list.
For HttpsURLConnection, set the system property https.cipherSuites.
As you mentioned SSLSocketFactory doesn't support setEnabledCipherSuites() so can do something like this
SSLSocketFactory socketFactory=(SSLSocketFactory)SSLSocketFactory.getDefault();
SSLSocket socket=(SSLSocket)socketFactory.createSocket(host,port);
socket.setEnabledCipherSuites(CIPHERS);
SSLSocket provides setEnablesCipherSuites();
Related
I'm having some problems understanding how TLS/SSL is working for email.
I have some questions.
In my development machine if I debug the following code fails the first time arround on the "sslSocket.startHandshake()" line, but if I try it again straight away it is working fine.
The error message that I'm getting is: "Remote host closed connection during handshake".
When I deploy the same code to our staging environment and send an email the code is working fine first time.
Both the development and staging server are in the same network and both have no anti virus programs runnning.
The only thing that I can think of as to why it is not working the first time around in the development environment is because I'm stepping through the code with the debugger and it's slower because of this.
Do you have any knowledge as to why I am receiving this error?
The code underneath is creating an SSL Socket. I'm curious to know if this code is enough for the connection with the mail server to be secure. Are these SSLSocketFactory classes dealing with certificates themselves?
2a) Or do I still need to specify a certificate somehow?
2b) Or is this code getting the certificate from the server and using the certificate to encrypt the data and send the encrypted data back and forth to the email server?
I know that it should work like it is described here:
RFC 3207 defines how SMTP connections can make use of encryption. Once a connection is established, the client issues a STARTTLS command. If the server accepts this, the client and the server negotiate an encryption mechanism. If the negotiation succeeds, the data that subsequently passes between them is encrypted.
2c) Is the code underneath doing this?
socket.setKeepAlive(true);
SSLSocket sslSocket = (SSLSocket) ((SSLSocketFactory) SSLSocketFactory.getDefault()).createSocket(
socket,
socket.getInetAddress().getHostAddress(),
socket.getPort(),
true);
sslSocket.setUseClientMode(true);
sslSocket.setEnableSessionCreation(true);
sslSocket.setEnabledProtocols(new String[]{"SSLv3", "TLSv1"});
sslSocket.setKeepAlive(true);
// Force handshake. This can throw!
sslSocket.startHandshake();
socket = sslSocket;
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
In my development machine if I debug the following code fails the first time arround on the "sslSocket.startHandshake()" line, but if I try it again straight away it is working fine.
The error message that I'm getting is: "Remote host closed connection during handshake". []
The only thing that I can think of as to why it is not working the first time around in the development environment is because I'm stepping through the code with the debugger and it's slower because of this.
If you just do startHandshake() again with the underlying socket closed it should never work. If you go back to doing the TCP connection (e.g. new Socket(host,port)) and the initial SMTP exchange and STARTTLS, then yes I would expect it to avoid whatever problem affected the previous connection.
Yes, the server timing out because of the delay while you were debugging is quite possible, but to be certain you need to check logs on the server(s).
The code underneath is creating an SSL Socket. I'm curious to know if this code is enough for the connection with the mail server to be secure. Are these SSLSocketFactory classes dealing with certificates themselves?
Indirectly, yes. SSLSocketFactory creates an SSLSocket linked to an SSLContext which includes a TrustManager which is normally loaded from a truststore file. Your code defaults to the default SSLContext which has a TrustManager loaded from the default truststore, which is the file jssecacerts if present and otherwise cacerts in the lib/security directory in the JRE you are running. If your JRE hasn't been modified (by you or anyone else authorized on your system), depending on your variant or packaging of Java the installed JRE usually has no jssecacerts and contains or links to a cacerts file that (initially) contains root certs for about a hundred 'well-known' or established certificate authorities like Symantec, GoDaddy, Comodo, etc.
2a) Or do I still need to specify a certificate somehow?
Since when the handshake is done it is successful, obviously not.
2b) Or is this code getting the certificate from the server and using the certificate to encrypt the data and send the encrypted data back and forth to the email server?
Kind of/sort of/not quite. With some exceptions not applicable here, in an SSL/TLS handshake the server always provides its own certificate and usually intermediate or 'chain' certificates that link its cert to a trusted root cert (such as the abovementioned Symantec etc). The server cert is always used to authenticate the server, and sometimes alone but often combined with other mechanisms (particularly Diffie-Hellman ephemeral DHE or its elliptic-curve variant ECDHE) used to establish a set of several symmetric key values which are then used to encrypt and authenticate the data in both directions. For a more complete explanation see the canonical question and (multi-part!) answer in security.SX https://security.stackexchange.com/questions/20803/how-does-ssl-work/
2c) Is the code underneath doing this?
It is starting an SSLv3 or TLSv1 client-side session on an existing socket. I'm not sure what other question you have here.
You might be better off leaving out the setEnabledProtocols(). Sun/Oracle Java version 8, which is the only one now supported, supports TLS 1.0, 1.1 and 1.2 by default. 1.1 and especially 1.2 are definitely better than 1.0, and should definitely be offered so that if the server supports them they get used. (Sun/Oracle 7 is more problematic; it implements 1.1 and 1.2, but does not enable them client side by default. There I would look at .getSupportedProtocols and if 1.1 and 1.2 are supported but not enabled I would add enable them. But if possible I would just upgrade to 8. Other versions of Java, notably IBM, differ significantly in crypto details.)
SSLv3 should not be offered unless absolutely necessary; it is now badly broken by POODLE (search on security.SX for dozens of Qs about POODLE). I would try without it, and only if the server insists on it re-enable it temporarily, _along with TLS 1.0 through 1.2 whenever possible, and simultaneously urge the server to upgrade so I can remove it again.
Our server has just had it's cipher suites restricted to:
SSLCipherSpec ALL TLS_RSA_WITH_AES_128_GCM_SHA256 TLS_RSA_WITH_AES_256_GCM_SHA384
And now my test java client code fails with this message in the server log:
SSL Handshake Failed, No ciphers specified
and this stack trace on the client side:
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
at com.ibm.jsse2.j.a(j.java:42)
at com.ibm.jsse2.j.a(j.java:41)
at com.ibm.jsse2.qc.b(qc.java:485)
at com.ibm.jsse2.qc.a(qc.java:381)
at com.ibm.jsse2.qc.h(qc.java:453)
at com.ibm.jsse2.qc.a(qc.java:625)
at com.ibm.jsse2.qc.startHandshake(qc.java:113)
at com.ibm.net.ssl.www2.protocol.https.c.afterConnect(c.java:188)
at com.ibm.net.ssl.www2.protocol.https.d.connect(d.java:9)
at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1103)
at com.ibm.net.ssl.www2.protocol.https.b.getOutputStream(b.java:84)
Can anyone advise what I need to setup in my HttpURLConnection to get it to use these restricted cipher suites? Or is this something I need to setup in the JVM arguments (in Eclipse)?
Many thanks
You're visibly using the IBM JSSE. According to its documentation (see Customizing JSSE -> Customization)., this can be done by setting the https.cipherSuites system property:
This contains a comma-separated list of cipher suite names specifying which cipher suites to enable for use on this HttpsURLConnection.
This is of course exactly the same parameter as for the Sun/Oracle JSSE. (You might also find https.protocols useful, if you need to set the protocol too.)
If you want to achieve this on a per-connection basis, you can pass specific settings to your HttpsURLConnection using a custom SSLSocketFactory. Typically, you would implement your own SSLSocketFactory that delegates all calls to the default SSLSocketFactory (SSLSocketFactory.getDefault()), or an SSLSocketFactory coming from a custom SSLContext, but changes the SSLSocket created by any createSocket(...) method to change set its cipher suites. Something along these lines:
class MySSLSocketFactory {
public Socket createSocket(...) {
SSLSocket s = (SSLSocket) SSLSocketFactory.getDefault().createSocket(...);
s.setEnabledCipherSuites(...);
return s;
}
...
}
Then:
URLConnection urlConnection = url.openConnection();
HttpsURLConnection httpsUrlConnection = (HttpsURLConnection) urlConnection;
httpsUrlConnection.setSSLSocketFactory(new MySSLSocketFactory());
That said, the two cipher suites you're trying to use (TLS_RSA_WITH_AES_128_GCM_SHA256 and TLS_RSA_WITH_AES_256_GCM_SHA384) have only been supported in the Oracle JRE since Java 8, but they're already enabled by default there, so you shouldn't need any customisation.
The same table for the IBM® SDK, Java™ Technology Edition, Version 8 only has columns up to version 7, and these cipher suites are not listed at all. I'm not sure whether this is just a part of the documentation that has not been updated, or whether the IBM JSSE does not support these cipher suites, even in version 8.
I need to implement the Apache HttpClient SecureProtocolSocketFactory interface to create SSL sockets that do not use the SSLv2Hello protocol and make SSLv3 handshakes. From the documentation, the process to modify the protocol list is to call setEnabledProtocols on the SSLSocket. I believe I need to do this after creating it but before connecting it because connection initiates the SSL handshake and it's the SSL handshake protocol I'm trying to change.
This is mostly fine: rather than using the with-parameter context.getSocketFactory().createSocket(...) overloads which both create and connect the sockets then I can use the parameterless overload to create the socket, set it up and then connect it myself. The problem is there's another overload of createSocket() in SSLSocketFactory that wraps an existing socket with SSL, and that initiates the handshake immediately i.e. I do not have the opportunity to reconfigure it.
So to cover that case too I think I've got to throw away SSLSocket.setEnabledProtocols and instead do one of
modify my SSLContext's set of protocols which is what SSLSocketImpl uses to configure itself. However I can't see a public interface to do this so I'd need to change this by reflection, which means assuming private members of the class and also getting access to the internal class ProtocolList.
use my SSLContext only to get an SSLEngine which I can configure, and then implement my own SSL on the sockets using this.
I'm not very happy with either of these; I'd probably lean towards reflection since Java 7+ is dropping SSLv2Hello so if the classes change in the future and my reflection breaks then that's not actually a problem. I have a new instance of SSLContext already for a custom trust store.
Have I missed something - a public mechanism on SSLContext or SSLSocketFactory to set this up? Is there a better, cleaner way to do this? Thanks!
On further investigation, I don't think reflection is possible either. I was looking at the JDK8 source since that's what I had to hand, whereas in the JDK6 source SSLSocket initialises itself from a static list, not from an SSLContext or SSLContextSpi property:
enabledProtocols = ProtocolList.getDefault();
and the source field behind getDefault is static final so I can't modify that either :-(
So I'm running out of ideas. I'm still not keen on reimplementing SSLSocket (2000+ lines of it) so I think it's back to setEnabledProtocols and hope my HTTPS client never has to negotiate up a connection to SSL.
I ran into very similar problem.
I think you can break most of the issues down to the question, why can we override enabled ciphers in a custom socket factory but we cannot set the procol versions returned by getEnabledProtocols(): String[]?
Or even more simple:
If you could set enabled ciphers and protocol versions in SSLContext, you could use any HttpsConnection / SSLClient and SSLServerSocket and even libaries (Apache HttpClient, OKHttp, ...) and be sure that your security requirements in terms of SSL protocol versions and ciphers are both met.
Unfortunately most people stop at the point where a connection is established, security is just an announce to them.
I'm writing an Android client for a system that requires me open an SSLSocket to a proxy server, do a tunnel handshake and then create ANOTHER SSLSocket over the tunnel. Here is my code to create the tunnel:
SSLSocketFactory sslsocketfactory = securityService.getSslContextNoCerts().getSocketFactory();
SSLSocket sslSocket = (SSLSocket) sslsocketfactory.createSocket(proxyAddress.getAddress(),
proxyAddress.getPort());
sslSocket.setEnabledProtocols(new String[] { SecurityService.TLS10 });
sslSocket.setEnabledCipherSuites(SecurityService.CIPHERS);
sslSocket.startHandshake();
Then I do tunnel handshake and then:
SSLSocketFactory sslsocketfactory = securityService.getSslContext().getSocketFactory();
hostSocket = (SSLSocket) sslsocketfactory.createSocket(tunnel,
InetAddress.getByAddress(remoteAddress.getIpAddress()).getHostAddress(),
remoteAddress.getPort(), false);
hostSocket.setUseClientMode(false);
hostSocket.setNeedClientAuth(true);
securityService.setEnabledProtocols(hostSocket);
hostSocket.setEnabledCipherSuites(SecurityService.DATASESSION_CIPHERS);
hostSocket.startHandshake();
At this point I get an SSLProtocolException with this message:
error:140760FC:SSL routines:SSL23_GET_CLIENT_HELLO:unknown protocol (external/openssl/ssl/s23_srvr.c:589 0xad12b3f0:0x00000000)
Anybody know how I can achieve this? I know your first question would be why layer SSL over SSL, but I'm writing a client for and EXISTING system that requires it.
Any help would be much appreciated.
Zhubin
Ok I finally fixed this problem. For some reason when I use org.apache.harmony.xnet.provider.jsse.OpenSSLProvider (Android default SSL provider), SSL over SSL does not work. So I switched to org.apache.harmony.xnet.provider.jsse.JSSEProvider and now everything works fine.
Your code looks correct. As it doesn't work, I suggest you have misunderstood the requirement, or it has been misrepresented to you. I suggest you only need to keep using the original SSLSocket. Try it. I find it vanishingly unlikely that any real system works in the way you have described. Not only would its performance be abysmal; the server would have to have the same kind of double-SSL coding that you have here: and how would it know when to do that and when not? Once the tunnel is created the proxy just copies bytes. I bet that just continuing to use the original SSL connection will work.
I have this:
SSLSocketFactory factory = HttpsURLConnection.getDefaultSSLSocketFactory();
SSLSocket socket = (SSLSocket) factory.createSocket("www.verisign.com", 443);
This is failing on the 2nd line with a "Connection refused" error.
Now, would I have to install verisign's certificate in my trust store before I can even do the above? I was under the impression that I could connect to an SSL server and execute getPeerCertificates() to get the certificates. Is this not what our browsers do? Otherwise how would they know which signing authority to use?
(Obviously I'm using Verisign as an example. My real URL is far too fugly to use here...)
Connection refused means nothing was listening at the target host:port, or a firewall got in the way. This is logically and temporally prior to anything SSL does.
Have you checked that the remote service is actually up and running, and that you can connect to it? Perhaps the "Connection refused" error is actually a refused connection. :-)
Usually you don't need to install server's certificate on your computer explicitly. PKI works in the way that your system should be able to validate server's certificate without any prior knowledge about it. However this will work only when your server's certificate has it's roots in on of the "known CAs", i.e. certificate authorities, whose root or other certificates are already listed on the client system. If this is not the case (eg. you have a self-signed or some other custom certificate on the server), you really need to install the certificate on your client system before the mentioned classes can validate server certificate properly.
You can read about certificates and how they are used in SSL here.