Thursday, June 25, 2015

Working with HttpPost and SSL Certificates

This is code to perform an HttpPost to hit a REST endpoint.  It assumes the REST endpoint is expecting the Request to contain data in JSON format.  It also assumes the Response can come back in JSON format.  This will perform the HttpPost and pull the 'status' and 'token' values from the JSON response.

JAVA


The HttpPostSSLHandler code will install an SSL certificate prior to the HttpPost.  This will install in the same location as the Java Development Kit (jdk) being used.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.codehaus.jettison.json.JSONObject;


public class HttpPostRequestAndResponse {
    static String USERNAME = "sizu";
    static String PASSWORD = "myPassword";
    static String APPID = "myApp";
    static String HOST = "rest.host.com";
    static String REST_END_POINT = "http://rest.host.com:443/rest/api/services/authentication";
    
    /**
     * @param args
     * @throws Exception
     */

    public static void main(String[] args) throws Exception {
        System.out.println("Step 1: Install SSL Certificate");
        HttpPostSSLHandler.setupSSLContextByInstallingCertification(HOST);

        System.out.println("Step 2a: Create HttpPost");    
        HttpPost httpPost = new HttpPost(REST_END_POINT);

        System.out.println("Step 2b: Set Headers");
        httpPost.setHeader("Content-Type""application/json");
        httpPost.setHeader("Accept""application/json");

        System.out.println("Step 2c: Set Request for POST");    
        JSONObject jsonRequest = new JSONObject();
        jsonRequest.put("account", USERNAME);
        jsonRequest.put("appName", APPID);
        jsonRequest.put("password", PASSWORD);
        httpPost.setEntity(new StringEntity(jsonRequest.toString(), HTTP.UTF_8));

        System.out.println("Step 3a Start: Send Request/Receive Response");
        String jsonResponseString = EntityUtils.toString(new DefaultHttpClient().execute(httpPost).getEntity());
        System.out.println("Step 3a Finish: jsonResponseString:"+jsonResponseString);

        System.out.println("Step 3b Start: Obtain token from response");
        JSONObject jsonResponse = new JSONObject(jsonResponseString);
        String status = jsonResponse.getString("status");
        String token = jsonResponse.getString("token");
        System.out.println("Step 3b Finish: status:"+status+" token:"+token);
    }
    
    /**
     * Should be called right before connection
     * 
     * Installs C:\Program Files\Java\jdk1.7.0_51\jre\lib\security\jssecacerts file
     * @param host
     * @throws Exception
     */

