package at.gv.egiz.eaaf.core.impl.credential;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStore.LoadStoreParameter;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import at.gv.egiz.eaaf.core.api.idp.IConfiguration;
import at.gv.egiz.eaaf.core.exception.EaafKeyAccessException;
import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException;
import at.gv.egiz.eaaf.core.exceptions.EaafException;
import at.gv.egiz.eaaf.core.exceptions.EaafFactoryException;
import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration.KeyStoreType;
import at.gv.egiz.eaaf.core.impl.credential.SymmetricKeyConfiguration.SymmetricKeyType;
import at.gv.egiz.eaaf.core.impl.data.Pair;
import at.gv.egiz.eaaf.core.impl.utils.FileUtils;
import at.gv.egiz.eaaf.core.impl.utils.KeyStoreUtils;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class EaafKeyStoreFactory {
public static final String CONFIG_PROP_HSM_FACADE_HOST = "security.hsmfacade.host";
public static final String CONFIG_PROP_HSM_FACADE_PORT = "security.hsmfacade.port";
public static final String CONFIG_PROP_HSM_FACADE_SSLTRUST = "security.hsmfacade.trustedsslcert";
public static final String CONFIG_PROP_HSM_FACADE_CLIENT_USERNAME = "security.hsmfacade.username";
public static final String CONFIG_PROP_HSM_FACADE_CLIENT_PASSWORD = "security.hsmfacade.password";
public static final String ERRORCODE_00 = "internal.keystore.00";
public static final String ERRORCODE_01 = "internal.keystore.01";
public static final String ERRORCODE_02 = "internal.keystore.02";
public static final String ERRORCODE_03 = "internal.keystore.03";
public static final String ERRORCODE_04 = "internal.keystore.04";
public static final String ERRORCODE_05 = "internal.keystore.05";
public static final String ERRORCODE_06 = "internal.keystore.06";
public static final String ERRORCODE_07 = "internal.keystore.07";
public static final String ERRORCODE_10 = "internal.keystore.10";
public static final String ERRORCODE_KEY_00 = "internal.key.00";
private static final String HSM_FACADE_PROVIDER_CLASS = "at.asitplus.hsmfacade.provider.HsmFacadeProvider";
private static final String HSM_FACADE_KEYSTORELOADPARAMETERS_CLASS
= "at.asitplus.hsmfacade.provider.RemoteKeyStoreLoadParameter";
private static final String HSM_FACADE_PROVIDER_METHOD_CONSTRUCT = "getInstance";
private static final String HSM_FACADE_PROVIDER_METHOD_INIT = "init";
private static final String HSM_FACADE_PROVIDER_INIT_ERROR_MSG
= "Has HSM-Facade class supported '{}' method: {}";
private static final String HSM_FACADE_PROVIDER = "HsmFacade";
private static final String HSM_FACADE_KEYSTORE_TYPE = "RemoteKeyStore";
@Autowired
private IConfiguration basicConfig;
@Autowired
private ResourceLoader resourceLoader;
private boolean isHsmFacadeInitialized = false;
/**
* Get a new symmetric key based on a {@link SymmetricKeyConfiguration} object.
*
* @param config Symmetric key configuration
* @return {@link Pair} of a new {@link SecretKey} instance and an optional {@link Provider}.
* The {@link SecretKey} is {@link Nonnull}. If the {@link Provider} is not null
* this {@link SecretKey} requires a specific {@link Provider} for {@link Key} operations.
* @throws EaafException In case of a KeyStore initialization error
*/
@Nonnull
public Pair buildNewSymmetricKey(SymmetricKeyConfiguration config) throws EaafException {
log.trace("Starting symmetric-key generation based on configuration object ... ");
if (SymmetricKeyType.PASSPHRASE.equals(config.getKeyType())) {
return generatePassPhraseBasedSymmetricKey(config);
} else if (SymmetricKeyType.HSMFACADE.equals(config.getKeyType())) {
if (isHsmFacadeInitialized) {
return getSymmetricKeyFromHsmFacade(config);
} else {
log.error("HSMFacade can NOT be used for symmetric Key: {} because {} is not initialized",
config.getFriendlyName());
throw new EaafConfigurationException(ERRORCODE_00,
new Object[] { config.getFriendlyName() });
}
} else {
log.warn("Symmetric KeyType: {} is unrecognized", config.getKeyType());
throw new EaafConfigurationException(ERRORCODE_01,
new Object[] { config.getFriendlyName() });
}
}
/**
* Get a new KeyStore based on a KeyStore configuration-object.
*
* @param config KeyStore configuration
* @return {@link Pair} of a new {@link KeyStore} instance and an optional {@link Provider}.
* The {@link KeyStore} is {@link Nonnull}. If the {@link Provider} is not null
* this {@link KeyStore} requires a specific {@link Provider} for {@link Key} operations.
* @throws EaafException In case of a KeyStore initialization error
*/
@Nonnull
public Pair buildNewKeyStore(KeyStoreConfiguration config) throws EaafException {
log.trace("Starting KeyStore generation based on configuration object ... ");
if (KeyStoreType.PKCS12.equals(config.getKeyStoreType())
|| KeyStoreType.JKS.equals(config.getKeyStoreType())) {
return getKeyStoreFromFileSystem(config);
} else if (KeyStoreType.HSMFACADE.equals(config.getKeyStoreType())) {
if (isHsmFacadeInitialized) {
return getKeyStoreFromHsmFacade(config);
} else {
log.error("HSMFacade can NOT be used for KeyStore: {} because {} is not initialized",
config.getFriendlyName());
throw new EaafConfigurationException(ERRORCODE_00,
new Object[] { config.getFriendlyName() });
}
} else if (KeyStoreType.PKCS11.equals(config.getKeyStoreType())) {
log.warn("KeyStoreType: {} is NOT supported", config.getKeyStoreType());
throw new EaafConfigurationException(ERRORCODE_02,
new Object[] { config.getFriendlyName(), config.getKeyStoreType() });
} else {
log.warn("KeyStoreType: {} is unrecognized", config.getKeyStoreType());
throw new EaafConfigurationException(ERRORCODE_01,
new Object[] { config.getFriendlyName() });
}
}
/**
* Get the initialization state of the HSM Facade module.
*
* @return true if HSM Facade is available, otherwise false
*/
public boolean isHsmFacadeInitialized() {
return isHsmFacadeInitialized;
}
@PostConstruct
private void initialize() throws EaafException {
Class> hsmProviderClazz = getHsmProviderClass();
if (hsmProviderClazz != null) {
final String hsmFacadeHost = basicConfig.getBasicConfiguration(CONFIG_PROP_HSM_FACADE_HOST);
Provider alreadyLoadedProvider = Security.getProvider(HSM_FACADE_PROVIDER);
if (alreadyLoadedProvider != null
&& alreadyLoadedProvider.getClass().isAssignableFrom(hsmProviderClazz)) {
//TODO: check isInitialized() flag, if the parameter is available in next version
log.info("Find already initialized Java SecurityProvider: {}", alreadyLoadedProvider.getName());
log.info("HSM Facade is already initialized. {} can provide KeyStores based on remote HSM",
EaafKeyStoreFactory.class.getSimpleName());
isHsmFacadeInitialized = true;
} else if (StringUtils.isNotEmpty(hsmFacadeHost)) {
log.debug("Find host for HSMFacade. Starting crypto provider initialization ... ");
initializeHsmFacadeSecurityProvider(hsmProviderClazz, hsmFacadeHost);
} else {
log.info("HSM Facade is on ClassPath but not configurated. {} can only provide software keystores",
EaafKeyStoreFactory.class.getSimpleName());
}
} else {
log.info("HSM Facade is not on ClassPath. {} can only provide software keystores",
EaafKeyStoreFactory.class.getSimpleName());
}
}
private void initializeHsmFacadeSecurityProvider(Class> hsmProviderClazz, String hsmFacadeHost)
throws EaafException {
try {
final int port = Integer.parseUnsignedInt(
getConfigurationParameter(CONFIG_PROP_HSM_FACADE_PORT));
final String clientUsername =
getConfigurationParameter(CONFIG_PROP_HSM_FACADE_CLIENT_USERNAME);
final String clientPassword =
getConfigurationParameter(CONFIG_PROP_HSM_FACADE_CLIENT_PASSWORD);
//initialize HSM-Facade by using JAVA Reflection, because in that case HSM-Facade
//has not be in ClassPath on every project
Method constructor = hsmProviderClazz.getMethod(HSM_FACADE_PROVIDER_METHOD_CONSTRUCT, new Class[]{});
Method initMethod = hsmProviderClazz.getMethod(HSM_FACADE_PROVIDER_METHOD_INIT,
X509Certificate.class, String.class, String.class, String.class, int.class);
if (initMethod != null && constructor != null) {
Object rawProvider = constructor.invoke(hsmProviderClazz);
initMethod.invoke(
rawProvider, getHsmFacadeTrustSslCertificate(),
clientUsername, clientPassword, hsmFacadeHost, port);
if (rawProvider instanceof Provider) {
Security.insertProviderAt((Provider) rawProvider, 0);
isHsmFacadeInitialized = true;
log.info("HSM Facade is initialized. {} can provide KeyStores based on remote HSM",
EaafKeyStoreFactory.class.getSimpleName());
} else {
log.warn("Is HSM-Facade class type of 'java.security.Provider': {}",
rawProvider instanceof Provider);
throw new EaafException(ERRORCODE_10, new Object[] {HSM_FACADE_PROVIDER_CLASS});
}
} else {
log.warn(HSM_FACADE_PROVIDER_INIT_ERROR_MSG,
HSM_FACADE_PROVIDER_METHOD_CONSTRUCT, constructor != null);
log.warn(HSM_FACADE_PROVIDER_INIT_ERROR_MSG,
HSM_FACADE_PROVIDER_METHOD_INIT, initMethod != null);
throw new EaafException(ERRORCODE_10, new Object[] {HSM_FACADE_PROVIDER_CLASS});
}
//final HsmFacadeProvider provider = HsmFacadeProvider.Companion.getInstance();
//provider.init(getHsmFacadeTrustSslCertificate(), clientUsername, clientPassword, hsmFacadeHost, port);
} catch (final EaafException e) {
throw e;
} catch (final Exception e) {
log.error("HSM Facade initialization FAILED with an generic error.", e);
throw new EaafConfigurationException(ERRORCODE_03, new Object[] { e.getMessage() }, e);
}
}
private Class> getHsmProviderClass() {
try {
return Class.forName(HSM_FACADE_PROVIDER_CLASS);
} catch (ClassNotFoundException e1) {
log.debug("No HSM-Facade implemenation in ClassPath. HSM-Facade will not be available");
return null;
}
}
@Nonnull
private Pair getKeyStoreFromFileSystem(KeyStoreConfiguration config)
throws EaafConfigurationException, EaafFactoryException {
try {
final String keyStorePath = checkConfigurationParameter(config.getSoftKeyStoreFilePath(),
ERRORCODE_06, config.getFriendlyName(), "Software-KeyStore missing filepath to KeyStore");
final String keyStorePassword = checkConfigurationParameter(config.getSoftKeyStorePassword(),
ERRORCODE_06, config.getFriendlyName(), "Software-KeyStore missing Password for KeyStore");
final String absKeyStorePath = FileUtils.makeAbsoluteUrl(keyStorePath, basicConfig
.getConfigurationRootDirectory());
final Resource ressource = resourceLoader.getResource(absKeyStorePath);
if (!ressource.exists()) {
throw new EaafConfigurationException(ERRORCODE_05,
new Object[] { config.getFriendlyName(),
"File not found at: " + absKeyStorePath });
}
final InputStream is = ressource.getInputStream();
final KeyStore keyStore = KeyStoreUtils.loadKeyStore(is, keyStorePassword);
is.close();
if (keyStore == null) {
throw new EaafFactoryException(ERRORCODE_06,
new Object[] { config.getFriendlyName(), "KeyStore not valid or password wrong" });
}
return Pair.newInstance(keyStore, null);
} catch (KeyStoreException | IOException e) {
log.error("Software KeyStore initialization FAILED with an generic error.", e);
throw new EaafConfigurationException(ERRORCODE_03, new Object[] { e.getMessage() }, e);
}
}
@Nonnull
private Pair getKeyStoreFromHsmFacade(KeyStoreConfiguration config)
throws EaafFactoryException, EaafConfigurationException {
return getKeyStoreFromHsmFacade(config.getKeyStoreName(), config.getFriendlyName());
}
@Nonnull
private Pair getKeyStoreFromHsmFacade(String keyStoreName, String friendlyName)
throws EaafFactoryException, EaafConfigurationException {
final String validatedKeyStoreName = checkConfigurationParameter(keyStoreName,
ERRORCODE_06, friendlyName, "KeyStoreName missing for HSM Facade");
try {
final KeyStore keyStore = KeyStore.getInstance(HSM_FACADE_KEYSTORE_TYPE, HSM_FACADE_PROVIDER);
keyStore.load(getHsmFacadeKeyStoreParameter(validatedKeyStoreName));
return Pair.newInstance(keyStore, keyStore.getProvider());
} catch (NoSuchAlgorithmException | CertificateException | IOException | KeyStoreException
| NoSuchProviderException | EaafException e) {
log.error("Can not initialize KeyStore: {} with reason: {}",
friendlyName, e.getMessage());
throw new EaafFactoryException(ERRORCODE_06,
new Object[] {friendlyName, e.getMessage() }, e);
}
}
private KeyStore.LoadStoreParameter getHsmFacadeKeyStoreParameter(String keyStoreName) throws EaafException {
try {
Class> clazz = Class.forName(HSM_FACADE_KEYSTORELOADPARAMETERS_CLASS);
Constructor> constructor = clazz.getConstructor(String.class);
Object keyStoreParams = constructor.newInstance(keyStoreName);
return (LoadStoreParameter) keyStoreParams;
} catch (Exception e) {
log.error("Can NOT build class: {} for HSM-Facade provider", HSM_FACADE_KEYSTORELOADPARAMETERS_CLASS, e);
throw new EaafException(ERRORCODE_10, new Object[] {HSM_FACADE_PROVIDER_CLASS}, e);
}
}
@Nonnull
private Pair generatePassPhraseBasedSymmetricKey(SymmetricKeyConfiguration config)
throws EaafConfigurationException {
checkConfigurationParameter(config.getSoftKeyPassphrase(),
ERRORCODE_KEY_00, config.getFriendlyName(), "passphrase missing");
checkConfigurationParameter(config.getSoftKeySalt(),
ERRORCODE_KEY_00, config.getFriendlyName(), "salt missing");
try {
final SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WITHHMACSHA256");
final KeySpec spec = new PBEKeySpec(
config.getSoftKeyPassphrase().toCharArray(),
config.getSoftKeySalt().getBytes("UTF-8"),
10000, 128);
return Pair.newInstance(keyFactory.generateSecret(spec), null);
} catch (NoSuchAlgorithmException | InvalidKeySpecException | UnsupportedEncodingException e) {
log.error("Passphrase based symmetric-key generation FAILED", e);
throw new EaafConfigurationException(ERRORCODE_KEY_00,
new Object[] { config.getFriendlyName(), e.getMessage() },
e);
}
}
@Nonnull
private Pair getSymmetricKeyFromHsmFacade(SymmetricKeyConfiguration config)
throws EaafFactoryException, EaafConfigurationException, EaafKeyAccessException {
Pair keyStore = getKeyStoreFromHsmFacade(
config.getKeyStoreName(), config.getFriendlyName());
checkConfigurationParameter(config.getKeyAlias(),
ERRORCODE_KEY_00, config.getFriendlyName(), "keyAlias missing");
try {
SecretKey secretKey = (SecretKey) keyStore.getFirst().getKey(config.getKeyAlias(), null);
if (secretKey == null) {
throw new EaafKeyAccessException(EaafKeyAccessException.ERROR_CODE_09,
config.getFriendlyName(), config.getKeyAlias(), "No SecretKey with Alias ");
}
return Pair.newInstance(secretKey, keyStore.getSecond());
} catch (UnrecoverableKeyException | KeyStoreException | NoSuchAlgorithmException e) {
throw new EaafKeyAccessException(EaafKeyAccessException.ERROR_CODE_09, e,
config.getFriendlyName(), config.getKeyAlias(), e.getMessage());
} catch (ClassCastException e) {
throw new EaafKeyAccessException(EaafKeyAccessException.ERROR_CODE_09,
config.getFriendlyName(), config.getKeyAlias(), "Wrong SecretKey type ");
}
}
private X509Certificate getHsmFacadeTrustSslCertificate() throws EaafConfigurationException {
try {
final String certFilePath = getConfigurationParameter(CONFIG_PROP_HSM_FACADE_SSLTRUST);
final String absolutCertFilePath = FileUtils.makeAbsoluteUrl(
certFilePath, basicConfig.getConfigurationRootDirectory());
final Resource certFile = resourceLoader.getResource(absolutCertFilePath);
if (!certFile.exists()) {
throw new EaafConfigurationException(ERRORCODE_05,
new Object[] { CONFIG_PROP_HSM_FACADE_SSLTRUST,
"File not found at: " + absolutCertFilePath });
}
return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(certFile
.getInputStream());
} catch (final EaafConfigurationException e) {
throw e;
} catch (CertificateException | IOException e) {
log.error("Can not load trusted server-certificate for HSM-Facade. Reason: {}", e.getMessage());
throw new EaafConfigurationException(ERRORCODE_05,
new Object[] { CONFIG_PROP_HSM_FACADE_SSLTRUST, e.getMessage() }, e);
}
}
@Nonnull
private String getConfigurationParameter(@Nonnull String configParamKey)
throws EaafConfigurationException {
return checkConfigurationParameter(
basicConfig.getBasicConfiguration(configParamKey), ERRORCODE_04, configParamKey);
}
@Nonnull
private String checkConfigurationParameter(@Nullable String configParam, @Nonnull String errorCode,
@Nonnull String... errorParams)
throws EaafConfigurationException {
if (StringUtils.isEmpty(configParam)) {
throw new EaafConfigurationException(errorCode, new Object[] { errorParams });
}
return configParam;
}
}