Secret Key SSL Socket connections in Java
Asked Answered
P

2

8

I'm working on encrypting a tcp connection between a server and a client. In the course of research and testing I'm leaning towards using secret key encryption. My problem is that I cannot find any tutorials on how to implement this feature. The tutorials I have found revolve around one-shot https requests, all I need is a SSL Socket.

The code I've written so far is below. I'm almost certain that it needs to be extended, I just don't know how. Any help is appreciated.

private ServerSocketFactory factory;
private SSLServerSocket serverSocket;

factory = SSLServerSocketFactory.getDefault();
serverSocket = (SSLServerSocket) factory.createServerSocket( <portNum> );

Server code for accepting client connections

SSLSocket socket = (SSLSocket) serverSocket.accept();
socket.startHandshake();

I just don't know how to actually do the handshake.

reference: http://docs.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html

Pontifical answered 29/3, 2014 at 22:53 Comment(3)
Interested in an answer here with hopefully an example. I've wanted to use SSL sockets before, but kind of like you, never quite understood how the keys were exchanged, but then I never put much effort in, just kind of read the docs and was like, hmm, that doesn't sound straightforward, lol.Emptor
I've provided what I hope amounts to a tutorial in my answer below. In your case, you mostly just need to move the socket.startHandshake call to the client, getting the client's SSLSocket appropriately - and you need to make sure you have your certificates straight.Regret
@WarrenDew, too bad we can't downvote comments, but what you've said above really doesn't make sense. socket.startHandshake is a Java API call that doesn't have that much to do with who actually starts the handshake at the protocol level. It can be called on either client or server side (although it mostly makes sense on the server as part of a renegotiation). It's generally optional. "Moving" it to the client side shouldn't really matter.Nero
R
20

SSL socket connections are well supported in Java and are likely a good choice for you. The one thing to understand in advance is that SSL provides both encryption and server authentication; you can't easily get just the encryption. For reference, the encryption protects against network eavesdropping, while the server authentication protects against "man in the middle" attacks, where the attacker acts as a proxy between the client and the server.

Since authentication is an integral part of SSL, the server will need to provide an SSL certificate, and the client will need to be able to authenticate the certificate. The server will need a "key store" file where its certificate is stored. The client will need a "trust store" file where it stores the certificates it trusts, one of which must either be the server's certificate, or a certificate from which a "chain of trust" can be traced to the server's certificate.

Note that you do not have to know anything about the ins and outs of SSL in order to use Java SSL sockets. I do think it is interesting to read through information on how SSL works, for example in the Wikipedia article on TLS, but the complicated multistep handshake and the setup of the actual connection encryption is all handled under the covers by the SSLServerSocket and SSLSocket classes.

The code

All of the above is just background information to explain some of the following code. The code assumes some familiarity with regular, unencrypted sockets. On the server, you will need code like this:

/**
 * Returns an SSLServerSocket that uses the specified key store file 
 * with the specified password, and listens on the specified port.
 */
ServerSocket getSSLServerSocket(
    File keyStoreFile, 
    char[] keyStoreFilePassword,
    int port
) throws GeneralSecurityException, IOException {
    SSLContext sslContext 
        = SSLConnections.getSSLContext(keyStoreFile, keyStoreFilePassword);
    SSLServerSocketFactory sslServerSocketFactory 
        = sslContext.getServerSocketFactory();
    SSLServerSocket sslServerSocket
        = (SSLServerSocket) sslServerSocketFactory.createServerSocket(port);
    return sslServerSocket;
}

The SSLServerSocket can then be used exactly like you would use any other ServerSocket; the authentication, encryption and decryption will be completely transparent to the calling code. In fact, the cognate function in my own code declares a return type of just plain ServerSocket, so the calling code can't get confused.

Note: if you want to use the JRE's default cacerts file as your key store file, you can skip the line creating the SSLContext, and use ServerSocketFactory.getDefault() to get the ServerSocketFactory. You will still have to install the server's public/private key pair into the key store file, in this case the cacerts file.