    public static class HttpPostSSLHandler {
        static Set<String> hostsInstalled = new HashSet<String>();
        public static void setupSSLContextByInstallingCertification(String host) {
            if(hostsInstalled.contains(host)) {
                return;
            } else {
                hostsInstalled.add(host);
            }
            System.out.println("Installing SSLContext Certification into Java Home...");
            try {
                int port = 443;
                char[] passphrase = "changeit".toCharArray();
                
                char SEP = File.separatorChar;
                File file = new File("jssecacerts");
                if (file.isFile() == false) {
                    File dir = new File(System.getProperty("java.home") + SEP
                            + "lib" + SEP + "security");
                    file = new File(dir, "jssecacerts");
                    if (file.isFile() == false) {
                        file = new File(dir, "cacerts");
                    }
                }
                
                System.out.println("Loading KeyStore " + file + "...");
                InputStream in = new FileInputStream(file);
                KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
                ks.load(in, passphrase);
                in.close();
         
                TrustManagerFactory tmf =
                        TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
                tmf.init(ks);
                final X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
                final List<X509Certificate> chainList = new ArrayList<X509Certificate>();
                
                TrustManager tm = new X509TrustManager() {
                    public void checkClientTrusted(X509Certificate[] arg0, String arg1)
                                    throws CertificateException { }
                    public void checkServerTrusted(X509Certificate[] arg0, String arg1)
                            throws CertificateException {
                            chainList.clear();
                            if(arg0 != null) {
                                for (int i = 0; i < arg0.length; i++) {
                                    chainList.add(arg0[i]);
                                }
                            }
                            defaultTrustManager.checkServerTrusted(arg0, arg1); }
                    public X509Certificate[] getAcceptedIssuers() { return null; }
                };
                
                SSLContext context = SSLContext.getInstance("TLS");
                context.init(nullnew TrustManager[]{tm}, null);
                SSLSocketFactory factory = context.getSocketFactory();
         
                System.out.println("Opening connection to " + host + ":" + port + "...");
                SSLSocket socket = (SSLSocket) factory.createSocket(host, port);
                socket.setSoTimeout(10000);
                try {
                    socket.startHandshake();
                    System.out.println("No errors, certificate is already trusted");
                } catch (SSLException e) { }
                socket.close();
         
                if (chainList.isEmpty()) {
                    System.out.println("Could not obtain server certificate chain");
                    return;
                }
         
                System.out.println("Server sent " + chainList.size() + " certificate(s):");
    
                MessageDigest sha1 = MessageDigest.getInstance("SHA1");
                MessageDigest md5 = MessageDigest.getInstance("MD5");
                for (int i = 0; i < chainList.size(); i++) {
                    X509Certificate cert = chainList.get(i);
                    System.out.println
                            (" " + (i + 1) + " Subject " + cert.getSubjectDN());
                    System.out.println("   Issuer  " + cert.getIssuerDN());
                    sha1.update(cert.getEncoded());
                    System.out.println("   sha1    " + toHexString(sha1.digest()));
                    md5.update(cert.getEncoded());
                    System.out.println("   md5     " + toHexString(md5.digest()));
                }
                
                int k = 0;
                X509Certificate cert = chainList.get(k);
                String alias = host + "-" + (k + 1);
                ks.setCertificateEntry(alias, cert);
         
                File dir = new File(System.getProperty("java.home") + SEP
                        + "lib" + SEP + "security");
                file = new File(dir, "jssecacerts");
                OutputStream out = new FileOutputStream(file);
                ks.store(out, passphrase);
                out.close();
                
                System.out.println("Installed SSLContext Certification into Java Home: "+file.getAbsolutePath());
            } catch (Exception ex) { }
        }
    
        /**
         * Used to print ssl certificate message
         */

        private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();
        private static String toHexString(byte[] bytes) {
            StringBuilder sb = new StringBuilder(bytes.length * 3);
            for (int b : bytes) {
                b &= 0xff;
                sb.append(HEXDIGITS[b >> 4]);
                sb.append(HEXDIGITS[b & 15]);
                sb.append(' ');
            }
            return sb.toString();
        }
    }
}

CURL

The corresponding curl command would be

curl -X POST --data '{"account":"sizu","appName":"myApp","password":"myPassord"}' http://rest.host.com:443/rest/api/services/authentication --header "Content-Type:application/json" --header "Accept:application/json"

Common Error: error setting certificate verify locations

curl: (77) error setting certificate verify locations:
CAfile: C:\Users\sizu\Downloads\curl-ca-bundle.crt
CApath: none

The problem is that the system default CA bundle is missing or does not have read access.

Option 1: Use the -k option to use an insecure version of curl without SSL verification

curl -k -X POST --data '{"account":"sizu","appName":"myApp","password":"myPassord"}' http://rest.host.com:443/rest/api/services/authentication --header "Content-Type:application/json" --header "Accept:application/json"

Option 2: Download a default CA bundle file to the missing location

curl http://curl.haxx.se/ca/cacert.pem -o /c/Users/sizu/Downloads/curl-ca-bundle.crt

curl -X POST --data '{"account":"sizu","appName":"myApp","password":"myPassord"}' http://rest.host.com:443/rest/api/services/authentication --header "Content-Type:application/json" --header "Accept:application/json"

Option 3: Download a default CA bundle file and use the --cacert option

curl http://curl.haxx.se/ca/cacert.pem -o cacert.crt

curl -X POST --data '{"account":"sizu","appName":"myApp","password":"myPassord"}' http://rest.host.com:443/rest/api/services/authentication --header "Content-Type:application/json" --header "Accept:application/json" --cacert cacert.crt

Common Error: SSL certificate problem

If you get the following error, this will be hard to solve (root certificates, creating your own certificates, exporting from internet explorer):

SSL certificate problem, verify that the CA cert is OK. Details: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed