package at.gv.egiz.eaaf.core.impl.credential; import java.io.IOException; import java.io.InputStream; 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.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 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.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 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"; 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 = "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 { Class hsmProviderClazz = getHsmProviderClass(); final String hsmFacadeHost = basicConfig.getBasicConfiguration(CONFIG_PROP_HSM_FACADE_HOST); if (hsmProviderClazz != null && 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); //initialize HSM-Facade by using JAVA Reflection, because in that case HSM-Facade //has not be in ClassPath on every project Method initMethod = hsmProviderClazz.getMethod("init", X509Certificate.class, String.class, String.class, String.class, int.class); Object rawProvider = hsmProviderClazz.getMethod("getInstance", new Class[]{}).invoke(hsmProviderClazz); if (rawProvider instanceof Provider && initMethod != null) { initMethod.invoke( rawProvider, getHsmFacadeTrustSslCertificate(), clientUsername, clientPassword, hsmFacadeHost, port); 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("HSM-Facade class is type of 'java.security.Provider': {}", rawProvider instanceof Provider); log.warn("HSM-Facade class is supported 'init' method: {}", rawProvider instanceof Provider); 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); } } else { log.info("HSM Facade is not configurated. {} can only provide software keystores", EaafKeyStoreFactory.class.getSimpleName()); } } 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 { 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(getHsmFacadeKeyStoreParameter(keyStoreName)); return Pair.newInstance(keyStore, keyStore.getProvider()); } catch (NoSuchAlgorithmException | CertificateException | IOException | KeyStoreException | NoSuchProviderException | EaafException 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 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); } } 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; } }