Java JSSE TLS - Is this connection safely encrypted in both directions? - java

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.
I have a public / private key pair stored in files public.key and private.key (i made these using JSSE's keytool utility
I included public.key in the client
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.

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.)

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

Related

SSL/TLS dynamic key generation

On this project of mine I needed to implement secure connection using SSL/TLS between a client and a server. I found a good article about that so I've managed to do my task without any problem.
This is the article.
My question is pretty simple but I cannot find an answer anywhere. In this particular case, my clients have the same key in the SSL protocol which is created through tutorial on a previous link and put in some kind of a file. Potential problem in this process is that someone can access that file and since every client has that key, someone can listen to all connections.
What I wanted to ask, is there any chance to dynamically generate keys every time some client wants to access the server and put the generated key in the server truststore?
UPDATE
public static final String PATH_TO_ANDROID_KEYSTORE = "and/client.bks";
public static final String PATH_TO_ANDROID_TRUSTSTORE = "and/clienttruststore.bks";
String pathToKeyStore = PATH_TO_ANDROID_KEYSTORE;
String pathToTrustStore = PATH_TO_ANDROID_TRUSTSTORE;
KeyStore keyStoreKeys = KeyStore.getInstance(keyStoreType);
keyStoreKeys.load(Gdx.files.internal(pathToKeyStore).read(), passphrase);
KeyStore keyStoreTrust = KeyStore.getInstance(keyStoreType);
keyStoreTrust.load(Gdx.files.internal(pathToTrustStore).read(), passphrase);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStoreKeys, passphrase);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStoreTrust);
This is the client code and seems like clients have exported server's certificates in their truststores but they actually use the same private key that is generated only once in the keystore using openssl tool.
In this particular case, my clients have the same key in the SSL protocol which is created through tutorial on a previous link and put in some kind of a file.
Unclear. Do you mean they share the same private key? If so, that is a flaw in your system design. Every client should have its own private key. Otherwise the private key isn't, err, private. And access to that key should be via a keystore whose password only the applicion knows, which provides at least another line of defence.
If you just mean that they all have an exported copy of the server's certificate, in their truststores, there is no security risk attached to that at all: it is perfectly normal.
Potential problem in this process is that someone can access that file and since every client has that key, someone can listen to all connections.
No they can't. SSL is immune to man-in-the-middle attacks provided you don't compromise your server's private key, but if you're talking about client private keys they can masquerade as a real client even if they aren't, if they can break through the keystore-password barrier.
What I wanted to ask, is there any chance to dynamically generate keys every time some client wants to access the server and put the generated key in the server truststore?
Not securely, and not online. If your genuine clients can do it, so can an attacker. That's why trust material must be distributed offline.

Using a letsencrypt certificate in a java web server

