CertificateLoader.java
/*
* #%L
* wcm.io
* %%
* Copyright (C) 2014 wcm.io
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package io.wcm.caravan.commons.httpclient.impl.helpers;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.conn.ssl.SSLInitializationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.wcm.caravan.commons.httpclient.HttpClientConfig;
/**
* Helper class for loading certificates for SSL communication.
*/
public final class CertificateLoader {
/**
* Default SSL context type
*/
public static final String SSL_CONTEXT_TYPE_DEFAULT = "TLS";
/**
* Default key manager type
*/
public static final String KEY_MANAGER_TYPE_DEFAULT = "SunX509";
/**
* Default key store type
*/
public static final String KEY_STORE_TYPE_DEFAULT = "PKCS12";
/**
* Default trust manager type
*/
public static final String TRUST_MANAGER_TYPE_DEFAULT = "SunX509";
/**
* Default trust store type
*/
public static final String TRUST_STORE_TYPE_DEFAULT = "JKS";
private CertificateLoader() {
// static methods only
}
/**
* Build SSL Socket factory.
* @param config Http client configuration
* @return SSL socket factory.
* @throws IOException I/O exception
* @throws GeneralSecurityException General security exception
*/
@SuppressWarnings("null")
@SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE") // null checks happen in separate methods
public static @NotNull SSLContext buildSSLContext(@NotNull HttpClientConfig config) throws IOException, GeneralSecurityException {
KeyManagerFactory kmf = null;
if (isSslKeyManagerEnabled(config)) {
kmf = getKeyManagerFactory(config.getKeyStorePath(),
new StoreProperties(config.getKeyStorePassword(), config.getKeyManagerType(),
config.getKeyStoreType(), config.getKeyStoreProvider()));
}
TrustManagerFactory tmf = null;
if (isSslTrustStoreEnbaled(config)) {
tmf = getTrustManagerFactory(config.getTrustStorePath(), new StoreProperties(config.getTrustStorePassword(),
config.getTrustManagerType(), config.getTrustStoreType(), config.getTrustStoreProvider()));
}
SSLContext sslContext = SSLContext.getInstance(config.getSslContextType());
sslContext.init(kmf != null ? kmf.getKeyManagers() : null,
tmf != null ? tmf.getTrustManagers() : null,
null);
return sslContext;
}
/**
* Get key manager factory
* @param keyStoreFilename Keystore file name
* @param storeProperties store properties
* @return Key manager factory
* @throws IOException I/O exception
* @throws GeneralSecurityException General security exception
*/
public static @NotNull KeyManagerFactory getKeyManagerFactory(@NotNull String keyStoreFilename, @NotNull StoreProperties storeProperties)
throws IOException, GeneralSecurityException {
InputStream is = getResourceAsStream(keyStoreFilename);
if (is == null) {
throw new FileNotFoundException("Certificate file not found: " + getFilenameInfo(keyStoreFilename));
}
try {
return getKeyManagerFactory(is, storeProperties);
}
finally {
try {
is.close();
}
catch (IOException ex) {
// ignore
}
}
}
/**
* Get key manager factory
* @param keyStoreStream Keystore input stream
* @param storeProperties store properties
* @return Key manager factory
* @throws IOException I/O exception
* @throws GeneralSecurityException General security exception
*/
private static @NotNull KeyManagerFactory getKeyManagerFactory(@NotNull InputStream keyStoreStream, @NotNull StoreProperties storeProperties)
throws IOException, GeneralSecurityException {
// use provider if given, otherwise use the first matching security provider
final KeyStore ks;
if (StringUtils.isNotBlank(storeProperties.getProvider())) {
ks = KeyStore.getInstance(storeProperties.getType(), storeProperties.getProvider());
}
else {
ks = KeyStore.getInstance(storeProperties.getType());
}
ks.load(keyStoreStream, storeProperties.getPassword().toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(storeProperties.getManagerType());
kmf.init(ks, storeProperties.getPassword().toCharArray());
return kmf;
}
/**
* Build TrustManagerFactory.
* @param trustStoreFilename Truststore file name
* @param storeProperties store properties
* @return TrustManagerFactory
* @throws IOException I/O exception
* @throws GeneralSecurityException General security exception
*/
public static @NotNull TrustManagerFactory getTrustManagerFactory(@NotNull String trustStoreFilename, @NotNull StoreProperties storeProperties)
throws IOException, GeneralSecurityException {
InputStream is = getResourceAsStream(trustStoreFilename);
if (is == null) {
throw new FileNotFoundException("Certificate file not found: " + getFilenameInfo(trustStoreFilename));
}
try {
return getTrustManagerFactory(is, storeProperties);
}
finally {
try {
is.close();
}
catch (IOException ex) {
// ignore
}
}
}
/**
* Build TrustManagerFactory.
* @param trustStoreStream Truststore input stream
* @param storeProperties store properties
* @return TrustManagerFactory
* @throws IOException I/O exception
* @throws GeneralSecurityException General security exception
*/
private static @NotNull TrustManagerFactory getTrustManagerFactory(@NotNull InputStream trustStoreStream, @NotNull StoreProperties storeProperties)
throws IOException, GeneralSecurityException {
// use provider if given, otherwise use the first matching security provider
final KeyStore ks;
if (StringUtils.isNotBlank(storeProperties.getProvider())) {
ks = KeyStore.getInstance(storeProperties.getType(), storeProperties.getProvider());
}
else {
ks = KeyStore.getInstance(storeProperties.getType());
}
ks.load(trustStoreStream, storeProperties.getPassword().toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(storeProperties.getManagerType());
tmf.init(ks);
return tmf;
}
/**
* Tries to load the given resource as file, or if no file exists as classpath resource.
* @param path Filesystem or classpath path
* @return InputStream or null if neither file nor classpath resource is found
* @throws IOException I/O exception
*/
private static @Nullable InputStream getResourceAsStream(@NotNull String path) throws IOException {
if (StringUtils.isEmpty(path)) {
return null;
}
// first try to load as file
File file = new File(path);
if (file.exists() && file.isFile()) {
return new FileInputStream(file);
}
// if not a file fallback to classloader resource
return CertificateLoader.class.getResourceAsStream(path);
}
/**
* Generate filename info for given path for error messages.
* @param path Path
* @return Absolute path
*/
private static @Nullable String getFilenameInfo(@Nullable String path) {
if (StringUtils.isEmpty(path)) {
return null;
}
try {
return new File(path).getCanonicalPath();
}
catch (IOException ex) {
return new File(path).getAbsolutePath();
}
}
/**
* Checks whether a SSL key store is configured.
* @param config Http client configuration
* @return true if client certificates are enabled
*/
public static boolean isSslKeyManagerEnabled(@NotNull HttpClientConfig config) {
return StringUtils.isNotEmpty(config.getSslContextType())
&& StringUtils.isNotEmpty(config.getKeyManagerType())
&& StringUtils.isNotEmpty(config.getKeyStoreType())
&& StringUtils.isNotEmpty(config.getKeyStorePath());
}
/**
* Checks whether a SSL trust store is configured.
* @param config Http client configuration
* @return true if client certificates are enabled
*/
public static boolean isSslTrustStoreEnbaled(@NotNull HttpClientConfig config) {
return StringUtils.isNotEmpty(config.getSslContextType())
&& StringUtils.isNotEmpty(config.getTrustManagerType())
&& StringUtils.isNotEmpty(config.getTrustStoreType())
&& StringUtils.isNotEmpty(config.getTrustStorePath());
}
/**
* Creates default SSL context.
* @return SSL context
*/
public static @NotNull SSLContext createDefaultSSlContext() throws SSLInitializationException {
try {
final SSLContext sslcontext = SSLContext.getInstance(SSL_CONTEXT_TYPE_DEFAULT);
sslcontext.init(null, null, null);
return sslcontext;
}
catch (NoSuchAlgorithmException | KeyManagementException ex) {
throw new SSLInitializationException(ex.getMessage(), ex);
}
}
}