views:

51

answers:

2

Hi,

I need to request a URL from a server that uses client certificates for authentication and can't find a way to do this for my application.

My problem is that the Java client I'm working on has the certificate file available locally but due to restrictions on the PCs it will be running on it cannot install the certificate in a keystore.

In short, I just want to be able to explicilty specify the certificate to use for the URL I need to retrieve.

Any suggestions?

+2  A: 

It's not clear what the restrictions you're talking about are. More specifically, I'm not sure what you consider the difference between the local certificate file and a keystore. Most keystores are file-based, so you might be able to load the file this way directly, without needing an installation process. Are the restrictions related to the security policies used by the JVM itself (which may prevent you from instantiating KeyStores)?

First, it's not just the certificate you need on the client side, but its private key. Often, people use the word "certificate" in this context to mean both, but you do need to make sure your file doesn't contain the certificate without the private key. Typically, you'll find the combination of private key + certificate in a PKCS#12 file (.p12/.pfx), a lot of tools import/export in this format; it's also a keystore format natively supported by the Sun JVM (type PKCS12).

To make this work, you need to configure what makes the connection with the appropriate keystore. SSL/TLS client-certificate authentication is always initiated by the server: the client responds with a certificate if it has one (and wants to use it). To configure it for a specific URL, you need to find out what makes the connection (perhaps HttpsURLConnection) and set it there (unless it's set up in the default context -- even if it's set up in the default context, it will only be used for servers that request it).

To set up the keystore globally on the JVM (which may be what your restrictions prevent you to do), you can set the javax.net.ssl.keyStore javax.net.ssl.keyStorePassword (and related) system properties. (Because the password could be visible, it's better not to do it on the command line).

These system properties are used for the configuration of the default SSLContext (which is used, often transparently, by libraries or classes such as HttpsURLConnection to build the SSLSocketFactory and then SSLSocket, initialized with those properties).

You could build SSLContext from your file specifically for use for that connection. The SSLContext is effectively a factory for the SSLSocketFactory or SSLEngine, and you can set the SSLSocketFactory in a given HttpsURLConnection.

The following would build an SSLContext using "/path/to/file.p12" as your keystore (that is the one with your private key and the certificate you're going to send) and keep the default settings for the truststore (you'd need to catch the exception for the input stream too).

KeyStore ks = KeyStore.getInstance("PKCS12");
FileInputStream fis = new FileInputStream("/path/to/file.p12");
ks.load(fis, "password".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, "password".toCharArray());
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(kmf.getKeyManagers(), null, null);

From there you can configure the connection like this (if this is what you're using):

HttpURLConnection connection = (HttpURLConnection) url.openConnection();
if (connection instanceof HttpsURLConnection) {
    ((HttpsURLConnection)connection)
         .setSSLSocketFactory(sc.getSSLSocketFactory());
}

Some libraries will let you pass an SSLContext directly (Apache HTTP Client 4 supports that, and this can be done with Apache HTTP Client 3 using this.)

Note that you don't need to provide the password as a direct parameter when loading the keystore, you could also use a callback (maybe better from the GUI point of view).

Perhaps this library could help (but it's not necessary): you could use the KeystoreLoader for its helpers to do this. There are also SSLContextFactories in this libraries (but you would probably not need any of the wrappers as they tend to be for customizing the trust management or key selection).

This is generally how using a client-certificate is configured, but it's difficult to provide more details without clarifications regarding what your restrictions exactly are (and which libraries you're using).

Bruno
Thanks for the comprehensive repose Bruno.
Steve Neal
I have a .cer file to authenticate the client with. From what I can gather this is just the public key right? If so, then your answer will mean that this is not going to work. However, I've imported this into a browser and can access the Tomcat server using that. Any thoughts?
Steve Neal
Usually, .cer files (ultimately, the extension is just an indication) only contain the certificate, which contains the public key), but not the private key, and you need to have the private key to use client-certificate authentication (otherwise, anyone could send the certificate and that wouldn't be authentication). It's possible that you Tomcat server uses optional client-cert authentication, in which case it would let you in, even without a cert. Where did you import this file into your browser? How was this client-certificate issued to you? What OS are you using?
Bruno
The client cert was issued by the company I work for, and they have signed it. When I access a Web app within the organization I'm automatically logged in. The login on the Tomcat server I'm using required CLIENT-CERT authentication.
Steve Neal
I should add that the realm used for the authentication in Tomcat is a custom one. It's possible that the realm just looks for a valid username in the certificate and allows access.
Steve Neal
OK for having your cert issued by your company, but where was the private key generated? In the browser? On a smart card? Regarding Tomcat, is the connector (in server.xml) configured with clientAuth="want" or clientAuth="need"?
Bruno
I guess the private key was created by the browsers when they downloaded the certificates. I hadn't realised that these were required until you mentioned them. In Tomcat, clientAuty='false' and I can't change this setting.
Steve Neal
OK, so you have two potentially big problems here unfortunately. (1) if you've lost your private key, you're probably going to have to re-emit a certificate (it might be worth asking assistance within your company). (2) With clientAuth="false", this implies a re-negotiation per webapp (otherwise, the client-cert is requested initially). The problem with this is that renegotiation has been disabled (J2SE 1.6_19, I think) following a security issue, and the fix (RFC 5746) doesn't seem to be in 1.6_21. There is an unsafe workaround: http://java.sun.com/javase/javaseforbusiness/docs/TLSReadme.html
Bruno
Thanks so much for your advice. I think I'll try another approach to this problem then.
Steve Neal
+1  A: 

You can change the default JSSE keystore through a property when you invoke your app, -Djavax.net.ssl.keystore=somefile.

So you could import your cert with the keytool command into a keystore and invoke your app pointing to that, e.g.

keytool -importcert -file mycert -keystore mystore
java -Djavax.net.ssl.keystore=mystore ...
locka
Importing the cert on its own will not import the private key: you need that if you want to use the cert for client-certificate authentication.
Bruno
Thanks, but I can't use a keystore on the file system.
Steve Neal