How do I add SSL to an NIO socket client? - java

I have some code to connect to a remote server using the Java NIO libraries. I wrote the code to connect asynchronously with callbacks, and it works great when the host is just a regular TCP server. But when the server uses SSL, it appears that I have to make a bunch of modifications. What I want to do is set up SSL only to the point where it checks to see if the server certificate is valid, i.e. issued by a certificate authority and not expired. I do not want to do anything more extensive than that (like name checking). In other words I just want very basic SSL behavior. Also, I plan to run this on Android, so I really can't do a whole lot of fumbling with external keystores and all that on the client side. I want to do everything I have to internally to the app. If I can avoid any external cert files that would be great.
I'm stuck as to what exactly I need to do in order to proceed. I found some NIO socket setup code here as follows:
private void setupSecurity() {
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextInt();
KeyStore clientKeyStore = KeyStore.getInstance("JKS");
clientKeyStore.load(new FileInputStream("client.jks"), "KeyStorePassword".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(clientKeyStore);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(clientKeyStore, "KeyInKeystorePassword".toCharArray());
sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), secureRandom);
}
And after this, there is a bunch of apparently horrendous SSL handshke code that must be implemented manually. The problem with the above code is that I have no idea what I am supposed to have inside the client.jks file. I don't want to have to have some sort of keystore file to begin with.
I found a library called Naga that claims to implement SSL sockets, but it doesn't work on an SSL socket server I have set up - I get an exception:
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Any suggestions on how to proceed?

You need to use the SSLEngine, which is no joke in itself, and I'm not sure it works correctly on Android, although I am open to correction. The SSLEngine is a state machine and takes a lot of implementing. Frankly having done it I would forget it and use SSLSockets.

This was actually discussed before here: SSH library for Java.
I ended up using Java Secure Channel (JSCH) which is documented here: JSCH javadoc

Related

Question about Java SSL connection between client and server

So I'm wanting to set up an SSL server/client in Java, my knowledge with networking is not very good I've created normal servers/clients in java many times and wanted to up it and setup SSL so a user can't just send false packets to my client thinking its the server sending them.
I've looked up a few examples etc and came across this GitHub repo which shows a basic server with SSL encryption between the client and server and set it up in my IDE.
Here is the repo:
https://github.com/AlphaGarden/SSL-Client-Server
My question is why does the client and server use both certificates? Can't a user just get the certificates from the client and use them to decrypt the SSL? Also in the client there's 2 strings, password & password2... Am I supposed to hide these from the person using my client too? If not am I supposed to hide anything client sided from the user that could help them decrypt the SSL traffic and feed my client false information?
Just some basics to explain a SSL/TLS connection: Wkipedia https://en.wikipedia.org/wiki/Transport_Layer_Security, for TLS 1.2 RFC5246: https://www.rfc-editor.org/rfc/rfc5246 and for TLS 1.3 RFC8446 https://www.rfc-editor.org/rfc/rfc8446.
The basic principle for a secure connection is to use (each) a certificate on server and client's side and exchange them. The certificate itself is useless as it does not prove that you are whom you say to be. To get trust in the certificate the usual way is to "buy" a certificate from a Certificate Agency (CA) that checks your identity.
The CA's root certificate usually is known to today browsers and so the server and client certificate can get checked
by the browser (client) and server against the CA's root certificate.
Let us see the code for the simple SSL Server & Client code. I'm for sure you noticed that the server and client are using
"twisted" sources for the keystore and the truststore and therefore they need two (different) passwords to get access to the two stores:
SSLServer:
String password = "abcdefg";
InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream("server/certificate-server.p12");
// TrustManagerFactory
String password2 = "aabbcc";
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("PKIX", "SunJSSE");
InputStream inputStream1 = ClassLoader.getSystemClassLoader().getResourceAsStream("client/certificate-client.p12");
SSLClient:
String password = "aabbcc";
InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream("client/certificate-client.p12");
// TrustManagerFactory ()
String password2 = "abcdefg";
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("PKIX", "SunJSSE");
InputStream inputStream1 = ClassLoader.getSystemClassLoader().getResourceAsStream("server/certificate-server.p12");
What are the keystore and the truststore good for? The keystore holds the (own) private key (of server and client) and in the truststore (all) trusted certificates (usually the CA root certificates) are saved. To check the received certificate from the other party the server and client prove the certificate chain up to the root certificate from the CA.
But here is the problem - as the programs are using self signed certificates there is not "real" and saved root certificate available in the trust store. To get the programs to run without an user interaction ("do you trust this certificate ?") both provide use a truststore with the "approved" certifcates and all is running.
To your second question "Can't a user just get the certificates from the client and use them to decrypt the SSL" the answer is simple: YES. But when securing a communication only between "allowed" partners - how should e.g. the servers knows that the client is the real one and not an attacker? For that reason the client is sending a certificate as well that can get checked by the servers
truststore.