I opted to use acme4j to create a letsencrypt certificate. So far it seems to have worked perfectly and I have some java code that creates a registration, responds to a challenge an ultimately presents me with a x509 certificate for my domain (along with a 'certificate chain'). The code is integrated nicely into my java application and doesn't require any downtime for certificate renewal. Awesome.
From here I'm a bit stuck. My application is a just a main app that has an embedded undertow webserver that I instantiate programatically. In order to create an https listener I need to create an SSLContext object. I've saved the x509 certificate that I got from letsencrypt to disk so it can be reused:
...
X509Certificate x509 = cert.download();
Path pemFile = pathTo(domain + ".pem");
try (Writer writer = Files.newBufferedWriter(pemFile); JcaPEMWriter jcaPEMWriter = new JcaPEMWriter(writer)) {
jcaPEMWriter.writeObject(x509);
}
And then on start up my application reloads that certificate and passes it into the undertow web server:
CertificateFactory cf = CertificateFactory.getInstance("X.509");
FileInputStream finStream = new FileInputStream(certFile.toFile());
X509Certificate x509Certificate = (X509Certificate)cf.generateCertificate(finStream);
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
keyStore.setCertificateEntry("someAlias", x509Certificate);
TrustManagerFactory instance = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
instance.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, instance.getTrustManagers(), null);
SSLContext.setDefault(sslContext);
Undertow.Builder builder = Undertow.builder();
builder.addHttpsListener(httpsPort, ipAddress, sslContext);
The app starts, can't see any errors or warnings until I try and hit an https endpoint where Chrome just shows _ERR_CONNECTION_CLOSED_. I turned on -Djavax.net.debug=all to try and see whats going on:
%% Initialized: [Session-12, SSL_NULL_WITH_NULL_NULL]
XNIO-1 task-12, fatal error: 40: no cipher suites in common
javax.net.ssl.SSLHandshakeException: no cipher suites in common
%% Invalidated: [Session-12, SSL_NULL_WITH_NULL_NULL]
XNIO-1 task-12, SEND TLSv1.1 ALERT: fatal, description = handshake_failure
XNIO-1 task-12, WRITE: TLSv1.1 Alert, length = 2
XNIO-1 I/O-2, fatal: engine already closed. Rethrowing javax.net.ssl.SSLHandshakeException: no cipher suites in common
XNIO-1 I/O-2, called closeInbound()
XNIO-1 I/O-2, fatal: engine already closed. Rethrowing javax.net.ssl.SSLException: Inbound closed before receiving peer's close_notify: possible truncation attack?
2016-09-08 08:34:46,861 DEBUG [io] - UT005013: An IOException occurred
java.io.IOException: javax.net.ssl.SSLException: Inbound closed before receiving peer's close_notify: possible truncation attack?
I'm trying to come up with a pure java solution here. Something that is repeatable, in code and can be tested and checked in to source control. I want to avoid having to do any out-of-jvm machine level set up if possible.
After lots of hackery and reading, it seems like I need to use the keytool and some combination of the certificate I was issued, along with the certificate chain I was issued, along with the root certificate and some/none/all of the intermediate letsencrypt certificates! Seriously?
I tried following the instructions here from the section titled "7.3.1.3. Using an existing Certificate" but only to end up with exactly the same error.
Any help would be greatly appreciated.
An SSL/TLS server (in any language) requires a PRIVATE KEY AND certificate (and nearly always a cert chain) not just a certificate. And a Java SSL/TLS server needs that privatekey+certchain in a KeyManager not a TrustManager -- any advice that recommended you set only a TrustManager for an SSL/TLS server is totally incompetent. The section 7.3.1.3 you link to makes no sense, although the next section 7.3.1.4, although it claims a bug I have never seen and I have used just about every version of OpenSSL, does describe almost the right way to convert OpenSSL key+cert to Java, which matches the earlier sections which described creating keys and certs in OpenSSL format. But you don't have OpenSSL format.
That acme4j page
correctly says you should "generate a separate pair of keys" -- pair meaning private and public -- which the earlier "How to Use" page told you how to do with their KeyPairUtils, then generate a CSR and send it in, and get the certificate and chain. It specifies a single call
X509Certificate[] chain = cert.downloadChain()
which might be what you need if LetsEncrypt is clever; look at chain[0].getSubjectX500Principal() and see if it's you. If not you probably need to do both download() and downloadChain() and put them together in one array with your cert first:
X509Certificate[] fixchain = new X509Certificate [chain.length+1];
fixchain[0] = mycert; System.arraycopy (chain,0, fixchain,1, chain.length);
chain = fixchain; // for simplicity
Once you have the cert chain AND the key pair, do something like
char[] password = /* some value, should be secure if used for file,
if used only in memory doesn't really matter */
KeyStore ks = KeyStore.getInstance("jks"/*or default if you don't care*/);
ks.load (null); // above line and this same as you have now
ks.setKeyEntry ("alias", keypair.getPrivateKey(), password, chain);
// this line different -- KeyEntry not CertificateEntry
// if you want to save in a file for reuse
try( OutputStream os = new FileOutputStream ("blah") ){ ks.store (os, password); }
// if you want to save somewhere else, extrapolate
// if/when you want to run server
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, password); // same password as above (or when stored)
// MAYBE TrustManager if you want to require certs FROM CLIENTS
// in which case the certs you trust probably shouldn't be limited to LetsEncrypt
SSLContext ctx = SSLContext.getInstance("TLS";
ctx.init(kmf.getKeyManagers(), null /*or tmf.getTrustManagers()*/, null);
SSLContext.setDefault (ctx); // or otherwise use ctx
FYI if a certificate had actually been what you needed to store and read, you don't need to convert it to PEM unless you want people to cut&paste etc. Java CertificateFactory has handled both DER and PEM for at least a decade.

SSLSocket: why do we need to do the handshake before accessing the server certificate?

I'm connecting through a SSLSocket to a distant host which is using a certificate for the handshake. As we don't use the default JVM truststore with all the certificate authorities, i need to add the remote host certificates to my truststore.
How can i get the certificates that i should trust from the SSLSocket? It seems do retrieve them i need to use the SSLSession which seems to require the handshake.
Why do we need to perform the handshake to be able to retrieve the certificates?
Is there any tool that permits to extract the remote host certificates used?
Actually certificates are presented during the handshake, so that the server can identify itself, and the eventually the same for the client.
When you do:
SSLContext context = SSLContext.getInstance("TLS");
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
[...]
SavingTrustManager tm = new SavingTrustManager(defaultTrustManager);
SSLSocket socket = (SSLSocket)factory.createSocket(host, port);
try {
socket.startHandshake();
socket.close();
} catch (SSLException e) {
e.printStackTrace(System.out);
}
if you don't get an exception on startHandshake(), it means the certificate is already trusted for some reason (directly present in keystore, of signed by a trusted entity).
Exception occurred or not, you can access the downloaded chain:
X509Certificate[] chain = tm.chain;
if (chain == null) {
// error in downloading certificate chain
return;
}
// loop through chain
for (int i = 0; i < chain.length; i++) {
X509Certificate cert = chain[i];
[....]
}
With the X509Certificate object instance, you can actually update your k-ieth keystore:
X509Certificate cert = chain[k];
String alias = host + "-" + (k + 1);
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
[...]
ks.setCertificateEntry(alias, cert);
OutputStream out = new FileOutputStream("jssecacerts");
ks.store(out, passphrase);
out.close();
Look here for the complete sample.
Alternatively, another maybe safer way to download the certificate for a server you trust, is using openssl command:
# openssl s_client -showcerts -connect $SERVER:$PORT 2>&1 | \
sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' >/tmp/$SERVERNAME.cert
then import it as usual with keytool.
Normally, you shouldn't get the certificate you should trust from an SSLSocket, instead, it should be a configured setting that you obtained independently, as a reference for what you want to trust.
What you seem to want to do is to get the certificate for the first connection, hoping that that connection wasn't intercepted, and then use this as a reference for subsequent connections (similar to what's commonly done with SSH, when you don't necessarily know the server key's fingerprint on the first connection, but check that you get the same later).
Security-wise, this isn't ideal because the initial connection may be intercepted by a MITM attacker (which would make all subsequent connections vulnerable), but that's certainly a way to mitigate the risks. Ideally, you should compare that certificate with a known reference you've obtained some other way.
You can access the remote certificate during the handshake using a custom X509TrustManager (or you can disable trust verification with it and get the certificate later), which you can then use to initialise an SSLContext, from which you can obtain your SSLSocketFactory. It's generally a bad idea to disable trust verification in a trust manager (since it opens the connection to MITM attacks), but it can be acceptable for this purpose. You may be interested in the InstallCert utility, which should do more or less what you're after.
Why do we need to do the handshake before accessing the server
certificate?
This is done during the handshake, because the purpose of the SSL/TLS socket API is to provide the application layer with a socket it can consider secure and use more or less as a normal socket at that stage. Typically, for most uses of JSSE (or generally other SSL/TLS stacks), as an application developer using that stack, you don't want to have to do the verification explicitly. Checking the certificate during the handshake is also recommended as part of the TLS specification:
Upon receipt of the server hello done message, the client SHOULD
verify that the server provided a valid certificate, if required
and check that the server hello parameters are acceptable.