On the client, you will need code like this:

SSLSocket getSSLSocket(
    File trustStoreFile,
    char[] trustStoreFilePassword,
    InetAddress serverAddress,
    port serverPort
) throws GeneralSecurityException, IOException {
    SSLContext sslContext 
        = SSLConnections.getSSLContext(trustStoreFile, trustStoreFilePassword);
    SSLSocket sslSocket 
        = (SSLSocket) sslContext.getSocketFactory().createSocket
            (serverAddress, serverPort);
    sslSocket.startHandshake();
    return sslSocket;
}

As in the case of the SSLServerSocket in the server code, the returned SSLSocket here is used just like a regular Socket; I/O into and out of the SSLSocket is done with unencrypted data, and all the cryptographic stuff is done inside.

As with the server code, if you want to use the default JRE cacerts file as your trust store, you can skip creation of the SSLContext and use SSLSocketFactory.getDefault() instead of sslContext.getSocketFactory(). In this case, you will only need to install the server's certificate if the server's certificate was self signed or not otherwise issued by one of the major certificating authorities. In addition, to ensure you aren't trusting a certificate that is legitimately issued within a certificate chain you trust, but to an entirely different domain than you are trying to get to, you should add the following (untested) code just after the line where you create the SSLSocket:

    sslSocket.getSSLParameters().setEndpointIdentificationAlgorithm("HTTPS");

This would also apply if you are using your own trust store file, but trusting all certificates issued by one or more certificating authorities in that file, or trusting a number of certificates for different servers in that file.

Certificates, key stores, and trust stores

Now for the hard, or at least, slightly harder part: generating and installing the certificates. I recommend using the Java keytool, preferably version 1.7 or above, to do this work.

If you are creating a self signed certificate, first generate the server's keypair from the command line with a command like the following: keytool -genkey -alias server -keyalg rsa -dname "cn=server, ou=unit, o=org, l=City, s=ST, c=US" -validity 365242 -keystore server_key_store_file -ext san=ip:192.168.1.129 -v. Substitute your own names and values. In particular, this command creates a key pair that expires in 365242 days - 1000 years - for a server that will be at IP address 192.168.1.129. If the clients will be finding the server through the domain name system, use something like san=dns:server.example.com instead of san=ip:192.168.1.129. For more information on keytool options, use man keytool.

You will be prompted for the key store's password - or to set the key store's password, if this is a new key store file - and to set the password for the new key pair.

Now, export the server's certificate using keytool -export -alias server -file server.cer -keystore server_key_store_file -rfc -v. This creates a server.cer file containing the certificate with the server's public key.

Finally, move the server.cer file to the client machine and install the certificate into the client's trust store, using something like, keytool -import -alias server -file server.cer -keystore client_trust_store_file -v. You will be prompted for the password to the trust store file; the prompt will say "Enter keystore password", since the Java keytool works with both key store files and trust store files. Note that if you are using the default JRE cacerts file, the initial password is changeit, I believe.

If you are using a certificate purchased from a generally recognized certificating authority, and you're using the default JRE cacerts file on the client, you only have to install the certificate in the server's key store file; you don't have to mess with the client's files. Server installation instructions should be provided by the certificating authority, or again you can check man keytool for instructions.

There's a tremendous amount of mystique surrounding sockets and, especially, SSL sockets, but they're actually quite easy to use. In many cases, ten lines of code will avoid the need for complex and fragile messaging or message queueing infrastructure. Good for you for considering this option.

