How do I provide a specific TrustStore while using the default KeyStore in Java (JSSE)
Asked Answered
B

2

6

Overview

JSSE allows users to provide default trust stores and key stores by specifying javax.net.ssl.* parameters. I would like to provide a non-default TrustManager for my application, while allowing the user to specify the KeyManager as usual, but there doesn't seem to be any way to achieve this.

Details

http://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/JSSERefGuide.html#CustomizingStores

Suppose on unix machines I want to allow the user to use a pkcs12 key store for authentication, while on OS X I want allow the user to use the system keychain. On OS X the application might be started as follows:

java -Djavax.net.ssl.keyStore=NONE -Djavax.net.ssl.keyStoreType=KeychainStore \
     -Djavax.net.ssl.keyStorePassword=- -jar MyApplication.jar

This will work fine: when the application accesses an https server that requires mutual authentication (client certificate authentication) then the user will be prompted to allow access to their keychain.

The Problem

Now suppose I want to bundle a self-signed certificate authority with my application. I can override the default trust manager by constructing a TrustManagerFactory and passing in a KeyStore containing my certificate (javadoc). However, to use this non-default trust manager I need to create and initialise an SSLContext. Here-in lies the problem.

SSLContexts are initialised by calling init(..) and passing both a KeyManager and a TrustManager. However, logic for creating a KeyManager using the javax.net.ssl.* parameters is embedded in the implementation of the default SSLContexts -- I can't find a way to obtain a KeyManager or a KeyManagerFactory using the default behaviour while also specifying a non-default TrustManager or TrustManagerFactory. Thus, it seems that it is not possible to use, for example, the appropriate operating-system specific keychain implementation while also providing a root certificate for authenticating remote servers.

Broadway answered 7/3, 2013 at 11:1 Comment(1)
To clarify, I don't want to re-implement the default behaviour (e.g. by copying the implementation of sun.security.ssl.SSLContextImpl or javax.net.ssl.DefaultSSLContext) as the default load behaviour can vary on different platforms and may change in future JVM versions. I'm specifically looking for a way to reuse the default behaviour e.g. by extracting the KeyManager from the default SSLContext.Broadway
E
6

It sounds like you're facing a similar problem to this question, in that using null for the trustmanager parameter in SSLContext.init(...) reverts to the default trust manager, whereas it doesn't for the keymanager.

This being said, it's not that hard to initialise a KeyManager using the default system properties. Something like this should work (code written directly in this answer, so you might need to fix a few little things):

String provider = System.getProperty("javax.net.ssl.keyStoreProvider");
String keystoreType = System.getProperty("javax.net.ssl.keyStoreType", KeyStore.getDefaultType());
KeyStore ks = null;
if (provider != null) {
    ks = KeyStore.getInstance(keystoreType, provider);
} else {
    ks = KeyStore.getInstance(keystoreType);
}
InputStream ksis = null;
String keystorePath = System.getProperty("javax.net.ssl.keyStore");
String keystorePassword = System.getProperty("javax.net.ssl.keyStorePassword");
if (keystorePath != null && !"NONE".equals(keystorePath)) {
    ksis = new FileInputStream(keystorePath);
}
try {
    ks.load(ksis, keystorePassword.toCharArray());
} finally {
     if (ksis != null) { ksis.close(); }
}

KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, keystorePassword.toCharArray());
// Note that there is no property for the key password itself, which may be different.
// We're using the keystore password too.

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), ..., null);

(This utility class may also be of interest, more specifically getKeyStoreDefaultLoader().)

EDIT: (Following your additional comment)

I'm afraid there doesn't seem to be a default behaviour for both the Oracle and the IBM JSSE when you want to customise only half of the SSLContext. The section you link to in the Oracle JSSE documentation says, "If a keystore is specified by the javax.net.ssl.keyStore system property and an appropriate javax.net.ssl.keyStorePassword system property, then the KeyManager created by the default SSLContext will be a KeyManager implementation for managing the specified keystore." This wouldn't really apply here, since you're using a custom SSLContext, not the default one anyway (even if you're customising part of it).

Anyway, the Oracle JSSE reference guide and the IBM JSSE reference guide differ on this subject. (I'm not sure how much of this is meant to be "standard" and whether one should in principle be compliant with the other, but this is clearly not the case.)

Both "Creating an SSLContext Object" sections are almost identical, but they are different.

The Oracle JSSE Reference guide says:

If the KeyManager[] parameter is null, then an empty KeyManager will be defined for this context.

The IBM JSSE Reference guide says:

If the KeyManager[] paramater is null, the installed security providers will be searched for the highest-priority implementation of the KeyManagerFactory, from which an appropriate KeyManager will be obtained.

Unfortunately, if you want the same behaviour across implementations that have different specifications, you'll have to write a bit of code, even if that's effectively duplicating what one of the implementations already does.

Explicative answered 7/3, 2013 at 12:13 Comment(2)
Not an ideal answer because it requires re-implementing the default behaviour rather than reusing it, but useful non-the-less. Thanks for the tip about jsslutils.Broadway
It's unfortunate that there isn't a way to reuse half the SSL context. Thanks for your detailed answer.Broadway
E
3

It's not too hard to write a KeyManager that has the default behaviour. It's only a few lines of code. It's surprising that SSLContexts don't all behave like that w.r.t. the KeyManager, as they do w.r.t. the TrustManager. IBM's JSSE does behave like that. But it's not hard to synthesize yourself:

SSLContext  context = SSLContext.getInstance("TLS");
String  keyStore = System.getProperty("javax.net.ssl.keyStore");
String  keyStoreType = System.getProperty("javax.net.ssl.keyStoreType", KeyStore.getDefaultType());
String  keyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword","");
KeyManager[]    kms = null;
if (keyStore != null)
{
    KeyManagerFactory   kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    KeyStore    ks = KeyStore.getInstance(keyStoreType);
    if (keyStore != null && !keyStore.equals("NONE")) {
        fs = new FileInputStream(keyStore);
    ks.load(fs, keyStorePassword.toCharArray());
    if (fs != null)
        fs.close();
    char[]  password = null;
    if (keyStorePassword.length() > 0)
        password = keyStorePassword.toCharArray();
    kmf.init(ks,password);
    kms = kmf.getKeyManagers();
}
context.init(kms,null,null);
Epiclesis answered 7/3, 2013 at 12:11 Comment(4)
As you observe, different JSSE implementations have different behaviours, I don't want to have to implement my own KeyManager and then test it on every possible combination of JVM, architecture and JSSE implementation. The JDK7 default implementation is just shy of 100 lines of code, btw.Broadway
@StephenNelson Hmm, mine is 21 :-|Epiclesis
Yours doesn't have comments or handle PKCS11 :-) Otherwise it's useful, thanks.Broadway
@StephenNelson It should handle PKCS#11, if that's the javax.net.ssl.keyStoreType specified.Epiclesis

© 2022 - 2024 — McMap. All rights reserved.