How can I effect 'known_hosts' vs self-certified servers for my app's SSL usage?

Oh wise and noble Oracle,
I'm adding SSL to a TCP client I've written on my Android phone. I can
successfully connect to servers with properly signed certificates, and I can
connect to self-certifying hosts by cooking up a TrustManager implementation
that always thinks everything is fine.
I now have a decorator TrustManager capturing the certificates (before
delegating to its decoratee) for self-certifying hosts and presenting them for
my breathless perusal, but what I can't work out is how to implement ssh's
behaviour of warning that a host is unknown and offering to remember it for
next time - and doing so.
I presumed all I needed was to store the public key - as ssh does with
known_hosts - and re-represent it, but with this code and 'sslTrust' holding
the public key:
TrustManagerFactory tmf = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null); // initialise!
ks.setKeyEntry("dbentry", Base64.decode(sslTrust, Base64.NO_WRAP), null);
tmf.init(ks);
tms = tmf.getTrustManagers();
ss.stm = new SnoopyTrustManager((X509TrustManager) tms[0]);
// ...
SLContext context = SSLContext.getInstance("SSL");
context.init(null, new TrustManager[] { ss.stm } , null);
ss.factory = context.getSocketFactory();
// ...
SocketFactory factory = ss.getFactory();
mSocket = factory.createSocket(host, port);
attempting to establish a connection results in
SSLHandshakeException: InvalidAlgorithmParameterException: trustAnchors.isEmpty()
which is fair enough: I don't know how to cook things up from the certificate
offered by the remote server. I'm also fairly sure this isn't how I tell a
TrustManager about a remote server's public key anyway.
Since the site is self-certifying, I imagine could probably just verify that
the public keys match in a trivial TrustManager, but I'd like to understand
how this 'should' be done - adding a CA on a per-connection basis, since
I won't trust that CA for anything else.
You need to use your own trust store on pre-ICS version, and add the serer's certificates to it on first error. Subsequent connections will load it from the trust store and thus trust the remote certificate. This is not a complete solution, but here's one way to do it (code on Github), along with some discussion:
http://nelenkov.blogspot.jp/2011/12/using-custom-certificate-trust-store-on.html