Include intermediate chain in keystore for mutual SSL

I've been at this way too long and have searched high and low for a solution.
I am trying to do mutual SSL. The target is using a certificate signed by an intermediate that is valid and trusted. My side, a Java web service client using httpclient 4.3.6, is also using a certificate signed by the intermediate.
The intermediate is in a JKS format keystore and has the full chain. My certificate is also in a JSK keystore.
I am also running Wireshark to follow the SSL process, since the Java exceptions are very unhelpful. I can see that the Server Hello finishes (a line in Wireshark called "Server Hello, Certificate, Server Key Exchange, Certificate Request, Server Hello Done"). A couple of packets later, there is a "Certificate, Client Key Exchange" line that seems to run ok. Two packets later, there is an alert about a handshake failure (40). When I go backward two packets to the "Certificate, Client Key Exchange" line and inspect it closely, I can see that while the key was sent off, there is a line that says "Certificates Length: 0" in the packet inspection window.
So it appears that my certificate is not even being sent? I saw on another question here that it may be because my side doesn't see a valid path to my cert, and so doesn't bother including it? (The answer here https://stackoverflow.com/a/14876605/5136448). I do include the trust store that has my intermediate in it when using SSLContext and SSLConnectionSocketFactory.
A colleague of mine got a successful connection using openssl when including my keystore and intermediate together, so I'm trying to add the certificate chain from the intermediate keystore to my own keystore to see if that will solve it. I have no found any successful way of doing this (openssl, keytool, Keystore Explorer), so I may not have the right idea as to properly solve this.
To sum up, using Apache http-client 4.3.6, I need to do mutual SSL to a server where the server is trusted, but the client doesn't seem to be sending its certificate (which was signed by the same entity that signed the server's certificate).
Should it be relevant, here is the client-side code:
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(new FileInputStream(new File("/path/to/test.keystore")), "abc123".toCharArray());
KeyStore trustStore = KeyStore.getInstance("PKCS12");
trustStore.load(new FileInputStream(new File("/path/to/test.truststore")), "123abc".toCharArray());
SSLContext sslContext = SSLContexts
.custom()
.loadKeyMaterial(keystore, "abc123".toCharArray(), null)
.loadTrustMaterial(trustStore, null)
.build();
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
new String[]{"TLSv1.2"},
null,
SSLConnectionSocketFactory.STRICT_HOSTNAME_VERIFIER);
CloseableHttpClient httpclient1 = HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build();

Where to put keystore for Tomcat web app using Apache HTTP client