Regret answered 30/3, 2014 at 0:5 Comment(12)
Can you explain how to generate the certificates? It would be nice to also explain how to give the certificate to the client as well, but I think that may be off topic here.Emptor
I've added a description of how to generate the key pair and associated certificate, and if it's a self generated certificate, how to install it in the client.Regret
Great answer, thanks. I will be bookmarking this page for further reference.Emptor
@WarrenDew, this is by far the most concise tutorial I've seen. I'll give this a go, thanks!Pontifical
You're both welcome! Hope this works for you, Kyte.Regret
Good answer, but you're missing hostname verification on the client side. Using sslParameters.setEndpointIdentificationAlgorithm("HTTPS") is probably the best way to do this (even for non-HTTPS). In addition, you don't need to use startHandshake, it will all be done for you when you start using the I/O streams.Nero
@Jared, you should read this question on Security.SE. The server doesn't encrypt a certificate at all. The client encrypts a pre-master secret (RSA key exchange) with the server's pubkey (or something a bit more complex for DHE exchange, where the server signs a newly-generated key). From then on, it's all symmetric crypto, no public/private keys.Nero
@Nero I added the information on hostname verification in cases where one might trust the wrong certificate - thanks. As for startHandshake, I use it to force immediate handshake negotiation - and, possibly, connection, as the Javadoc does not make it clear when the socket actually connects - even in cases where the connection may be idle for some time, with no data being transferred.Regret
@Nero Thanks, I'm going to delete my comments (so it doesn't confuse anyone).Emptor
If you want help generating the certificates, have a look at gpotter2.github.io/tutos/en/sslsocketsStaten
I am trying to SLLSockets on my LAN. Is there a way I can skip the certificates? I want the data to be encrypted so that only the client and server can read it, but I don't need to validate the server's authenticity. I am also not concerned about MITM attacks.Bighorn
It seems as if the provided code doesn't work anymore. "SSLConnections" isn't found. Can anyone update this post with updated code, because I think it's a very helpful one, if it wasn't for the old code.Rework
T
1

You've started the handshake. That's all you have to do: in fact you don't even have to do that, as it will happen automatically. All you have to do now is normal input and output, same as you would with a plaintext socket.

Thermae answered 30/3, 2014 at 0:0 Comment(7)
I would note that usually the handshake is started by the client.Regret
@WarrenDew I would note that as far as the application is concerned it is automatic, as I said, and also that either side, or indeed both sides, can call startHandshake(). Over the wire it is always started by the client (in SSL terms), i.e. with a ClientHello message, but it's not something the OP needs to be concerned about.Thermae
As far as the application code is concerned, that is true. However, from a configuration standpoint, it's the side that does not start the handshake that needs to have a key store with a public/private key pair. The side that starts the handshake just needs a trust store with the appropriate certificate.Regret
@WarrenDew, I think what EJP is referring to is the fact that "any attempt to read or write application data on this socket causes an implicit handshake" (see introduction of SSLSocket Javadoc). Neither the server nor the client needs to do this explicitly in Java. By definition, the initial handshake is always started by the SSL/TLS client (see glossary), and you more or less always want to authenticate the server in any SSL/TLS connection indeed.Nero
@Nero I don't disagree with all that. However, in the general case, it cannot be assumed that part of the application desired to be the client is necessarily the first to read or write data on the connection. Thus, there may be cases where you do want to explicitly start the handshake from the client. There are definitely no cases where you want to explicitly start the handshake from the server, as the question's original code had it. I was not disagreeing with EJP; I was merely adding additional related information that might be relevant to some readers.Regret
@WarrenDew I don't know what this has to do with my answer. Nor does the first side to do application I/O determine which is the SSL client. It is configured into the SSL socket which is the client and which the server. Your own answer appears comprehensive enough, although I haven't read it, that you don't need to polish the apple by adding information to mine, especially misinformation.Thermae
@WarrenDew "[...] it cannot be assumed that part of the application desired to be the client is necessarily the first to read or write data on the connection". I think you can make that assumption on the SSLSocket. In a server-talks-first proto. (e.g. IMAPS), the client will first read the InputStream, triggering the handshake. Same for a client-talks-first proto. (e.g. HTTPS) with the client writing to the OutputStream. Where there's an upgrade to SSL (e.g. SMTP+STARTLS), what happens before isn't on the SSLSocket, and using its I/O stream will also trigger the handshake.Nero

© 2022 - 2024 — McMap. All rights reserved.