Java JSSE TLS - Is this connection safely encrypted in both directions?
Asked Answered
R

2

5

In Java using JSSE with TLS. I have created a secure socket between the server and client. After finally getting the sockets to connect securely, I still have a fundamental question about my existing code's security. I followed instructions in a tutorial, and sometimes the documentation in the JavaDoc is very precise but a little vague unless you speak the Swaheli dialect of Jargon....

I have been network programming for quite awhile now in C++. The transition to Java was easy. Recently, however, I have found it prudent to make the traffic secure. This being said:

I want to create a secure socket in the same way a web browser creates a secure socket, so traffic in both direction is encrypted. A client can see their personal account information sent from the server (very bad if intercepted), and a client can send their username and password to the server securely (also very bad if intercepted).

I know all about how public key cryptography works, but there's a side effect to public key cryptography alone. You send your public key to a client, the client encrypts with the public key, and sends data to the server only the server can decrypt. Now from what I understand, the server uses the private key to encrypt messages going to the client, and another layer of security needs to be added to prevent anyone with the public key from being able to decrypt it.

  1. I have a public / private key pair stored in files public.key and private.key (i made these using JSSE's keytool utility
  2. I included public.key in the client
  3. I included private.key in the server

Client Class:

    KeyStore keyStore;
    TrustManagerFactory tmf;
    KeyManagerFactory kmf;
    SSLContext sslContext;
    SecureRandom secureRandom = new SecureRandom();
    secureRandom.nextInt();

        keyStore = KeyStore.getInstance("JKS");
        keyStore.load(this.getClass().getClassLoader().getResourceAsStream("server.public"),"public".toCharArray());
        tmf = TrustManagerFactory.getInstance("SunX509");
        tmf.init(keyStore);
        kmf = KeyManagerFactory.getInstance("SunX509");
        kmf.init(keyStore, "public".toCharArray());
        sslContext = SSLContext.getInstance("TLS");
        sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), secureRandom);
        SSLSocketFactory sslsocketfactory = sslContext.getSocketFactory();
        SSLSocket sslsocket = (SSLSocket)sslsocketfactory.createSocket("localhost", 9999);

Server Class:

    String passphrase = "secret"
    KeyStore keyStore;
    TrustManagerFactory tmf;
    KeyManagerFactory kmf;
    SSLContext sslContext;
    SecureRandom secureRandom = new SecureRandom();
    secureRandom.nextInt();

        keyStore = KeyStore.getInstance("JKS");
        keyStore.load(this.getClass().getClassLoader().getResourceAsStream("server.private"),passphrase.toCharArray());
        tmf = TrustManagerFactory.getInstance("SunX509");
        tmf.init(keyStore);
        kmf = KeyManagerFactory.getInstance("SunX509");
        kmf.init(keyStore, passphrase.toCharArray());
        sslContext = SSLContext.getInstance("TLS");
        sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), secureRandom);
        SSLServerSocketFactory sslserversocketfactory = sslContext.getServerSocketFactory();
        SSLServerSocket sslserversocket =
        (SSLServerSocket)sslserversocketfactory.createServerSocket(9999);

/ ******* THE QUESTION ********/

Everything works! I attach the sockets to BufferedReader and BufferedWriter and begin talking beautifully back and forth after accept(); ing the connection from the client and starting my client and server send / receive loops.

Now I know that at this point client to server communication is secure. Only the server key can decrypt traffic coming from the client. But what about server to client communication? The client's key can decrypt messages coming from the server, but in Public Key Crypto 101 you learn that the client is now supposed to send a public key to the server. Is this happening behind the scenes in this code? Did SSLContext take care of this? Or now that I have an encrypted connection from the client to the server, am I now expected to generate a private/public key pair for the client as well?

Let me know if the traffic being sent and received in the above code is actually secure in both directions.

Remediless answered 15/10, 2012 at 2:32 Comment(1)
How did you "include" public.key on client and private.key on server??Lynnette
P
6

The certificates (and their private keys) in SSL/TLS are only used for authenticating the parties in SSL/TLS (often, only the server uses a certificate).

