KeyStoreBuilder.java
/*
* The contents of this file are subject to the terms of the Common Development and
* Distribution License (the License). You may not use this file except in compliance with the
* License.
*
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
* specific language governing permission and limitations under the License.
*
* When distributing Covered Software, include this CDDL Header Notice in each file and include
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2016-2017 ForgeRock AS.
*/
package org.forgerock.security.keystore;
import static org.forgerock.util.Reject.checkNotNull;
import static org.forgerock.util.Utils.closeSilently;
import static org.forgerock.util.Utils.isBlank;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.Security;
import java.security.cert.CertificateException;
import java.util.Locale;
import org.forgerock.util.Reject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Builder class for loading key stores.
*/
public final class KeyStoreBuilder {
private static final Logger logger = LoggerFactory.getLogger(KeyStoreBuilder.class);
private static final String NONE = "none";
private String type = "JKS";
private KeyStore.LoadStoreParameter loadStoreParameter;
private InputStream inputStream;
private Provider provider;
private char[] password;
private Class<?> providerClass;
private String providerArg;
/**
* Specifies the input stream to load the keystore from. Defaults to {@code null} to create a fresh keystore.
* <p>
* Note: the input stream will be closed automatically after the keystore is loaded.
*
* @param inputStream the input stream to load the keystore from.
* @return the same builder instance.
*/
public KeyStoreBuilder withInputStream(final InputStream inputStream) {
this.inputStream = inputStream;
return this;
}
/**
* Specifies the file to load the keystore from.
*
* @param keyStoreFile the keystore file to load.
* @return the same builder instance.
* @throws FileNotFoundException if the file does not exist, is not a file, or cannot be read.
*/
public KeyStoreBuilder withKeyStoreFile(final File keyStoreFile) throws FileNotFoundException {
Reject.ifNull(keyStoreFile);
return withInputStream(new FileInputStream(keyStoreFile));
}
/**
* Specifies the file to load the keystore from. If the file name is "NONE" (case-insensitive), empty, or null
* the keystore will be loaded with a null {@link InputStream}.
*
* @param keyStoreFile the name of keystore file to load.
* @return the same builder instance.
* @throws FileNotFoundException if the file does not exist, is not a file, or cannot be read.
*/
public KeyStoreBuilder withKeyStoreFile(final String keyStoreFile) throws FileNotFoundException {
if (isBlank(keyStoreFile) || NONE.equals(keyStoreFile.toLowerCase(Locale.ROOT))) {
return withInputStream(null);
} else {
return withInputStream(new FileInputStream(keyStoreFile));
}
}
/**
* Specifies the type of keystore to load. Defaults to JKS.
*
* @deprecated Use withKeyStoreType(String) instead.
*
* Use of the KeyStoreType enum is deprecated as it restricts the keystore type to those specified in the
* enum. Library consumers may want to specify the keystore type at runtime.
*
* @param type the type of keystore to load. May not be null.
* @return the same builder instance.
*/
@Deprecated()
public KeyStoreBuilder withKeyStoreType(final KeyStoreType type) {
this.type = checkNotNull(type).toString();
return this;
}
/**
* Specifies the type of keystore to load. Defaults to JKS.
*
* @param type the type of keystore to load. May not be null.
* @return the same builder instance.
*/
public KeyStoreBuilder withKeyStoreType(final String type) {
this.type = checkNotNull(type);
return this;
}
/**
* Specifies the password to unlock the keystore. Defaults to no password. The password will be cleared after the
* keystore has been loaded.
*
* @param password the password to unlock the keystore.
* @return the same builder instance.
*/
public KeyStoreBuilder withPassword(final char[] password) {
this.password = password;
return this;
}
/**
* Specifies the password to unlock the keystore.
*
* @param password the password to use. May not be null.
* @return the same builder instance.
* @see #withPassword(char[])
*/
public KeyStoreBuilder withPassword(final String password) {
return withPassword(password.toCharArray());
}
/**
* Specifies the security provider to use for the keystore.
*
* @param provider the security provider. May not be null.
* @return the same builder instance.
*/
public KeyStoreBuilder withProvider(final Provider provider) {
this.provider = checkNotNull(provider);
return this;
}
/**
* Specifies the security provider to use for the keystore.
*
* @param providerName the name of the provider to use.
* @return the same builder instance.
* @throws IllegalArgumentException if no such provider exists.
*/
public KeyStoreBuilder withProvider(final String providerName) {
if (isBlank(providerName)) {
return this;
}
Provider provider = Security.getProvider(providerName);
if (provider == null) {
throw new IllegalArgumentException("No such provider: " + providerName);
}
return withProvider(provider);
}
/**
* Specifies the {@link KeyStore.LoadStoreParameter} to use to load the {@link KeyStore}.
*
* @param loadStoreParameter the {@link KeyStore.LoadStoreParameter}.
* @return the same builder instance.
*/
public KeyStoreBuilder withLoadStoreParameter(final KeyStore.LoadStoreParameter loadStoreParameter) {
Reject.ifNull(loadStoreParameter);
this.loadStoreParameter = loadStoreParameter;
return this;
}
/**
* Specifies the java class name of a keystore provider. The class will be loaded via reflection
* using the default class loader.
*
* @param className Java class name of a KeyStoreProvider - specififed as a string
* @return the same builder instance.
*/
public KeyStoreBuilder withProviderClass(final String className) {
return withProviderClass(className, Thread.currentThread().getContextClassLoader());
}
/**
* Specifies the java class name of a keystore provider. The class will be loaded via reflection
* using the supplied Class Loader
*
* @param className Java class name of a KeyStoreProvider - specififed as a string
* @param classLoader - The Java Class Loader to use.
* @return the same builder instance.
*/
public KeyStoreBuilder withProviderClass(final String className, final ClassLoader classLoader) {
try {
providerClass = Class.forName(className, true, classLoader);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Can not dynamically load new keystore class " + className, e);
}
return this;
}
/**
* Specifies the argument to the Java Keystore Provider. This is used when loading the provider
* through reflection. The interpretation of the argument is specific to the KeyStore Provider.
*
* @param arg The string argument to the provider.
* @return the same builder instance.
*/
public KeyStoreBuilder withProviderArgument(String arg) {
this.providerArg = checkNotNull(arg);
return this;
}
/**
* Builds and loads the keystore using the provided parameters. If a password was provided, then it is blanked
* after the keystore has been loaded.
*
* @return the configured keystore.
*/
public KeyStore build() {
try {
if (providerClass != null) {
this.provider = loadClass();
}
final KeyStore keyStore = provider != null
? KeyStore.getInstance(type, provider)
: KeyStore.getInstance(type);
if (inputStream != null && loadStoreParameter != null) {
throw new IllegalStateException("Can not specify a load store parameter and an input stream");
} else if (loadStoreParameter != null) {
keyStore.load(loadStoreParameter);
} else if (inputStream == null) { // this works for PKCS11 and LDAP
keyStore.load(null, password);
} else {
keyStore.load(inputStream, password);
}
return keyStore;
} catch (CertificateException | NoSuchAlgorithmException | IOException | KeyStoreException e) {
logger.error("Error loading keystore", e);
throw new IllegalStateException("Unable to load keystore", e);
} finally {
closeSilently(inputStream);
}
}
// Dynamically loads the keystore provider class
// This assume the provider constructor takes a single String argument, which can be null
// This is the case for the known dynamic keystore providers
private Provider loadClass() {
try {
Constructor<?> ctor = providerClass.getConstructor(String.class);
Provider provider = (Provider) ctor.newInstance(providerArg);
// This is not required unless you want other classes to be able to load the provider
// by the type name. This builder class explicitly loads the provider
// The insertProvider call may fail if the Java Security provider blocks it
// This is left here for documentation purposes.
//Security.insertProviderAt(provider, 1);
return provider;
} catch (Exception e) { // there a bunch of reasons reflection can fail. None can be fixed
throw new IllegalStateException("Can not load provider class using reflection", e);
}
}
}