I am trying to work out a way to check the Cipher Suite (CS) that has been chosen during the TLS handshake. I want to be able to manually kick out of the handshake, or at least stop the body of the http message being sent if the CS fails some manual check.
I am requesting a subset of CSs using a custom SocketFactory but this is not always honoured by the server.
I have played with doing this inside the HandshakeCompletedListener callback but this is proving difficult. Throwing any RuntimeException class will not do anything due to the catch all in the calling method. Closing the socket wont make any difference either, which i thought may internally throw an exception, which would not be a great solution anyway.
Any help would be great.
EDIT: after #jesse-wilsons answer below I am using 2.1-RC1 and the ConnectionSpec class to specify what Cipher Suites (CS) I want to use - however there are cases when an existing SSL session can be reused with a differnt cipher suite. I have included a custom HostNameVerifier to catch any cases where an SSL connection is trying to be setup with a differnt than requested CS. This is a little bit of a shoehorn but I cant see another way to hook in after the SSL handshake has taken place.
public class HostnameAndCipherSuiteVerifier implements HostnameVerifier
{
private final String mHostname, mCipherSuite;
public HostnameAndCipherSuiteVerifier(String hostname, String cipherSuite)
{
mHostname = hostname;
mCipherSuite = cipherSuite;
}
#Override
public boolean verify(String hostname, SSLSession session)
{
//delegate to original hostname verifier
if(!OkHostnameVerifier.INSTANCE.verify(hostname, session))
{
XLog.w("delegate hostname check failed");
return false;
}
//hardcode check for domain
if(!hostname.equals(mHostname))
{
XLog.w("Hard-coding hostname fail");
return false;
}
//handshake cipher check
if(!session.getCipherSuite().equals(mCipherSuite))
{
XLog.w("cipher check failed");
return false;
}
else
return true;
}
}
Grab OkHttp 2.1-RC1 and use the new ConnectionSpec class to limit the cipher suites to those you like.
Related
I'd like to make an HTTPS connection to a server and, if I'm using
non-ephemeral DH key exchange, I'd like to know what the parameters
are for that connection. Actually, I don't really care if it's
ephemeral or not.
What I'm looking for is the ability to make a connection and then warn
if the connection is using "weak" DH parameters. Is that something I
can check at connection-time? Or is the set of DH parameters (or, more
specifically, the length of those parameters, in bits) defined by
the cipher suite itself?
For example, the Qualys community thread has an illustration of the
cipher suites that SSLLabs considers "weak" (well, everyone considers
them weak... they just have a public tool which complains about them):
https://community.qualys.com/thread/14821
They specifically mention e.g. TLS_DHE_RSA_WITH_AES_256_GCM_SHA384
which is cipher suite 0x9f and mention the DH parameters. Are those
parameters' parameters baked-into the cipher suite (meaning they are
always 1024-bit) or is this a configuration of the server that makes
those cipher suites weak due to the specific DH parameter choice?
In either case, I'd like to be able to sniff that information from the
connection if at all possible. Does anyone know if this can be done,
and how?
I've written some code to attempt to get this information about the handshake, but I keep getting null for the object I was hoping would contain this data.
SSLSocketFactory sf = ...;
Socket sock = new Socket();
sock.connect(address, timeout);
SSLSocket socket = (SSLSocket)sf.createSocket(sock, host, port, true);
socket.startHandshake();
SSLSession sess = socket.getHandshakeSession();
I was hoping that sess at this point would contain some interesting information about the handshake, but it's null. The javadoc for startHandshake indicates that it will notify an event listener when the handshake is completed. So I tried this:
SSLSocketFactory sf = ...;
Socket sock = new Socket();
sock.connect(address, timeout);
SSLSocket socket = (SSLSocket)sf.createSocket(sock, host, port, true);
socket.startHandshake();
// SSLSession sess = socket.getHandshakeSession();
SSLSession sess = socket.getSession(); // This forces the handshake to complete
sess = socket.getHandshakeSession();
... but sess is still null at this point. The "real" SSLSession does exist and gives me information about the connection, but the "handshake session" seems to always be null.
So I tried writing an HandshakeCompletedListener, and I do in fact get an SSLSession, but it appears to be the same one that I can get from the SSLSocket already, so the "handshake" session seems to be unhelpful.
How can I get those parameters from the SSLSession?
Are those parameters' parameters baked-into the cipher suite (meaning they are always 1024-bit) or is this a configuration of the server that makes those cipher suites weak due to the specific DH parameter choice?
No, this is a configuration parameter for the protocol. There is a default of 1024 bits for Java but that may be changed globally for JSSE (the Java TLS implementation) using a system property: jdk.tls.ephemeralDHKeySize. Best set this during startup with a -D option for the Java VM.
For static DH key pairs (that are used for authentication) you would have to look into the DH certificate. But I don't think you'll find any, everybody uses RSA for authentication.
In either case, I'd like to be able to sniff that information from the connection if at all possible. Does anyone know if this can be done, and how?
Well, for sniffing tools such as WireShark would suffice. Undoubtedly you can parse things like DH parameters from a TLS connection (if they are used in the first place of course).
You can also debug connections using -Djavax.net.debug
For Java applications / libraries you could look up the cipher suite and then, if it contains DHE_ look up the aforementioned system property (keeping in mind its default values).
The Java JSSE API was not written with deep packet inspection in mind. It's (literally) a service oriented implementation for servers and client applications. Although you could of course use the OpenJDK code itself (it's GPL'ed, right?) you are better off using a separate implementation, possibly with an even more permissive license.
For a sniffer however I would rather use C/C++ (or at least a C/C++ frontend) than Java.
For most cipher algorithms, the length is determined by the name cypher name, as also mentioned here How to get the actual block cipher key size for Java SSL connection _in code_? .
Instead of trying to warn people when they are using unsecure cyphers, I'd recommend to disable those ciphers by selecting only the cyphers you want to support. You can do this on a jvm level or on the SSLSocket, e.g.
String pickedCipher[] ={"TLS_RSA_WITH_AES_128_CBC_SHA"};
socket.setEnabledCipherSuites(pickedCipher);
You can also set the desired key size, see here
https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#customizing_dh_keys
You can see defaults and classes used in Java security here https://docs.oracle.com/javase/8/docs/technotes/guides/security/SunProviders.html
If you are curious and want to investigate this in more detail, I'd recommend to turn on ssl logging, as described here.
I need to check whether a server supports SSL and the ciphers from the web server.
I looked into SSLSocket in Java but I do not get it to work properly.
I used the method getSupportedProtocols() it always gives out the same protocols for each url. Moreover I do not get the ciphers that are given from the server. I guessed getEnabledCipherSuites() would be the correct method
try {
SSLContext ctx = SSLContext.getDefault();
ctx.getClientSessionContext().setSessionTimeout(5); // in seconds
SSLSocket socket = (SSLSocket) ctx.getSocketFactory().createSocket("host.com", 443);
socket.setSoTimeout(5000); // in millis
String[] result = socket.getEnabledCipherSuites();
Arrays.sort(result);
for (int i = 0; i < result.length; i++) {
System.out.println(result[i]);
}
} catch (IOException ex) {
System.out.println("Error!");
}
How can I check that the server uses SSL? What ciphers were returned from the server?
getSupportedProtocols()/CipherSuites() returns the lists of protocols and ciphers (respectively) that your side can support. getEnabledProtocols()/CipherSuites() returns the subset of these lists that are enabled on your side.
That won't tell you much about about what the server supports.
According to the TLS specification, the client sends the highest protocol version can use and the list of cipher suites it wants to use. The server then uses the highest protocol it supports within that list (if any) and selects the cipher suite it wants amongst that list (if any).
There's some flexibility regarding how the cipher suite selection is done (see SSLHonorCipherOrder directive in Apache Httpd, for example).
Essentially, if you want to see which cipher suites are actually supported by your server, you'll have to iterate through each cipher suite you support and enable them one by one to try a handshake.
used the method getSupportedProtocols() it always gives out the same protocols for each url.
It always give you the protocols supported by your end. That doesn't change per URL.
Moreover I do not get the ciphers that are given from the server. I guessed getEnabledCipherSuites() would be the correct method.
You guessed wrong. It gives you the ciphers supported by your end.
How can I check that the server uses SSL?
Call startHandshake() and catch the exception.
What ciphers were returned from the server?
You can't, but you can tell what cipher suite was negotiated, by looking in the SSLSession.
I'm trying to set up a connection with an https url, but since it's still in testing, the url redirects to the test environment, causing a browser to prompt the user with a message saying roughly that the certificate is trusted, valid, but doesn't match the domainname, and if they want to continue. I'm having a hard time dealing with this in Java. All the answers I've found suggest using a TrustManager like this
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
return;
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
return;
}
}};
sslContext.init(kmf.getKeyManagers(), trustAllCerts, new SecureRandom());
But this doesn't change anything.
The authentication involves entering a password on reading the client certificate's private key, and this works identically and correctly both from a browser and my application, it's after this authentication that either the prompt pops up in the browser or the connection returns a 'bad_certificate' error in my application. Is there some other way to either ignore the domain validation entirely or make the same prompt appear?
For clarity, my application doesn't cause the password prompt to appear because I programmed it in or anything, I think it just originates from windows detecting my application trying to access the certificate's private key, so I would assume that the second prompt would work in a similar fashion, but I haven't found any parameters to make that happen yet.
Let me warn you that if you are doing this for testing your code then this is not the right thing to do as. Browsers often ignore the errors and prompt the user if he would like to contnue despite the problems. If he decides to continue then he owns the decision.
In your java application, if you are using the JDK provided default SSLSocketFactory then the SSL sessionis only established if the SSL handshake succeeds. And this is for security reasons. If you need to have a funcationalty like your browsers; to proceed irrespective of the security issues then you need to have your own custom implementation. Or better dont use SSL at all.
Is there some other way to either ignore the domain validation...
Yes. See Java's HostnameVerifier interface:
During handshaking, if the URL's hostname and the server's
identification hostname mismatch, the verification mechanism can call
back to implementers of this interface to determine if this connection
should be allowed.
Is there simply way to dinamic generate untrusted ssl certificate without domain and applay it to server socket - all from code, no commandline or additional files?
Purpose is secure connection between two hosts witch know only IP and port to communicate each other - certificates generated randomly at server start and used as "untrusted", no domain so no verification (if I'm not wrong). I think this can be usefull in secureing data transfer between datacenters in third party apps.
This is working code for not encrypted simply client-server test.
package study.benchmark.utils;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import org.junit.Test;
public class DynamicSSLTest {
#Test
public void sslServerSocketTest() throws Exception {
System.out.println("ssl server test");
final int port = 8750;
// server
Thread th = new Thread() {
#Override
public void run() {
try {
//ServerSocketFactory factory = SSLServerSocketFactory.getDefault();
ServerSocketFactory factory = ServerSocketFactory.getDefault();
ServerSocket server = factory.createServerSocket(port);
Socket socket = server.accept();
OutputStream out = socket.getOutputStream();
out.write("some data".getBytes());
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
};
th.start();
//client
//SocketFactory factory = SSLSocketFactory.getDefault();
SocketFactory factory = SocketFactory.getDefault();
Socket socket = factory.createSocket("localhost", port);
InputStream is = socket.getInputStream();
StringBuffer sb = new StringBuffer();
int data;
while ((data = is.read()) >= 0) {
System.out.print((char) data);
}
System.out.println();
socket.close();
th.join();
}
}
You can generate a self-signed certificate dynamically using a library such as BouncyCastle (essentially, for the certificate to be self-signed, you using the same issuer DN as the subject DN and you sign with the private key corresponding to the certificate's public key). Then, you'll need to put it in a KeyStore (in memory at least, not necessarily on file) and build an SSLContext from it, so as to be able to build an SSLSocketFactory.
This can be useful for testing, but this will not make your application secure. Generally speaking, encryption without authentication of the remote party isn't secure. You can exchange information as "secretly" as you want with a remote party, but if you haven't verified its identity you're not really sure your secrets are given to the intended recipient.
If your certificate is generated dynamically, you would need to find a way for the client to know it's indeed the legitimate certificate, before making any calls to that server.
The general SSH approach (where one assumes few people actually check the fingerprint they get in the first connection -- some people actually do check out of band) is a compromise whereby clients tend to accept the key (more or less blindly) the first time but will be warned if it has changed. You could implement this sort of approach for handling X.509 certificate trust too, but if you re-generate a new self-signed certificate every time you restart your server, you're back to the initial problem.
You could address this problem by having some sort of online/dynamic CA, where the servers would request and be issued a certificate dynamically based on something they could prove to that CA dynamically (to prove they're one of your servers, perhaps based on some configuration parameter known by both), and then have the client trust that CA, but that's a more complex scenario.
Why? It's not secure, and who will trust it anyway? Why can't you create a server certificate per deployment offline?
For your stated purpose of securing communications between two hosts, both of which are under your control, SSH would be a better solution. You can generate a key pair shared only with your other machine. Google "java ssh" for a host of options.
I have a very simple code that uses HttpURLConnection to access some web site via proxy
System.setProperty("java.net.useSystemProxies", "true");
System.out.println("Proxy: " + ProxySelector.getDefault().select(new URI(urlS)));
URL url = new URL(urlS);
HttpURLConnection ic = (HttpURLConnection)url.openConnection();
ic.connect();
For some reason, Java thinks that I need SOCKS proxy, not http, throwing the following exception:
ERROR: Can't connect to SOCKS proxy:Connection timed out: connect
If you are having this issues on Windows, you may run into a Java bug.
Java treats any system proxy setting as SOCKS. You have to either disable useSystemProxies or don't use proxy in Windows.
If proxy is needed, try to uncheck "Use the same proxy server for all protocols", making sure the field for the SOCKS proxy is blank. That fixed our problem.
The real problem is that Java assumes that the "Use the same proxy server for all protocols" check affects SOCKS proxy too (I don't know the logic behind this dialog in Windows, but it is, at least, confusing)
If the check is set, you get proxies enabled for both HTTP and SOCKS, wich is very unlikely to be the desired configuration.
One way to solve it is unchecking the check and leaving blank the SOCKS field.
I finally solved it creating a ProxySelector wich first calls the default selector and if it finds the same configuration for HTTP and SOCKS connections, it omits the SOCKS proxy.
public class SocksFixerProxySelector extends ProxySelector {
ProxySelector base;
public SocksFixerProxySelector() {
base = ProxySelector.getDefault();
}
#Override
public List<Proxy> select(URI uri) {
List<Proxy> baseList = base.select(uri);
try {
if (uri.getScheme().equals("socket")) {
Proxy socksProxy = findByType(baseList, Type.SOCKS);
if (socksProxy != null) {
URI httpTestUri = new URI("http", uri.getHost(), uri.getPath(), uri.getFragment());
Proxy httpProxy = findByType(base.select(httpTestUri), Type.HTTP);
if (httpProxy != null && socksProxy.address().equals(httpProxy.address())) {
// Quitamos SOCKS
List<Proxy> filteredList = new ArrayList<>(baseList);
filteredList.remove(socksProxy);
return filteredList;
}
}
}
} catch (Exception e) {
}
return baseList;
}
#Override
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
base.connectFailed(uri, sa, ioe);
}
private Proxy findByType(List<Proxy> proxies, Proxy.Type type) {
for (Proxy proxy : proxies) {
if (proxy.type() == type)
return proxy;
}
return null;
}
Maybe a better solution would be to inspect the registry and detect the right settings, but I didn't want to mess with Windows specific code (and all those script settings looked bad, too )
You need to use the http.proxyHost system property instead. See http://java.sun.com/javase/6/docs/technotes/guides/net/proxies.html for details.
java -Dhttp.proxyHost=webcache.mydomain.com GetURL
Check that something has not set the "socksProxyHost" property in the Systems properties.
EDIT
The "useSystemProxies" property is described thus:
"On recent Windows systems and on Gnome 2.x platforms it is possible to tell the default ProxySelector to use the system proxy settings (both recent versions of Windows and Gnome 2.x let you set proxies globally through their user interface). If the system property java.net.useSystemProxies is set to true (by default it is set to false for compatibility sake), then the default ProxySelector will try to use these settings."
So, assuming that you have not supplied your own ProxySelector class, you should also check the system proxy settings to ensure that they don't say to use SOCKS.