The actual encryption is done using shared/symmetric keys that are negotiated during the handshake, derived from the pre master key exchanged using a form of authenticated key exchange (see TLS Specification, Section F.1.1.

How this authenticated key exchange is done depends on the cipher suite, but the end result is the same: a shared pre-master secret between the two parties, guaranteed to be known only to the client and the server with the private key for its certificate.

Following that pre-master secret exchange, the master secret itself is calculated, from which a pair of secret keys is derived (as described in the Key Calculation section): one for the client to write (and for the server to read) and one for the server to write (and for the client to read). (MAC secrets are also generated, to guarantee the connection integrity.)

In principle, not all cipher suites provide encryption and authenticated key exchange (see Cipher Suite Definitions section), but all those enabled by default in JSSE with the SunJSSE provider do (see Cipher Suite tables in the SunJSSE provider documentation). In short, don't enable cipher suites with anon or NULL in their names.

Regarding your code:

  • There are multiple example of code around that fix the Key/TrustManagerFactory algorithm like this ("SunX509"). This is typically code that hard-codes the Java 1.4 defaults. Since Java 5, the default TMF algorithm is PKIX (see Customization section of the JSSE Reference Guide). The best way to get around this is to use TrustManagerFactory.getDefaultAlgorithm() (same for KMF), which will also allow your code to run on other JREs that don't support SunX509 (e.g. IBM's).

  • Since you're not using client-certificate authentication, there's no point having a KeyManagerFactory on the client side. Your initialising it with a keystore that probably doesn't have a private key anyway, which makes it pointless. You might as well use sslContext.init(null, tmf.getTrustManagers(), null). (Same thing for the secure random in both cases, let the JSSE use its default values.)

Polaroid answered 15/10, 2012 at 8:42 Comment(1)
:) Thank you for your explanation. I had left that code in there from the tutorial because at the time I was not yet sure if I would need to add another handshake after negotiating the initial connection. In fact, I was unclear altogether on the JSSE implementation of public key cryptography. The resources you've linked were very useful.Remediless
P
1

You do have an understanding of how PKI works, but you are missing two key pieces of SSL implementation. First most PKI algorithms allow for encrypting traffic both ways. You can send encrypt message using public key and only whoever has a private key can read it, this is called encryption. You can also encrypt the message using a private key and anybody who has a public key can decrypt it, this is called digital signature.

Another missing piece is that SSL doesn't use PKI to send network traffic between client and server. It uses symmetric encryption algorithm. However the key for the symmetric encryption (called session key) is establish using rather complicated challenge-response protocol that employs PKI, and certificates. During this phase server proves to the client that it is not man in in the middle, client can optionally proved it's certificate to the server if it has any for stronger authentication, and the symmetric session key is established. More details are here https://www.rfc-editor.org/rfc/rfc5246

The symmetric key is used for encrypting traffic using algorithms like RC5 or AES

Proximo answered 15/10, 2012 at 3:28 Comment(4)
Thank you for your response, but what I was wondering is if this implementation of JSEE's API for TLS is encrypting traffic from the server to the client (the code I have posted) or is any client with the public key going to be able to decrypt the traffic from the server to the client?Remediless
Nope, TLS and SSL imply point-to-point privacy. The symmetric session key generated at the handshake contains a piece of random data generated by the client and encrypted with servers public key. So only those with server's private key are able to decipher it. This random data is used to generate the session key that is used for actual encryption of the traffic. Thus, those without the server's private key won't be able to decipher this data going to the server and subsequently won't have the session key to snoop on the encrypted traffic.Proximo
Well that's good news. Nothing really says "This method generates the session key to secure 2-way communication" in the docs, or if it does it's subtle. After reading up on PKI and finding this tutorial I was thinking I'd have to negotiate another socket from the client to the server after setting up the initial socket and manage different keys for different clients... glad to see this is all handled. BTW the tutorial I found, best one yet, cs.wmich.edu/~alfuqaha/Spring07/cs6030/lectures/jsse.pdfRemediless
@Remediless The handshake does it. There is no specific Java method that does that specifically, but the point is irrelevant. Nothing in the Java documentation has to say that. RFC 2246 says it. Any API whatsoever that implements TLS/SSL has to do what it says in the RFC, which is symmetric encryption in both directions.Paranoid

© 2022 - 2024 — McMap. All rights reserved.