Using a Custom Truststore in Java as Well as the Default One

Using a custom truststore in java as well as the default one

You could use a similar pattern to what I've mentioned in a previous answer (for a different problem).

Essentially, get hold of the default trust manager, create a second trust manager that uses your own trust store. Wrap them both in a custom trust manager implementation that delegates call to both (falling back on the other when one fails).

TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
// Using null here initialises the TMF with the default trust store.
tmf.init((KeyStore) null);

// Get hold of the default trust manager
X509TrustManager defaultTm = null;
for (TrustManager tm : tmf.getTrustManagers()) {
if (tm instanceof X509TrustManager) {
defaultTm = (X509TrustManager) tm;
break;
}
}

FileInputStream myKeys = new FileInputStream("truststore.jks");

// Do the same with your trust store this time
// Adapt how you load the keystore to your needs
KeyStore myTrustStore = KeyStore.getInstance(KeyStore.getDefaultType());
myTrustStore.load(myKeys, "password".toCharArray());

myKeys.close();

tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(myTrustStore);

// Get hold of the default trust manager
X509TrustManager myTm = null;
for (TrustManager tm : tmf.getTrustManagers()) {
if (tm instanceof X509TrustManager) {
myTm = (X509TrustManager) tm;
break;
}
}

// Wrap it in your own class.
final X509TrustManager finalDefaultTm = defaultTm;
final X509TrustManager finalMyTm = myTm;
X509TrustManager customTm = new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
// If you're planning to use client-cert auth,
// merge results from "defaultTm" and "myTm".
return finalDefaultTm.getAcceptedIssuers();
}

@Override
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
try {
finalMyTm.checkServerTrusted(chain, authType);
} catch (CertificateException e) {
// This will throw another CertificateException if this fails too.
finalDefaultTm.checkServerTrusted(chain, authType);
}
}

@Override
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
// If you're planning to use client-cert auth,
// do the same as checking the server.
finalDefaultTm.checkClientTrusted(chain, authType);
}
};

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { customTm }, null);

// You don't have to set this as the default context,
// it depends on the library you're using.
SSLContext.setDefault(sslContext);

You don't have to set that context as the default context. How you use it depends on the client library you're using (and where it gets its socket factories from).


This being said, in principle, you'd always have to update the truststore as required anyway. The Java 7 JSSE Reference Guide had an "important note" about this, now downgraded to just a "note" in version 8 of the same guide:

The JDK ships with a limited number of trusted root certificates in
the java-home/lib/security/cacerts file. As documented in keytool
reference pages, it is your responsibility to maintain (that is, add
and remove) the certificates contained in this file if you use this
file as a truststore.

Depending on the certificate configuration of the servers that you
contact, you may need to add additional root certificates. Obtain the
needed specific root certificates from the appropriate vendor.

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

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.

Is it possible to use custom truststore with SOAPConnection?

Additional research shows pretty clearly that there is no way to do this within the SOAP classes -- they always use the JVM-wide defaults.

So I ended up using the approach in this answer where you make a custom trustmanager that is a composite of two trustmanagers -- one built from the default truststore and one built from the custom truststore -- and then inject that composite trustmanager into the default SSLContext.

How to add multiple truststore paths to “java.net.ssl.trustStore”?

You can't have multiple paths for javax.net.ssl.trustStore.

The easiest would be to make a local copy of the JRE's cacerts and import the certificates from your other store into it (effectively merging them). (See keytool -importkeystore.)

Otherwise, if you know in advance that all your LDAP connections will use your second keystore (and you also want to be able to use the default truststore for other, non-related connections), you could configure that trust store for that SSLSocketFactory only. I'm not familiar with com.org.ldap.LDAPSocketFactory, but it might have an option to do so. (Otherwise, you could create your custom SSLContext initialised with your second truststore and get an SSLSocketFactory, as described in this answer).

Another, more complicated way, would be to create a custom X509TrustManager that wraps the default trust manager, catches its exceptions and tries again with another trust manager initialised using your second store. It's feasible, but you'd need to make sure it still throws the exception if neither trust managers accept your certificate (otherwise, there would be a security hole). If you're not familiar with the JSSE API (or Java altogether), it's probably not the best option.

In addition, be careful when you use System.setProperty("javax.net.ssl.trustStore", ...) in your code: it is what's read to initialise the default SSLContext, but the default SSLContext is only initialised once, the first time it's required. Setting this system property afterwards would have no effect (unless of course, other classes from other libraries also rely on this value).


It's also not clear what you're trying to achieve with this, since you'll always ever succeed to add a security provider that's already there:

  try 
{
SSLContext se = SSLContext.getInstance("TLS");
Security.addProvider(se.getProvider());
}
catch(NoSuchAlgorithmException e) { }

SSL Validator Exception when using other PC

if I build an artifact and use it on another PC which keystore will be
asked if certificate is valid. The one I build it with or the one
which is installed on the system of user.

The one that is installed on the system of the user.
There is nothing called builder keystore, when you build the app, the cacerts file in the JRE will not be bundled into your final app artifact, the app will trust by default all the certificates present in the cacerts file at runtime where the app is running, which could be the same machine where you built your app or a different machine.

how can I assure that the certificate will be accepted.

You can create your your own truststore and tell java to use your newly created truststore instead of the default one that is located at your_JRE_foler\lib\security\cacerts.

You can specify your own truststore through JVM system properties e.g. as follow:-Djavax.net.ssl.trustStore=/path/to/your/store/my-cacerts but be careful your application will no more trust all the default certificates since you are no more pointing to the default JRE cacerts file.

If you want to combine your own store and the default JRE cacerts store, then you need to provide a custom KeyManager implementation, check this answer: Using a custom truststore in java as well as the default one

Does javax.net.ssl.trustStore override or add to cacerts

If a custom Trust Store is specified by the javax.net.ssl.trustStore - then the default one (default JDK cacerts) won't be used.

Not sure if term "override" is 100% correct here. The defauld JDK cacerts remains, you don't update it or something. But your custom one is used.

I'd say it is not recommended that you modify the default Trust Store, given that it is shipped with your JVM and will be updated with it.

Instead you could make a copy and add your certificates to the copy and set this copy as the custom one (using javax.net.ssl.trustStore).

For instance:

  1. Copy the default one (simply copy the file)

  2. Use keytool to add some specific certificates to the copy

    keytool -import -file /path/to/certificate.pem -alias NameYouWantToGiveOfYourCertificate -keystore /path/to/copy/of/default/truststore.jks -storepass changeit

This is just an example. You might want to use some other tool, but hope the idea is clear :)

Why is it possible to point to a truststore that uses a custom password without providing the passwort?

It even depends on the SPI (JCE) implementation, indeed you don't need any password to list a JKS store.

Certificates aren't confidential, but you wouldn't want anyone to tamper with the list of certificates the system should trust (e.g. providing a rogue/false root CA).

There should be a way to enforce trust of the truststore (the password should be
validated). If you provide a wrong password, will it fail?

Yes it will fail, and if you don't provide a password you can't update the truststore

indeed, that's expected behavior. For JKS keystores you may list entries and certificates without any password, but unable to verify the integrity. Anyway you should provide a password with your application to make sure nobody sc..wd with your truststore.



Related Topics



Leave a reply



Submit