package at.gv.egiz.eaaf.core.impl.credential;
import java.io.IOException;
import java.io.InputStream;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import at.asitplus.hsmfacade.provider.HsmFacadeProvider;
import at.asitplus.hsmfacade.provider.RemoteKeyStoreLoadParameter;
import at.gv.egiz.eaaf.core.api.idp.IConfiguration;
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.data.Pair;
import at.gv.egiz.eaaf.core.impl.utils.FileUtils;
import at.gv.egiz.eaaf.core.impl.utils.KeyStoreUtils;
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 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 CONFIG_PROP_HSM_FACADE_HSM_NAME = "security.hsmfacade.hsmname";
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";
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 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 {
final String hsmFacadeHost = basicConfig.getBasicConfiguration(CONFIG_PROP_HSM_FACADE_HOST);
if (StringUtils.isNotEmpty(hsmFacadeHost)) {
log.debug("Find host for HSMFacade. Starting crypto provider initialization ... ");
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);
final String hsmName =
getConfigurationParameter(CONFIG_PROP_HSM_FACADE_HSM_NAME);
final HsmFacadeProvider provider = HsmFacadeProvider.Companion.getInstance();
provider.init(getHsmFacadeTrustSslCertificate(), clientUsername, clientPassword, hsmFacadeHost, port,
hsmName);
//Security.addProvider(provider);
Security.insertProviderAt(provider, 0);
isHsmFacadeInitialized = true;
log.info("HSM Facade is initialized. {} can provide KeyStores based on remote HSM",
EaafKeyStoreFactory.class.getSimpleName());
} 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);
}
} else {
log.info("HSM Facade is not configurated. {} can only provide software keystores",
EaafKeyStoreFactory.class.getSimpleName());
}
}
@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 {
final String keyStoreName = checkConfigurationParameter(config.getKeyStoreName(),
ERRORCODE_06, config.getFriendlyName(), "KeyStoreName missing for HSM Facade");
try {
final KeyStore keyStore = KeyStore.getInstance(HSM_FACADE_KEYSTORE_TYPE, HSM_FACADE_PROVIDER);
keyStore.load(new RemoteKeyStoreLoadParameter(keyStoreName));
return Pair.newInstance(keyStore, keyStore.getProvider());
} catch (NoSuchAlgorithmException | CertificateException | IOException | KeyStoreException
| NoSuchProviderException e) {
log.error("Can not initialize KeyStore: {} with reason: {}",
config.getFriendlyName(), e.getMessage());
throw new EaafFactoryException(ERRORCODE_06,
new Object[] { config.getFriendlyName(), e.getMessage() }, e);
}
}
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;
}
}