SSL and SocketChannel

Ideally, I only need a simple SSLSocketChannel.
I already have a component that reads and writes message over ordinary SocketChannel, but for some of these connections, I have to use SSL over the wire; the operations over these connections, however, are the same.
Does anyone knows a free SSLSocketChannel implementation (with the appropriate selector) or something similar? I've found this, but the selector doesn't accept it since its vendor isn't SUN.
I'm decoupling the reading_from/writing_to net logic from the insertion and retrieval of network data via a simple object, in order to use a SSLEngine without getting mad, but it's really tricky to implement that correctly, given the fact that I don't know the internals of SSL protocol...
Jetty has an NIO SSL implementation for their server: SslSelectorChannelConnector. You might want to peek at it for details on what its doing.
There is also an old (but decent) article from O'Reilly that explains the details about NIO + SSL along with example code.
TLS Channel is a simple library that does exactly that: wrapping a SSLContext (or SSLEngine) and exposing a ByteChannel interface, doing the heavy lifting internally.
(Disclaimer: I am the library's main author).
Check out Restlet's implementation it may do what you need, and it's all about NIO.
Restlet Engine Javadoc
Specifically the HttpClientCall. SetProtocol(HTTPS) - getResponseEntityChannel returns a ReadableByteChannel (getEntityChannel returns a WriteableByteChannel)
Not sure if this is what you're looking for, but may help... To create SSL/TLS enabled server sockets, I'm currently using code like the following (keystore.jks contains a self signed private/public key pair used for securing confirmation) - clients have a similar trust store which contains the signed certificate with the public key of that pair.
A bit of googling around getting that configured should get you underway.
String keyStorePath = "keystore.jks";
String keyStorePassword = "password";
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
KeyStore keyStore = new KeyStore();
keyStore.load(new FileInputStream(keyStorePath), keyStorePassword);
keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());
sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom());
SSLContext sslContext = getServerSSLContext(namespace.getUuid());
SSLServerSocketFactory serverSocketFactory = sslContext.getServerSocketFactory();
// Create sockets as necessary

Categories

Resources