问题
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.
回答1:
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.
回答2:
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);
来源:https://stackoverflow.com/questions/15269419/how-do-i-provide-a-specific-truststore-while-using-the-default-keystore-in-java