I am writing a routine to access a remote server. This server I am connecting to requires mutual authentication so I have to provide a keystore, and while I'm at it I'd like to put a proper truststore in place as well.
I can find plenty of tutorials on how to create a keystore with keytool and multiple ways to get an Apache HTTP client to recognize it, but not where to store it in a Tomcat environment so that the application can find it. Somehow putting it in the application's war file seems like a bad idea to me.
Again, this is not to permit Tomcat to handle inbound https connections - I have a reverse proxy set up by our admin team for that. I'm creating outgoing https connections that require mutual authentication, i.e., both accepting a self-signed destination server certificate, and providing my server's self-signed client certificate.
Where do you store the actual keystore and truststore files in a Tomcat environment for use by a web application?
You can put your keystore wherever you want, as long as you know how to tell httpclient where to load the keystore.
That, of course, is the trick.
Using Apache httpclient for https
Buried in all that mess of code in the accepted answer is the key (ha!) to using httpclient with your own custom keystore. It's unfortunate that httpclient doesn't have a simple API like "here's the path to my keystore file, now use it" or "here are the bytes for my keystore, use those" (if you wanted to load the keystore from the ClassLoader or whatever), but that seems to be the case.
The honest truth is that using keystores and truststores in Java is messy business, and there's usually no way around it. Having written a client-cert-capable HTTP client myself using nothing other than HttpsURLConnection and then also adding raw-socket components to that, I know how painful it is.
The code in the above-linked article is fairly straightforward if a bit verbose. Unfortunately, you're going to need to make it a lot messier for production-quality code because you've got to do error-checking, etc. for every step of the process to make sure your service doesn't fall-over when you are trying to set up the various stores and make your connection.
This is basically a comment to Christopher Schultz's answer, but since it involves some code snippets please excuse my putting it here
It's unfortunate that httpclient doesn't have a simple API like
"here's the path to my keystore file, now use it" or "here are the
bytes for my keystore, use those" (if you wanted to load the keystore
from the ClassLoader or whatever), but that seems to be the case
This is how one can configure Apache HttpClient 4.3 to use a specific trust store for SSL context initialization.
SSLContext sslContext = SSLContexts.custom()
.loadTrustMaterial(trustStore)
.build();
CloseableHttpClient client = HttpClients.custom()
.setSslcontext(sslContext)
.build();
One can load trust material from a resource like that
URL resource = getClass().getResource("/com/mycompany/mystuff/my.truststore");
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream inputStream = resource.openStream();
try {
trustStore.load(inputStream, null /*usually not password protected*/);
} finally {
inputStream.close();
}

How to request a URL that requires a client certificate for authentication

