Implementing HTTPS client in Java – part 1

JSC
HTTP had become de-facto standard for interprocess communications in all systems where real-time behavior is not a requirement. It’s very convenient in heterogenous environments where more than one programming language is in use or where formal communication contract should be established.
When it comes to the response side, there are a lot of standard servers and frameworks that make it very easy to create, test, deploy, configure and monitor the server-side HTTP code, including Apache Tomcat, Jetty and many more. But when it comes to the client-side of HTTP, it usually requires some more development effort, though it should have been vice versa.
So, we will create an example of a client Java code with support for two-way SSL encryption. In this article, we will only focus on transport layer, i.e. the actual request payloads may be XML, JSON, plain text or whatever else. Suppose, that the server part of the system is already running in some Web server and you know the host, port, application context root and have a certificate to check the server signature. We’ll only touch the client part.
I will make myself some tea (you are encouraged to make coffee if you’re not a tea fan), and let’s start with some required imports and create our class.
 
1
2
3
4
5
6
7
8
9
10
11
12
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.servlet.ServletConfig;

public class OurSSLClient implements X509TrustManager, HostnameVerifier {     Map<X509Certificate, Long> trusted = new HashMap();

Now we have our class, and you probably noticed that HashMap that we declared. This is because we’re not going to verify our server on every call (this is time and resource consuming, and even the best tea may not justify such waste of time). So, when we check the certificate presented, we’re going to store it in the HashMap, with the timestamp of the last verification. In this way we can easily check after configurable timeout if we want.
Also, you should have noticed that our class implements two interfaces, so there are several important methods which we should implement. Let’s do this boring job later, and now focus on interesting part – open connection and read response.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
  String getData(String url, String request) {
int responseCode = 0;
HttpURLConnection connection;
try {
URL httpURL = new URL(url);
connection = (HttpURLConnection) httpURL.openConnection();
//Https- specific setup
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setRequestProperty("Content-Length", Integer.toString(request.length()));
connection.setRequestProperty("Content-Type", "application/json; charset=utf-8");
connection.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(connection.getOutputStream());
wr.writeBytes(json);
wr.flush();
wr.close();
responseCode = connection.getResponseCode();
String response = null;
BufferedReader rd = null;
InputStream is = null;
if (responseCode == 200) {
is = connection.getInputStream();
} else {
is = connection.getErrorStream();
}
rd = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line;
while ((line = rd.readLine()) != null) {
sb.append(line);
}
response = sb.toString();
return response;
} catch (IOException e) {
e.printStackTrace();
return null;
} finally {
connection.disconnect();
}
}
This beautiful code actually opens connection to our server, sends request using HTTP POST method, and then reads response. If your server is pure HTTP, you don’t actually need to do anything else!
But you may be curious enough to ask where is the promised HTTPS. Here is goes! Now replace comment “HTTPS specific setup” with the below code:
            
if (serverURL.startsWith("https")) {
HttpsURLConnection con = (HttpsURLConnection) connection;
SSLContext SSL_CONTEXT = SSLContext.getInstance("TLSv1.2");
KeyManager[] km = getKeyManager();
SSL_CONTEXT.init(km, new TrustManager[]{this}, new java.security.SecureRandom());
con.setSSLSocketFactory(SSL_CONTEXT.getSocketFactory());
con.setHostnameVerifier((HostnameVerifier) this);
}

This code will do the trick but turning our HTTP connection into HTTPS. The only missing thing here is the getKeyManager() method, which we need to implement in order to provide the certificate, so that your client may identify yourself for the server. The implementation normally depends on the PKI infrastructure that is set up in your organization, but if you don’t have any, the most basic implementation will require a JKS storage file and two passwords: keystore password to access the keystore, and a password for the private key that you will use to encrypt and sign the data you are sending to the server, so that the server may verify your identity.

If you experience any difficulties creating keystore and generating private and public keys, please let us know, and we will post an article on this topic.
     
public KeyManager[] getKeyManager(String filePath, String password, String keyPassword) {
KeyStore ks = null;
try {
char[] storepass = password.toCharArray();
char[] keypass = keyPassword.toCharArray();
if (ks == null) {
FileInputStream fin = new FileInputStream(KEY_STORE_PASS);
ks = KeyStore.getInstance("JKS");
ks.load(fin, storepass);
}
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, keypass);
return kmf.getKeyManagers();
} catch (Exception e) {
e.printStackTrace();
} return null;
}
Almost done! Now we will need to implement couple of methods for two-way SSL. First, let’s check that the server we are connecting to actually identifies itself with the certificate given to the correct host:
@Override
public boolean verify(String string, SSLSession ssls) {
if (string == null) {
log.error("hostname is null");
return false;
}
return string.equalsIgnoreCase(ssls.getPeerHost());
}
Next very important method is checkServerTrusted. In almost all manuals in the internet this method just returns true. As you might guess, it’s completely wrong. Instead, it should check the validity of the server certificate including check in the CRL (Certificate Revocation List) that the certificate was not revoked. CRL check is really tricky topic that requires a lot of lines of code, so we will do this in one of the next posts. For now, let’s just return true and save the certificate presented by the server:
    
@Override
public void checkServerTrusted(X509Certificate[] arg0, String crypto) throws CertificateException {
if (arg0 != null) {
for (X509Certificate item : arg0) {
if(!trusted.containsKey(item) || (System.currentTimeMillis() &gt; (trusted.get(item) +3600*1000*24))) {
// Will do the actual check later
trusted.put(item, System.currentTimeMillis());
}
}
}
}

And the last couple of methods:

    
@Override
public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
X509Certificate[] res =newX509Certificate[trusted.size()];
trusted.keySet().toArray(res);
return res;
}
That’s it! Now we have successfully configured HTTPS connection with two-way SSL, and you may start programming real application logic.