CA Certificates in Java – Example of two-way authentication with https

Paddy McCarthy National Center for Atmospheric Research Research Applications Lab Boulder, Colorado 25 November 2009

The following example demonstrates how to set up a secure (https) connection using two-way authentication in Java. For programmers not using a J2EE framework, this document serves to describe the mechanics of setting up a secure connection using Java Secure Socket Extension (JSSE). The Federal Aviation Administration provides an XML source of Notices to Airmen (NOTAMs) data via the Aeronautical Information Data Access Portal (AIDAP), which is managed by the National Airspace System (NAS) Aeronautical Information Management Enterprise System (NAIMES). All connections to AIDAP are through https, using two-way authentication. Two-way authentication requires two keys -- a private key provided by NAIMES for user authentication, and a public key downloaded from the AIDAP server to authenticate the host. Potential users of AIDAP data must contact NAIMES to request access, and are then provided a private key with a password. User authentication is performed in the client code by creating an SSLSocketFactory with the private key, and associating the SocketFactory with the HttpsURLConnection to the AIDAP server. Authentication of the server is performed by downloading the public key from the AIDAP server, and loading it into a local keystore that is referenced by the client application via a runtime property.

Server Authentication Authentication of the server is accomplished by downloading the public key from the AIDAP server and inserting it into a local keystore. The keystore is then referenced within the client application via a java property. The symptom that indicates server authentication is not succeeding in the handshake is a message like the following: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

A handy utility called InstallCert from sun.com takes care of acquiring the server's public key and inserting it into a keystore. There is a link to this java class at: http://blogs.sun.com/andreas/entry/no_more_unable_to_find

The source code itself is located at: http://blogs.sun.com/andreas/resource/InstallCert.java

Download the source code, then compile it using: javac InstallCert

Then run it using a command like the following: java InstallCert www.aidaptest.naimes.faa.gov

When the utility requests input, enter the index number of the certificate you would like to add to your keystore. Simply start with the first certificate, then re-run the utility for each subsequent certificate until they have all been added to your keystore. This operation creates a new keystore file named "jssecacerts" in the directory where you ran the utility. Feel free to rename and/or move the keystore. In order to use the keystore in the handshake, the only necessary step is to reference the keystore file in your application using the "trustStore" property. This can be done on the command line with: "-Djavax.net.ssl.trustStore=/path/to/jssecacerts" or in the code with statements such as: Properties systemProps = System.getProperties(); systemProps.put( "javax.net.ssl.trustStore", "/path/to/jssecerts"); System.setProperties(systemProps);

Note that if you have set a password on the keystore, you will need to also set the "trustStorePassword" property using one of these methods. When specifying property files such as these, they cannot be relative to the classpath -- instead, they must be absolute paths or relative to a given jar file.

After performing these steps, server authentication should be handled when the client connects to the server. As a result, the client application will no longer throw a javax.net.ssl.SSLHandshakeException. If the server does not require user authentication, connections should succeed at this point. However, if user authentication is required by the server, the client should get an error message back from the server indicating that access is denied. For the AIDAP server, here is an example of what is returned when server authentication is in place without user authentication (this will be different for every server): Forbidden

Forbidden

Your client is not allowed to access the requested object. User Authentication If user authentication is required by a server, the administrator of the server will create a public/private key pair for your client and send you the private key and a password. The administrator will register the public key with the server so your client can be authenticated when it connects. The following code fragment associates the private key with the HttpsURLConnection by loading it into an SSLSocketFactory: URL url = new URL( "https://www.aidaptest.naimes.faa.gov/aidap/XmlNotamServlet" ); HttpsURLConnection con = (HttpsURLConnection) url.openConnection(); File pKeyFile = new File("/path/to/your_private_key.pfx"); String pKeyPassword = "your_private_keypass"; 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()); SSLContext context = SSLContext.getInstance("TLS"); context.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom()); SSLSocketFactory sockFact = context.getSocketFactory(); con.setSSLSocketFactory( sockFact );

(Lots of exception handling ommitted)

Complete Code Example Below is a complete code example for a client that connects to the AIDAP server using two-way authentication in order to retrieve a dataset in XML format.

/**=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* ** Copyright UCAR (c) 2009 ** University Corporation for Atmospheric Research(UCAR) ** National Center for Atmospheric Research(NCAR) ** Research Applications Laboratory(RAL) ** P.O.Box 3000, Boulder, Colorado, 80307-3000, USA *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* * Created by Paddy McCarthy on Nov 25, 2009 */ package edu.ucar.rap.util.net;

import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.security.KeyStore; import java.security.SecureRandom; import java.util.Properties; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; public class AidapClient {

public static void main( String[] args ) throws Exception { // Use the public key from the AIDAP server as the trust store for this client. // (note: created this keystore using InstallCerts.java from sun.com) Properties systemProps = System.getProperties(); systemProps.put( "javax.net.ssl.trustStore", "/d1/cvs_all/jssecacerts"); System.setProperties(systemProps);

try { // Open a secure connection. URL url = new URL( "https://www.aidaptest.naimes.faa.gov/aidap/XmlNotamServlet" ); String requestParams = "uid=adds&password=aAsS22.q&active=y&type=F"; HttpsURLConnection con = (HttpsURLConnection) url.openConnection();

// Set up the connection properties con.setRequestProperty( "Connection", "close" ); con.setDoInput(true); con.setDoOutput(true); con.setUseCaches(false); con.setConnectTimeout( 30000 ); con.setReadTimeout( 30000 ); con.setRequestMethod( "POST" ); con.setRequestProperty( "Content-Type", "application/x-www-form-urlencoded" ); con.setRequestProperty( "Content-Length", Integer.toString(requestParams.length()) );

// Set up the user authentication portion of the handshake with the private // key provided by NAIMES Tech Support. // Based on an example posted by Torsten Curdt on his blog: // http://vafer.org/blog/20061010073725 (as of Nov, 2009) File pKeyFile = new File("/d1/cvs_all/aidapuser_1f5d_2011_03_1192.pfx"); String pKeyPassword = "UB#20abba"; 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()); SSLContext context = SSLContext.getInstance("TLS"); context.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom()); SSLSocketFactory sockFact = context.getSocketFactory(); con.setSSLSocketFactory( sockFact );

// Send the request OutputStream outputStream = con.getOutputStream(); outputStream.write( requestParams.getBytes("UTF-8") ); outputStream.close();

// Check for errors int responseCode = con.getResponseCode(); InputStream inputStream; if (responseCode == HttpURLConnection.HTTP_OK) { inputStream = con.getInputStream(); } else { inputStream = con.getErrorStream(); }

// Process the response BufferedReader reader; String line = null; reader = new BufferedReader( new InputStreamReader( inputStream ) ); while( ( line = reader.readLine() ) != null ) { System.out.println( line ); }

inputStream.close(); } catch (Exception e) { e.printStackTrace(); } } }

Contact Paddy McCarthy at: [email protected]