In my last article, I showed how to remove all security from a secure web (https) transaction by installing dummy trust manager and host name verifier objects into an SSLSocketFactory. Today, I’m going to take it to the next level by demonstrating how to create a private key and self-signed certificate in a JKS keystore, exporting the public key certificate to a client-side trust store, and configuring our client to use the trust store to verify our server.
I’ll be using a Tomcat 6 server – mainly because it’s almost trivial to install and configure for SSL traffic. On my OpenSuSE 11.1 64-bit GNU/Linux machine, I’ve installed the tomcat6 package, and then I’ve gone into YaST’s service management panel and enabled the tomcat6 service.
Self-Signed Certificates
Let’s start by generating the proper keys. First, we’ll generate the server’s self-signed certificate, with embedded public/private key pair. For the common name (CN) field, I’ll make sure to enter the fully qualified domain name of my server (jmc-linux-64.provo.novell.com). This will ensure that my Java client code will properly compare the hostname used in my URL with the server’s certificate. Using any other value here would cause my client to fail with an invalid hostname exception. Here’s the Java keytool command line to create a self-signed certificate in a JKS key store called jmc-linux-64.keystore.jks:
$ keytool -genkey -alias jmc-linux-64 \ -keyalg RSA -keystore jmc-linux-64.keystore.jks Enter keystore password: password Re-enter new password: password What is your first and last name? [Unknown]: jmc-linux-64.provo.novell.com What is the name of your organizational unit? [Unknown]: Engineering What is the name of your organization? [Unknown]: Novell, Inc. What is the name of your City or Locality? [Unknown]: Provo What is the name of your State or Province? [Unknown]: Utah What is the two-letter country code for this unit? [Unknown]: US Is CN=jmc-linux-64.provo.novell.com, OU=Engineering, O="Novell, Inc.", L=Provo, ST=Utah, C=US correct? [no]: yes Enter key password for (RETURN if same as keystore password): <CR> $
To view the new certificate and key pair, just use the -list option, along with the -v (verbose) option, like this:
$ keytool -list -v -keystore jmc-linux-64.keystore.jks Enter keystore password: password Keystore type: JKS Keystore provider: SUN Your keystore contains 1 entry Alias name: jmc-linux-64 Creation date: Jun 19, 2009 Entry type: PrivateKeyEntry Certificate chain length: 1 Certificate[1]: Owner: CN=jmc-linux-64.provo.novell.com, OU=Engineering, O="Novell, Inc.", L=Provo, ST=Utah, C=US Issuer: CN=jmc-linux-64.provo.novell.com, OU=Engineering, O="Novell, Inc.", L=Provo, ST=Utah, C=US Serial number: 4a3c006f Valid from: Fri Jun 19 15:17:35 MDT 2009 until: Thu Sep 17 15:17:35 MDT 2009 Certificate fingerprints: MD5: E5:37:9F:85:C9:76:60:FC:DC:01:81:AD:5F:FC:F4:9A SHA1: FD:E3:47:6C:AE:9B:75:3B:9C:6C:05:7B:C9:A4:B4:E6:07:F6:B5:FB Signature algorithm name: SHA1withRSA Version: 3 ******************************************* ******************************************* $
Server Configuration
Okay, now we have a server certificate with public and private key pair in a JKS keystore. The next step is to configure Tomcat to listen for https requests. The default configuration for Tomcat is to run a bare http server on port 8080. To enable the https server on port 8443, I edited the /usr/share/tomcat6/conf/server.xml file and uncommented the default entry for SSL that was already in place as a comment:
... <!-- Define a SSL HTTP/1.1 Connector on port 8443 This connector uses the JSSE configuration, when using APR, the connector should be using the OpenSSL style configuration described in the APR documentation --> <Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" maxThreads="150" scheme="https" secure="true" keystoreFile="/jmc-linux-64.keystore.jks" keystorePass="password" clientAuth="false" sslProtocol="TLS" /> ...
Make sure the sslProtocol is set to at least “SSLv3″ – I just used “TLS” here. The important fields, however, are the keystoreFile and keystorePass fields, which I’ve set to the keystore we created in the previous step, and its password. You can put the keystore file anywhere on your file system accessible by the user running the tomcat service. On my system, the tomcat6 service is executed as root by default, so I just copied my keystore to the root of my file system.
After editing the file, I had to restart the tomcat6 service:
# rctomcat6 restart Shutting down Tomcat (/usr/share/tomcat6) ... done Starting Tomcat (/usr/share/tomcat6) ... done #
Client-Side Trust Store
So much for server configuration. Now we have to configure the client’s trust store with the server’s self-signed certificate. This is done by exporting the certificate and public key from the server’s keystore, and then importing it into a client trust store. A trust store is just a JKS keystore that contains only trust certificates:
$ keytool -export -alias jmc-linux-64 \ -keystore jmc-linux-64.keystore.jks -rfc \ -file jmc-linux-64.cert Enter keystore password: password Certificate stored in file $ $ cat jmc-linux-64.cert -----BEGIN CERTIFICATE----- MIICezCCAeSgAwIBAgIESjwAbzANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UEBhMCVVMxDTALBgNV BAgTBFV0YWgxDjAMBgNVBAcTBVByb3ZvMRUwEwYDVQQKEwxOb3ZlbGwsIEluYy4xFDASBgNVBAsT C0VuZ2luZWVyaW5nMSYwJAYDVQQDEx1qbWMtbGludXgtNjQucHJvdm8ubm92ZWxsLmNvbTAeFw0w OTA2MTkyMTE3MzVaFw0wOTA5MTcyMTE3MzVaMIGBMQswCQYDVQQGEwJVUzENMAsGA1UECBMEVXRh aDEOMAwGA1UEBxMFUHJvdm8xFTATBgNVBAoTDE5vdmVsbCwgSW5jLjEUMBIGA1UECxMLRW5naW5l ZXJpbmcxJjAkBgNVBAMTHWptYy1saW51eC02NC5wcm92by5ub3ZlbGwuY29tMIGfMA0GCSqGSIb3 DQEBAQUAA4GNADCBiQKBgQCOwb5migz+c1mmZS5eEhBQ5wsYFuSmp6bAL7LlHARQxhZg62FEVBFL Y2klPoCGfUoXUFegnhCV5I37M0dAQtNLSHiEPj0NjAvWuzagevE6Tq+0zXEBw9fKoVV/ypEsAxEX 6JQ+a1WU2W/vdL+x0lEbRpRCk9t6yhxLw16M/VD/GwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAC5E kniYYFxwZUqg9ToFlF0LKjGZfttkXJoTMfOFwA6OXrO6cKdzS04srxhoDzkD8V4RskPxttt0pbKr iAoGKT/9P4hpDb0Ej4urek9TxlrnoC8g0rOYaDfE57SMStDrCg2ha4IuJFtJOh1aMcl4pm/sk+JW 7U/cWyW9B7InJinZ -----END CERTIFICATE----- $ $ keytool -import -alias jmc-linux-64 \ -file jmc-linux-64.cert \ -keystore jmc-linux-64.truststore.jks Enter keystore password: trustpass Re-enter new password: trustpass Owner: CN=jmc-linux-64.provo.novell.com, OU=Engineering, O="Novell, Inc.", L=Provo, ST=Utah, C=US Issuer: CN=jmc-linux-64.provo.novell.com, OU=Engineering, O="Novell, Inc.", L=Provo, ST=Utah, C=US Serial number: 4a3c006f Valid from: Fri Jun 19 15:17:35 MDT 2009 until: Thu Sep 17 15:17:35 MDT 2009 Certificate fingerprints: MD5: E5:37:9F:85:C9:76:60:FC:DC:01:81:AD:5F:FC:F4:9A SHA1: FD:E3:47:6C:AE:9B:75:3B:9C:6C:05:7B:C9:A4:B4:E6:07:F6:B5:FB Signature algorithm name: SHA1withRSA Version: 3 Trust this certificate? [no]: yes Certificate was added to keystore $
We now have a file called jmc-linux-64.truststore.jks, which contains only the server’s public key and certificate. You can show the contents of the truststore JKS file with the -list option, like this:
$ keytool -list -v -keystore jmc-linux-64.truststore.jks Enter keystore password: trustpass Keystore type: JKS Keystore provider: SUN Your keystore contains 1 entry Alias name: jmc-linux-64 Creation date: Jun 19, 2009 Entry type: trustedCertEntry Owner: CN=jmc-linux-64.provo.novell.com, OU=Engineering, O="Novell, Inc.", L=Provo, ST=Utah, C=US Issuer: CN=jmc-linux-64.provo.novell.com, OU=Engineering, O="Novell, Inc.", L=Provo, ST=Utah, C=US Serial number: 4a3c006f Valid from: Fri Jun 19 15:17:35 MDT 2009 until: Thu Sep 17 15:17:35 MDT 2009 Certificate fingerprints: MD5: E5:37:9F:85:C9:76:60:FC:DC:01:81:AD:5F:FC:F4:9A SHA1: FD:E3:47:6C:AE:9B:75:3B:9C:6C:05:7B:C9:A4:B4:E6:07:F6:B5:FB Signature algorithm name: SHA1withRSA Version: 3 ******************************************* ******************************************* $
A Simple Https Client
We have several options for how to consume this trust store in client code. I’ll take the easy route today, but watch for another article that describes more complex mechanisms that provide more flexibility. Today, I’ll just show you how to set system properties on our client application. This client is very simple. All it does is connect to the server and display the contents of the web page in raw html to the console:
import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; public class HttpsClient { private final String serverUrl; public HttpsClient(String serverUrl) { this.serverUrl = serverUrl; } public void connect() { try { HttpURLConnection conn = null; URL url = new URL(serverUrl); try { conn = (HttpURLConnection)url.openConnection(); conn.setRequestMethod("GET"); conn.setDoOutput(false); conn.setDoInput(true); conn.connect(); InputStream is = conn.getInputStream(); Integer bytes; byte [] buffer = new byte[512]; while ((bytes = is.read(buffer, 0, 512)) > 0) System.out.write(buffer, 0, bytes); } catch (IOException e) { e.printStackTrace(); } } catch(MalformedURLException e) { e.printStackTrace(); } } public static void main(String[] args) { HttpsClient client = new HttpsClient( "https://jmc-linux-64.provo.novell.com:8443"); client.connect(); } }
Executing this client as is, without an assigned trust store will cause it to use the default trust store ($JAVA_HOME/lib/security/cacerts), which doesn’t contain our server’s public certificate, so it will fail with an exception:
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 ... stack trace ... Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target ... stack trace ... Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target ... stack trace ...
Configuring the Client Trust Store
The quick way to get this client to work properly is to assign our client’s trust store (containing the server’s public key and self-signed certificate) to JSSE system properties in this manner:
$ java -Djavax.net.ssl.trustStore=jmc-linux-64.truststore.jks \ -Djavax.net.ssl.trustStorePassword=trustword
If you get the path to the trust store file wrong, you’ll get a different cryptic exception:
javax.net.ssl.SSLException: java.lang.RuntimeException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty ... stack trace ... Caused by: java.lang.RuntimeException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty ... stack trace ... Caused by: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty ... stack trace ...
And if you get the password wrong, you’ll get yet another (somewhat less) cryptic exception:
java.net.SocketException: java.security.NoSuchAlgorithmException: Error constructing implementation (algorithm: Default, provider: SunJSSE, class: com.sun.net.ssl.internal.ssl.DefaultSSLContextImpl) ... stack trace ... Caused by: java.security.NoSuchAlgorithmException: Error constructing implementation (algorithm: Default, provider: SunJSSE, class: com.sun.net.ssl.internal.ssl.DefaultSSLContextImpl) ... stack trace ... Caused by: java.io.IOException: Keystore was tampered with, or password was incorrect ... stack trace ... Caused by: java.security.UnrecoverableKeyException: Password verification failed ... stack trace ...
In these examples, my client is using my server’s fully qualified domain name in the URL, which is the common name we used when we created the self-signed certificate:
... public static void main(String[] args) { HttpsClient client = new HttpsClient( "https://jmc-linux-64.provo.novell.com:8443"); client.connect(); } }
This is the only name that will work with this trust store. In my next article I’ll show you how to generate certificates that work with aliases like the IP address. I’ll also show you how to add a hostname verifier to allow our client code to be a bit more intelligent about which aliases it rejects out of hand.
