Include intermediate chain in keystore for mutual SSL - java

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();

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.

Selecting specific client certificate from keystore using SAAJ

I have written a Java client in SAAJ which works successfully when sending SOAP messages over HTTP however when I attempt to send any SOAP messages to a web service over HTTPS that requires a client certificate it doesnt work.
At the bottom of the page at the following link - SAAJ Security - it states the following:
From the SAAJ side, all you need to do is use URLs with HTTPS as the protocol. This will work only if the certificate was successfully imported into /jre/lib/security/cacerts; otherwise JSSE will not allow the connection
I imported the client certificate along with the associated root certificate into my Java cacerts as instructed to do so above and ran the program however I get the following error:
java.net.SocketException: Connection reset
I ran a wireshark trace on the traffic and noticed that the Java code isnt presenting a client certificate when asked to do so by the server therefore I have the following questions:
1) By only passing the HTTPS URL to the soapConnection.call() method along with importing the certs to my cacerts file, is this enough for authentication to occur i.e. does SAAJ handle this automatically? Or are there more steps required that arent described in the above link?
2) By importing the certificates into the cacerts file within my JAVA_HOME, does the Java SAAJ client automatically know to look here when calling the soapConnection.call()? Or do I need to explicitly tell my code what cacerts file to use?
3) If the Java SAAJ client is automatically using the cacerts file under my JAVA_HOME then how does it know what client certificate to use? Again, shouldnt I need to explicitly code this or does SAAJ handle this automatically?
Thanks in advance
I managed to figure this out. I used the following code:
static public void doTrustToCertificates() throws Exception
{
// Set truststore that contains root / intermediary certs
System.setProperty("javax.net.ssl.trustStore", "C:\\cert\\trusted.jks");
System.setProperty("javax.net.ssl.trustStorePassword", "changeit");
// Set keystore that contains private key
File pKeyFile = new File("C:\\cert\\privatekey.pfx");
String pKeyPassword = "Password01";
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
KeyStore keyStore = KeyStore.getInstance("PKCS12");
InputStream keyInput = new FileInputStream(pKeyFile);
keyStore.load(keyInput, pKeyPassword.toCharArray());
keyInput.close();
keyManagerFactory.init(keyStore, pKeyPassword.toCharArray());
// Set ssl context with private key and truststore details
SSLContext sc = SSLContext.getInstance("TLSv1");
sc.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom());
SSLSocketFactory sockFact = sc.getSocketFactory();
// Add ssl context to https connection
HttpsURLConnection.setDefaultSSLSocketFactory(sockFact);
}
Then called the doTrustToCertificates() method just before the soapConnection.call() method of SAAJ and it worked like so:
doTrustToCertificates();
SOAPMessage soapResponse = soapConnection.call(soapMsgXml, ENDPOINT_URL);
You weren't so instructed. The instructions are misleading, but they only apply to the situation where the server certificate is self-signed, and not at all to the situation where the client needs a certificate.
In this case, you need to create a client certificate in your client's keystore. Either you create a CSR and get it signed and import that back into the same keystore using the same alias as you started with when you generated the keypair and CSR, or else you need to generate a self-signed certificate (yuck) and export it and get it imported into the server's truststore.
You don't need to write any code. All you need to do is set the system properties javax.net.ssl.keyStore and friends to tell JSSE about your client keystore.

How to load SSL Certificate in Java

I am creating a Java program to get information from a server but I have to perform a ssl handshake with the server from the Java program.
I have myfilercert.cer file certificate for authentication purpose but I have no idea how I can load that certificate in java so that the java program can perform 'handshake' with the server where I want to get information from. Where to begin?
What you need is the java keystore. The keystore is a repository of security certificates used in SSL encryption.
You can read here about the Server Authentication During SSL Handshake. This is a keystore tutorial.
As an alternative to keytool, i would suggest a tool with a Graphical User Interface called Portecle. You can use it to browse the contents of your .cer file and see what's in it.
It can be useful to know about the various certificate encodings. Also read about the X.509 standard.
This is an article on java keytool essentials (which is the oracle tool that works with the java keystore).
You can google and find a lot of resources that instruct you how to generate. I think you will want to keep the certificate at the application level.
Some SO questions that helped me along the way:
Trust Store vs Key Store - creating with keytool - important to know the difference between the trust manager and keymanager
Java HTTPS client certificate authentication
How to export private key from a keystore of self-signed certificate
What is difference between cacerts and keystore
How to connect to a secure website using SSL in Java with a pkcs12 file?
Received fatal alert: handshake_failure through SSLHandshakeException
How to configure trustStore for javax.net.ssl.trustStore on windows?
Good luck!
You can use Apache HttpClient (or just use the required classes from it to use SslContextBuilder, really), and then it'd be like so:
SSLContextBuilder sslContextBuilder = SSLContextBuilder.create();
sslContextBuilder.loadTrustMaterial(new File("yourTrustStore.jks"), "thePassWord");
SSLContext sslContext = sslContextBuilder.build();
HttpsURLConnection httpsURLConnection = (HttpsURLConnection) (new URL("https://thesite.com").openConnection());
httpsURLConnection.setSSLSocketFactory(sslContext.getSocketFactory());
But you need to create a keystore for your certificate, which can be done with keytool. If you need this for android, you'll need SpongyCastle library, and use that as a provider for KeyTool to create a BKS keystore instead of a JKS keystore; and you will need to explicitly open the KeyStore in Java.
KeyStore keyStore = KeyStore.getInstance("BKS",
BouncyCastleProvider.PROVIDER_NAME);
byteArrayInputStream = new ByteArrayInputStream(keyStoreBytes);
keyStore.load(byteArrayInputStream, keyStorePassword);
Certificate[] certificates = keyStore.getCertificateChain("theCertAlias");
Certificate certificate = certificates[0];

Secret Key SSL Socket connections in Java

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

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