I need to request a URL from a server that uses client certificates for authentication and can't find a way to do this for my application.
My problem is that the Java client I'm working on has the certificate file available locally but due to restrictions on the PCs it will be running on it cannot install the certificate in a keystore.
In short, I just want to be able to explicilty specify the certificate to use for the URL I need to retrieve.
Any suggestions?
It's not clear what the restrictions you're talking about are. More specifically, I'm not sure what you consider the difference between the local certificate file and a keystore. Most keystores are file-based, so you might be able to load the file this way directly, without needing an installation process. Are the restrictions related to the security policies used by the JVM itself (which may prevent you from instantiating KeyStores)?
First, it's not just the certificate you need on the client side, but its private key. Often, people use the word "certificate" in this context to mean both, but you do need to make sure your file doesn't contain the certificate without the private key. Typically, you'll find the combination of private key + certificate in a PKCS#12 file (.p12/.pfx), a lot of tools import/export in this format; it's also a keystore format natively supported by the Sun JVM (type PKCS12).
To make this work, you need to configure what makes the connection with the appropriate keystore. SSL/TLS client-certificate authentication is always initiated by the server: the client responds with a certificate if it has one (and wants to use it). To configure it for a specific URL, you need to find out what makes the connection (perhaps HttpsURLConnection) and set it there (unless it's set up in the default context -- even if it's set up in the default context, it will only be used for servers that request it).
To set up the keystore globally on the JVM (which may be what your restrictions prevent you to do), you can set the javax.net.ssl.keyStore javax.net.ssl.keyStorePassword (and related) system properties. (Because the password could be visible, it's better not to do it on the command line).
These system properties are used for the configuration of the default SSLContext (which is used, often transparently, by libraries or classes such as HttpsURLConnection to build the SSLSocketFactory and then SSLSocket, initialized with those properties).
You could build SSLContext from your file specifically for use for that connection. The SSLContext is effectively a factory for the SSLSocketFactory or SSLEngine, and you can set the SSLSocketFactory in a given HttpsURLConnection.
The following would build an SSLContext using "/path/to/file.p12" as your keystore (that is the one with your private key and the certificate you're going to send) and keep the default settings for the truststore (you'd need to catch the exception for the input stream too).
KeyStore ks = KeyStore.getInstance("PKCS12");
FileInputStream fis = new FileInputStream("/path/to/file.p12");
ks.load(fis, "password".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, "password".toCharArray());
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(kmf.getKeyManagers(), null, null);
From there you can configure the connection like this (if this is what you're using):
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
if (connection instanceof HttpsURLConnection) {
((HttpsURLConnection)connection)
.setSSLSocketFactory(sc.getSocketFactory());
}
Some libraries will let you pass an SSLContext directly (Apache HTTP Client 4 supports that, and this can be done with Apache HTTP Client 3 using this.)
Note that you don't need to provide the password as a direct parameter when loading the keystore, you could also use a callback (maybe better from the GUI point of view).
Perhaps this library could help (but it's not necessary): you could use the KeystoreLoader for its helpers to do this. There are also SSLContextFactories in this libraries (but you would probably not need any of the wrappers as they tend to be for customizing the trust management or key selection).
This is generally how using a client-certificate is configured, but it's difficult to provide more details without clarifications regarding what your restrictions exactly are (and which libraries you're using).
You can change the default JSSE keystore through a property when you invoke your app, -Djavax.net.ssl.keystore=somefile.
So you could import your cert with the keytool command into a keystore and invoke your app pointing to that, e.g.
keytool -importcert -file mycert -keystore mystore
java -Djavax.net.ssl.keystore=mystore ...

How do I sign a HTTP request with a X.509 certificate in Java?

How do I perform an HTTP request and sign it with a X.509 certificate using Java?
I usually program in C#. Now, what I would like to do is something similar to the following, only in Java:
private HttpWebRequest CreateRequest(Uri uri, X509Certificate2 cert)
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
request.ClientCertificates.Add(cert);
/* ... */
return request;
}
In Java I have created a java.security.cert.X509Certificate instance but I cannot figure out how to associate it to a HTTP request. I can create a HTTP request using a java.net.URL instance, but I don't seem to be able to associate my certificate with that instance (and I'm not sure whether using java.net.URL is even appropriate).
I'm not a C# programmer, but I'm presuming that code makes a call using HTTPS/TLS and provides a client certificate for authentication? Aka, you're not asking how to use WS-Security, right?
In that case, I think the answers here and here will be of use to you. You need to use an openssl utility to import your certificate into a p12 client keystore. If your server is using a non-standard CA or self-signed cert, you'll need to set up a client truststore with those certificates as well.
At this point, look at the questions I've linked: you'll need to specify a whole slew of JVM arguments. Finally, try to make your call again (using standard Java objects or httpclient). The client should accept the server cert if your truststore is correct and the server should ask for a client cert. If your keystore is set up correctly, the client with authenticate with the X.509 client cert and you'll be good to go.
It looks like you're trying to use HTTPS with client-certificate authentication. I'm assuming that your server is configured to request this (because the client certificate can only be requested by the server).
In Java, java.security.cert.X509Certificate is really just the certificate (a public key certificate, without the private key). What you need on the client side is to configure the private key with it.
Assuming that your private key and certificate are in a keystore (to simplify, I'm assuming there's only one suitable certificate with its private key, perhaps with other certificates in the chain, in that keystore), and that you're using the default trust store:
KeyStore ks = ...
/* load the keystore */
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, password);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), null, null);
URL url = new URL("https://example/");
HttpsURLConnection connection = (HttpsURLConnection)url.openConnection();
connection.setSSLSocketFactory(sslContext.getSSLSocketFactory());
Other libraries will allow you to set the SSLContext or the KeyStore slightly differently, but the principles should be the same.
(You could also use the javax.net.ssl.keyStore system properties if it's appropriate.)
I would recommend the open source HttpClient library from Apache Commons. Covers this use case and many others.

Categories

Resources