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:
The source code itself is located at:
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):
<HTML><HEAD><TITLE>Forbidden</TITLE></HEAD>
<BODY>
<H1>Forbidden</H1>
Your client is not allowed to access the requested object.
</BODY></HTML>
<BODY>
<H1>Forbidden</H1>
Your client is not allowed to access the requested object.
</BODY></HTML>
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.
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(); }
}
}
No comments:
Post a Comment