From c4e1a45e7958cab402d83f6f4ae208df1bb2ab58 Mon Sep 17 00:00:00 2001 From: Thomas Lenz Date: Fri, 14 Feb 2020 15:22:13 +0100 Subject: add common-code for KeyStore and Credential handling --- .../core/exception/EaafKeyAccessException.java | 22 ++ .../core/impl/credential/EaafKeyStoreFactory.java | 1 + .../core/impl/credential/EaafKeyStoreUtils.java | 147 +++++++++++ .../impl/credential/KeyStoreConfiguration.java | 41 +++- .../messages/eaaf_utils_message.properties | 4 +- .../test/credentials/EaafKeyStoreFactoryTest.java | 81 ++++++ eaaf_core_utils/src/test/resources/data/junit.jks | Bin 0 -> 3980 bytes .../gv/egiz/eaaf/modules/auth/sl20/Constants.java | 40 ++- .../modules/auth/sl20/utils/JsonSecurityUtils.java | 271 ++++++++++----------- .../impl/utils/AbstractCredentialProvider.java | 11 +- 10 files changed, 460 insertions(+), 158 deletions(-) create mode 100644 eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/exception/EaafKeyAccessException.java create mode 100644 eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/EaafKeyStoreUtils.java create mode 100644 eaaf_core_utils/src/test/resources/data/junit.jks diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/exception/EaafKeyAccessException.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/exception/EaafKeyAccessException.java new file mode 100644 index 00000000..f9abd6d9 --- /dev/null +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/exception/EaafKeyAccessException.java @@ -0,0 +1,22 @@ +package at.gv.egiz.eaaf.core.exception; + +import at.gv.egiz.eaaf.core.exceptions.EaafException; + +public class EaafKeyAccessException extends EaafException { + + private static final long serialVersionUID = -2641273589744430903L; + + public static final String ERROR_CODE_08 = "internal.keystore.08"; + public static final String ERROR_CODE_09 = "internal.keystore.09"; + + public EaafKeyAccessException(String errorCode, String... params) { + super(errorCode, new Object[] {params}); + + } + + public EaafKeyAccessException(String errorCode, Throwable e, String... params) { + super(errorCode, new Object[] {params}, e); + + } + +} diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/EaafKeyStoreFactory.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/EaafKeyStoreFactory.java index f13013f5..5e6ca34b 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/EaafKeyStoreFactory.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/EaafKeyStoreFactory.java @@ -48,6 +48,7 @@ public class EaafKeyStoreFactory { 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"; diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/EaafKeyStoreUtils.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/EaafKeyStoreUtils.java new file mode 100644 index 00000000..b4b44724 --- /dev/null +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/EaafKeyStoreUtils.java @@ -0,0 +1,147 @@ +package at.gv.egiz.eaaf.core.impl.credential; + +import java.security.Key; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import at.gv.egiz.eaaf.core.exception.EaafKeyAccessException; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class EaafKeyStoreUtils { + private static final String ERROR_MSG_REASON = "Maybe 'Alias' is not valid"; + private static final String ERROR_MSG_1 = "Can NOT access key: {} in KeyStore: {}. Reason: {}"; + private static final String ERROR_MSG_2 = "Key: {} will be NOT available"; + + /** + * Read all certificates from a {@link KeyStore}. + * + * @param keyStore KeyStore with certificates + * @return {@link List} of {@link X509Certificate}, but never null + * @throws KeyStoreException In case of an error during KeyStore operations + */ + @Nonnull + public static List readCertsFromKeyStore(@Nonnull final KeyStore keyStore) throws KeyStoreException { + final List result = new ArrayList<>(); + + final Enumeration aliases = keyStore.aliases(); + while (aliases.hasMoreElements()) { + final String el = aliases.nextElement(); + log.trace("Process TrustStoreEntry: " + el); + if (keyStore.isCertificateEntry(el)) { + final Certificate cert = keyStore.getCertificate(el); + if (cert != null && cert instanceof X509Certificate) { + result.add((X509Certificate) cert); + } else { + log.info("Can not process entry: {}. Reason: {}", el, cert != null ? cert.getType() : "cert is null"); + } + + } + } + + return Collections.unmodifiableList(result); + } + + /** + * Get a specific private Key and the corresponding certificate from a {@link KeyStore}. + * + * @param keyStore KeyStore with certificates + * @param keyAlias Alias of the entry + * @param keyPassword Password to access the Key + * @param isRequired if true, the method throw an {@link EaafKeyAccessException} + * if the key is not available or invalid + * @param friendlyName Name of the KeyStore for logging purposes + * @return A {@link Pair} of {@link Key} and {@link X509Certificate} for this alias, + * or maybe null if isRequired was false + * @throws EaafKeyAccessException In case of an error during KeyStore operations + */ + @Nullable + public static Pair getPrivateKeyAndCertificates(@Nonnull KeyStore keyStore, + @Nonnull String keyAlias, @Nullable char[] keyPassword, boolean isRequired, @Nonnull String friendlyName) + throws EaafKeyAccessException { + try { + Key privKey = keyStore.getKey(keyAlias, keyPassword); + if (privKey != null) { + final Certificate[] certChainSigning = keyStore.getCertificateChain(keyAlias); + X509Certificate[] certChain = new X509Certificate[certChainSigning.length]; + + for (int i = 0; i < certChainSigning.length; i++) { + if (certChainSigning[i] instanceof X509Certificate) { + certChain[i] = (X509Certificate) certChainSigning[i]; + } else { + log.warn("NO X509 certificate for signing: " + certChainSigning[i].getType()); + } + + } + + Pair keyResult = Pair.newInstance(privKey, certChain); + validateKeyResult(keyResult, friendlyName, keyAlias); + return keyResult; + + } else { + if (isRequired) { + log.warn(ERROR_MSG_1, + keyAlias, friendlyName, ERROR_MSG_REASON); + throw new EaafKeyAccessException(EaafKeyAccessException.ERROR_CODE_09, + friendlyName, keyAlias, ERROR_MSG_REASON); + + } else { + log.info(ERROR_MSG_1, + keyAlias, friendlyName, ERROR_MSG_REASON); + log.info(ERROR_MSG_2, keyAlias); + + } + } + + } catch (KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException e) { + if (isRequired) { + log.warn(ERROR_MSG_1, + keyAlias, friendlyName, e.getMessage()); + throw new EaafKeyAccessException( + EaafKeyAccessException.ERROR_CODE_09, e, friendlyName, keyAlias, e.getMessage()); + + } else { + log.info(ERROR_MSG_1, + keyAlias, friendlyName, e.getMessage()); + log.info(ERROR_MSG_2, keyAlias); + + } + } + + return null; + + } + + private static void validateKeyResult(Pair keyResult, + String friendlyName, String keyAlias) throws EaafKeyAccessException { + // some short validation + if (!(keyResult.getFirst() instanceof PrivateKey)) { + log.info("PrivateKey: {} in KeyStore: {} is of wrong type", keyAlias, friendlyName); + throw new EaafKeyAccessException( + EaafKeyAccessException.ERROR_CODE_09, + friendlyName, keyAlias, "Wrong PrivateKey type "); + + } + + if (keyResult.getSecond() == null || keyResult.getSecond().length == 0) { + log.info("NO certificate for Key: {} in KeyStore: {}", keyAlias, friendlyName); + throw new EaafKeyAccessException( + EaafKeyAccessException.ERROR_CODE_09, + friendlyName, keyAlias, "NO certificate for PrivateKey"); + + } + } +} diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/KeyStoreConfiguration.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/KeyStoreConfiguration.java index 400b724f..6dbbba3e 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/KeyStoreConfiguration.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/KeyStoreConfiguration.java @@ -137,6 +137,31 @@ public class KeyStoreConfiguration { } + /** + * Validate the internal state of this configuration object. + * + * @throws EaafConfigurationException In case of a configuration error + */ + public void validate() throws EaafConfigurationException { + if (KeyStoreType.HSMFACADE.equals(keyStoreType)) { + log.trace("Validate HSM-Facade KeyStore ... "); + checkConfigurationValue(keyStoreName, EaafKeyStoreFactory.ERRORCODE_07, + friendlyName, "Missing 'KeyName' for HSM-Facade"); + + } else if (KeyStoreType.PKCS12.equals(keyStoreType) + || KeyStoreType.JKS.equals(keyStoreType)) { + log.trace("Validate software KeyStore ... "); + checkConfigurationValue(softKeyStoreFilePath, EaafKeyStoreFactory.ERRORCODE_07, + friendlyName, "Missing 'KeyPath' for software keystore"); + checkConfigurationValue(softKeyStorePassword, EaafKeyStoreFactory.ERRORCODE_07, + friendlyName, "Missing 'KeyPassword' for software keystore"); + + } else { + log.info("Validation of type: {} not supported yet", keyStoreType); + + } + } + public enum KeyStoreType { PKCS12("pkcs12"), JKS("jks"), HSMFACADE("hsmfacade"), PKCS11("pkcs11"); @@ -182,12 +207,18 @@ public class KeyStoreConfiguration { @Nonnull String configParamKey) throws EaafConfigurationException { final String configValue = config.get(configParamKey); - if (StringUtils.isEmpty(configValue)) { - throw new EaafConfigurationException(EaafKeyStoreFactory.ERRORCODE_04, new Object[] { configParamKey }); - - } - + checkConfigurationValue(configValue, EaafKeyStoreFactory.ERRORCODE_04, configParamKey); return configValue; + } + private static void checkConfigurationValue(String configValue, String errorCode, String... params) + throws EaafConfigurationException { + if (StringUtils.isEmpty(configValue)) { + throw new EaafConfigurationException(errorCode, + new Object[] { params}); + + } + + } } diff --git a/eaaf_core_utils/src/main/resources/messages/eaaf_utils_message.properties b/eaaf_core_utils/src/main/resources/messages/eaaf_utils_message.properties index 970b8e5a..2d9a863a 100644 --- a/eaaf_core_utils/src/main/resources/messages/eaaf_utils_message.properties +++ b/eaaf_core_utils/src/main/resources/messages/eaaf_utils_message.properties @@ -5,6 +5,8 @@ internal.keystore.03=HSM-Facade initialization failed with a generic error: {0} internal.keystore.04=HSM-Facade has a wrong configuration. Missing property: {0} internal.keystore.05=HSM-Facade has a wrong configuration. Property: {0} Reason:{1} internal.keystore.06=KeyStore: {0} initialization failed. Reason: {1} - +internal.keystore.07=Validation of KeyStore: {0} failed. Reason: {1} +internal.keystore.08=Can not access Key: {1} in KeyStore: {0} +internal.keystore.09=Can not access Key: {1} in KeyStore: {0} Reason: {2} diff --git a/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/credentials/EaafKeyStoreFactoryTest.java b/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/credentials/EaafKeyStoreFactoryTest.java index 805000cb..c47805e8 100644 --- a/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/credentials/EaafKeyStoreFactoryTest.java +++ b/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/credentials/EaafKeyStoreFactoryTest.java @@ -1,6 +1,10 @@ package at.gv.egiz.eaaf.core.test.credentials; +import java.security.Key; import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.cert.X509Certificate; +import java.util.List; import org.apache.commons.lang3.RandomStringUtils; import org.junit.Assert; @@ -20,12 +24,15 @@ import com.google.common.base.Predicates; import com.google.common.base.Throwables; import com.google.common.collect.FluentIterable; +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.EaafKeyStoreFactory; +import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreUtils; import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration; 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.test.dummy.DummyAuthConfigMap; import io.grpc.StatusRuntimeException; @@ -34,6 +41,8 @@ import io.grpc.StatusRuntimeException; @DirtiesContext(methodMode = MethodMode.BEFORE_METHOD) public class EaafKeyStoreFactoryTest { + private static final String PATH_TO_SOFTWARE_KEYSTORE_JKS_WITH_TRUSTED_CERTS = + "src/test/resources/data/junit.jks"; private static final String PATH_TO_SOFTWARE_KEYSTORE_JKS = "src/test/resources/data/junit_without_trustcerts.jks"; private static final String PATH_TO_SOFTWARE_KEYSTORE_PKCS12 = @@ -254,10 +263,78 @@ public class EaafKeyStoreFactoryTest { keyStoreConfig.setSoftKeyStoreFilePath(PATH_TO_SOFTWARE_KEYSTORE_JKS); keyStoreConfig.setSoftKeyStorePassword(SOFTWARE_KEYSTORE_PASSWORD); + keyStoreConfig.validate(); final KeyStore keyStore = keyStoreFactory.buildNewKeyStore(keyStoreConfig); Assert.assertNotNull("KeyStore is null", keyStore); + } + + @Test + @DirtiesContext + public void softwareKeyStoreAccessOperations() throws EaafException, KeyStoreException { + final EaafKeyStoreFactory keyStoreFactory = context.getBean(EaafKeyStoreFactory.class); + Assert.assertFalse("HSM Facade state wrong", keyStoreFactory.isHsmFacadeInitialized()); + + final KeyStoreConfiguration keyStoreConfig = new KeyStoreConfiguration(); + keyStoreConfig.setKeyStoreType(KeyStoreType.JKS); + keyStoreConfig.setSoftKeyStoreFilePath(PATH_TO_SOFTWARE_KEYSTORE_JKS_WITH_TRUSTED_CERTS); + keyStoreConfig.setSoftKeyStorePassword(SOFTWARE_KEYSTORE_PASSWORD); + + keyStoreConfig.validate(); + + final KeyStore keyStore = keyStoreFactory.buildNewKeyStore(keyStoreConfig); + Assert.assertNotNull("KeyStore is null", keyStore); + + //read trusted certs + List trustedCerts = EaafKeyStoreUtils.readCertsFromKeyStore(keyStore); + Assert.assertNotNull("Trusted certs", trustedCerts); + Assert.assertEquals("Trusted certs size", 2, trustedCerts.size()); + + //read priv. key + Pair privCred1 = EaafKeyStoreUtils.getPrivateKeyAndCertificates( + keyStore, "meta", "password".toCharArray(), true, "jUnit test"); + Assert.assertNotNull("Credential 1", privCred1); + Assert.assertNotNull("Credential 1 priv. key", privCred1.getFirst()); + Assert.assertNotNull("Credential 1 certificate", privCred1.getSecond()); + + //read priv. key + Pair privCred2 = EaafKeyStoreUtils.getPrivateKeyAndCertificates( + keyStore, "sig", "password".toCharArray(), true, "jUnit test"); + Assert.assertNotNull("Credential 2", privCred2); + Assert.assertNotNull("Credential 2 priv. key", privCred2.getFirst()); + Assert.assertNotNull("Credential 2 certificate", privCred2.getSecond()); + + + //read priv. key + Pair privCred3 = EaafKeyStoreUtils.getPrivateKeyAndCertificates( + keyStore, "notexist", "password".toCharArray(), false, "jUnit test"); + Assert.assertNull("Credential 3", privCred3); + + //read priv. key + Pair privCred4 = EaafKeyStoreUtils.getPrivateKeyAndCertificates( + keyStore, "meta", "wrong".toCharArray(), false, "jUnit test"); + Assert.assertNull("Credential 3", privCred4); + + try { + EaafKeyStoreUtils.getPrivateKeyAndCertificates( + keyStore, "meta", "wrong".toCharArray(), true, "jUnit test"); + Assert.fail("Wrong password not detected"); + + } catch (EaafKeyAccessException e) { + Assert.assertEquals("wrong errorcode", "internal.keystore.09", e.getErrorId()); + } + + try { + EaafKeyStoreUtils.getPrivateKeyAndCertificates( + keyStore, "wrong", "password".toCharArray(), true, "jUnit test"); + Assert.fail("Wrong alias not detected"); + + } catch (EaafKeyAccessException e) { + Assert.assertEquals("wrong errorcode", "internal.keystore.09", e.getErrorId()); + } + + } @Test @@ -271,6 +348,8 @@ public class EaafKeyStoreFactoryTest { keyStoreConfig.setSoftKeyStoreFilePath(PATH_TO_SOFTWARE_KEYSTORE_PKCS12); keyStoreConfig.setSoftKeyStorePassword(SOFTWARE_KEYSTORE_PASSWORD); + keyStoreConfig.validate(); + final KeyStore keyStore = keyStoreFactory.buildNewKeyStore(keyStoreConfig); Assert.assertNotNull("KeyStore is null", keyStore); @@ -524,6 +603,8 @@ public class EaafKeyStoreFactoryTest { keyStoreConfig.setKeyStoreType(KeyStoreType.HSMFACADE); keyStoreConfig.setKeyStoreName("testkeyStore"); + keyStoreConfig.validate(); + try { final KeyStore keyStore = keyStoreFactory.buildNewKeyStore(keyStoreConfig); Assert.assertNotNull("KeyStore is null", keyStore); diff --git a/eaaf_core_utils/src/test/resources/data/junit.jks b/eaaf_core_utils/src/test/resources/data/junit.jks new file mode 100644 index 00000000..59e6ad13 Binary files /dev/null and b/eaaf_core_utils/src/test/resources/data/junit.jks differ diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/Constants.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/Constants.java index f607f8cb..11fd41fb 100644 --- a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/Constants.java +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/Constants.java @@ -7,17 +7,37 @@ public class Constants { public static final String CONFIG_PROP_VDA_AUTHBLOCK_TRANSFORMATION_ID = CONFIG_PROP_PREFIX + ".vda.authblock.transformation.id"; - public static final String CONFIG_PROP_SECURITY_KEYSTORE_PATH = CONFIG_PROP_PREFIX + ".security.keystore.path"; - public static final String CONFIG_PROP_SECURITY_KEYSTORE_PASSWORD = CONFIG_PROP_PREFIX - + ".security.keystore.password"; - public static final String CONFIG_PROP_SECURITY_KEYSTORE_KEY_SIGN_ALIAS = CONFIG_PROP_PREFIX + ".security.sign.alias"; - public static final String CONFIG_PROP_SECURITY_KEYSTORE_KEY_SIGN_PASSWORD = CONFIG_PROP_PREFIX - + ".security.sign.password"; - public static final String CONFIG_PROP_SECURITY_KEYSTORE_KEY_ENCRYPTION_ALIAS = CONFIG_PROP_PREFIX - + ".security.encryption.alias"; - public static final String CONFIG_PROP_SECURITY_KEYSTORE_KEY_ENCRYPTION_PASSWORD = CONFIG_PROP_PREFIX - + ".security.encryption.password"; + + //KeyStore configuration + public static final String CONFIG_PROP_SECURITY_KEYSTORE_TYPE = + CONFIG_PROP_PREFIX + ".security.keystore.type"; + public static final String CONFIG_PROP_SECURITY_KEYSTORE_NAME = + CONFIG_PROP_PREFIX + ".security.keystore.name"; + public static final String CONFIG_PROP_SECURITY_KEYSTORE_PATH = + CONFIG_PROP_PREFIX + ".security.keystore.path"; + public static final String CONFIG_PROP_SECURITY_KEYSTORE_PASSWORD = + CONFIG_PROP_PREFIX + ".security.keystore.password"; + + public static final String CONFIG_PROP_SECURITY_KEYSTORE_KEY_SIGN_ALIAS = + CONFIG_PROP_PREFIX + ".security.sign.alias"; + public static final String CONFIG_PROP_SECURITY_KEYSTORE_KEY_SIGN_PASSWORD = + CONFIG_PROP_PREFIX + ".security.sign.password"; + public static final String CONFIG_PROP_SECURITY_KEYSTORE_KEY_ENCRYPTION_ALIAS = + CONFIG_PROP_PREFIX + ".security.encryption.alias"; + public static final String CONFIG_PROP_SECURITY_KEYSTORE_KEY_ENCRYPTION_PASSWORD = + CONFIG_PROP_PREFIX + ".security.encryption.password"; + //TrustStore configuration + public static final String CONFIG_PROP_SECURITY_TRUSTSTORE_TYPE = + CONFIG_PROP_PREFIX + ".security.truststore.type"; + public static final String CONFIG_PROP_SECURITY_TRUSTSTORE_NAME = + CONFIG_PROP_PREFIX + ".security.truststore.name"; + public static final String CONFIG_PROP_SECURITY_TRUSTSTORE_PATH = + CONFIG_PROP_PREFIX + ".security.truststore.path"; + public static final String CONFIG_PROP_SECURITY_TRUSTSTORE_PASSWORD = + CONFIG_PROP_PREFIX + ".security.truststore.password"; + + public static final String CONFIG_PROP_VDA_ENDPOINT_QUALeID_DEFAULT_ELEMENT = "default"; public static final String CONFIG_PROP_VDA_ENDPOINT_QUALeID_DEFAULT = CONFIG_PROP_VDA_ENDPOINT_QUALeID + CONFIG_PROP_VDA_ENDPOINT_QUALeID_DEFAULT_ELEMENT; diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JsonSecurityUtils.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JsonSecurityUtils.java index 0d2c1815..259c21bf 100644 --- a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JsonSecurityUtils.java +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JsonSecurityUtils.java @@ -1,17 +1,12 @@ package at.gv.egiz.eaaf.modules.auth.sl20.utils; import java.io.IOException; -import java.net.MalformedURLException; import java.security.Key; import java.security.KeyStore; import java.security.KeyStoreException; -import java.security.PrivateKey; -import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; -import java.util.ArrayList; import java.util.Collections; -import java.util.Enumeration; import java.util.List; import javax.annotation.Nonnull; @@ -38,9 +33,13 @@ import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.JsonNode; 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.impl.utils.FileUtils; -import at.gv.egiz.eaaf.core.impl.utils.KeyStoreUtils; +import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory; +import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreUtils; +import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration; +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.X509Utils; import at.gv.egiz.eaaf.modules.auth.sl20.Constants; import at.gv.egiz.eaaf.modules.auth.sl20.data.VerificationResult; @@ -53,88 +52,60 @@ import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SlCommandoParserException; public class JsonSecurityUtils implements IJoseTools { private static final Logger log = LoggerFactory.getLogger(JsonSecurityUtils.class); - @Autowired(required = true) - IConfiguration authConfig; - private Key signPrivKey = null; - private X509Certificate[] signCertChain = null; - - private Key encPrivKey = null; - private X509Certificate[] encCertChain = null; - - private List trustedCerts = new ArrayList<>(); - + private static final String FRIENDLYNAME_KEYSTORE = "SL2.0 KeyStore"; + private static final String FRIENDLYNAME_TRUSTSTORE = "SL2.0 TrustStore"; + + @Autowired(required = true) IConfiguration authConfig; + @Autowired(required = true) EaafKeyStoreFactory keystoreFactory; + + private KeyStore keyStore; + private KeyStore trustStore; + private static JsonMapper mapper = new JsonMapper(); @PostConstruct - protected void initalize() { + protected void initalize() throws SL20Exception { log.info("Initialize SL2.0 authentication security constrains ... "); try { - if (getKeyStoreFilePath() != null) { - final KeyStore keyStore = KeyStoreUtils.loadKeyStore(getKeyStoreFilePath(), getKeyStorePassword()); - - // load signing key - signPrivKey = keyStore.getKey(getSigningKeyAlias(), getSigningKeyPassword().toCharArray()); - final Certificate[] certChainSigning = keyStore.getCertificateChain(getSigningKeyAlias()); - signCertChain = new X509Certificate[certChainSigning.length]; - for (int i = 0; i < certChainSigning.length; i++) { - if (certChainSigning[i] instanceof X509Certificate) { - signCertChain[i] = (X509Certificate) certChainSigning[i]; - } else { - log.warn("NO X509 certificate for signing: " + certChainSigning[i].getType()); - } - - } - - // load encryption key - try { - encPrivKey = keyStore.getKey(getEncryptionKeyAlias(), getEncryptionKeyPassword().toCharArray()); - if (encPrivKey != null) { - final Certificate[] certChainEncryption = keyStore.getCertificateChain(getEncryptionKeyAlias()); - encCertChain = new X509Certificate[certChainEncryption.length]; - for (int i = 0; i < certChainEncryption.length; i++) { - if (certChainEncryption[i] instanceof X509Certificate) { - encCertChain[i] = (X509Certificate) certChainEncryption[i]; - } else { - log.warn("NO X509 certificate for encryption: " + certChainEncryption[i].getType()); - } - } - } else { - log.info("No encryption key for SL2.0 found. End-to-End encryption is not used."); - } - - } catch (final Exception e) { - log.warn("No encryption key for SL2.0 found. End-to-End encryption is not used. Reason: " + e.getMessage(), - e); - - } - - // load trusted certificates - trustedCerts = readCertsFromKeyStore(keyStore); - - // some short validation - if (signPrivKey == null || !(signPrivKey instanceof PrivateKey)) { - log.info("Can NOT open privateKey for SL2.0 signing. KeyStore=" + getKeyStoreFilePath()); - throw new SL20Exception("sl20.03", new Object[] { "Can NOT open private key for signing" }); - - } - - if (signCertChain == null || signCertChain.length == 0) { - log.info("NO certificate for SL2.0 signing. KeyStore=" + getKeyStoreFilePath()); - throw new SL20Exception("sl20.03", new Object[] { "NO certificate for SL2.0 signing" }); - - } - - log.info("SL2.0 authentication security constrains initialized."); - + //load KeyStore + KeyStoreConfiguration keyStoreConfig = buildKeyStoreConfiguration(); + keyStore = keystoreFactory.buildNewKeyStore(keyStoreConfig); + + //load TrustStore + KeyStoreConfiguration trustStoreConfig = buildTrustStoreConfiguration(); + trustStore = keystoreFactory.buildNewKeyStore(trustStoreConfig); + + //validate KeyStore entries + EaafKeyStoreUtils.getPrivateKeyAndCertificates(keyStore, getSigningKeyAlias(), + getSigningKeyPassword(), true, FRIENDLYNAME_KEYSTORE); + Pair encCredentials = + EaafKeyStoreUtils.getPrivateKeyAndCertificates(keyStore, getEncryptionKeyAlias(), + getEncryptionKeyPassword(), false, FRIENDLYNAME_TRUSTSTORE); + if (encCredentials == null) { + log.info("No encryption key for SL2.0 found. End-to-End encryption is not used."); + + } + + //validate TrustStore + List trustedCerts = EaafKeyStoreUtils.readCertsFromKeyStore(trustStore); + if (trustedCerts.isEmpty()) { + log.info("No certificates in TrustStore: {}. Signature validation will FAIL!", + FRIENDLYNAME_TRUSTSTORE); + } else { - log.info("NO SL2.0 authentication security configuration. Initialization was skipped"); + log.info("Find #{} certificates in TrustStore: {}", + trustedCerts.size(), FRIENDLYNAME_TRUSTSTORE); + } + + log.info("SL2.0 authentication security constrains initialized."); } catch (final RuntimeException e) { throw e; } catch (final Exception e) { - log.error("SL2.0 security constrains initialization FAILED.", e); + log.error("SL2.0 security constrains initialization FAILED."); + throw new SL20Exception("sl20.11", new Object[] {e.getMessage()}, e); } @@ -153,15 +124,18 @@ public class JsonSecurityUtils implements IJoseTools { // set signing information jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256); - jws.setKey(signPrivKey); + Pair signingCred = EaafKeyStoreUtils.getPrivateKeyAndCertificates(keyStore, + getSigningKeyAlias(), getSigningKeyPassword(), true, FRIENDLYNAME_KEYSTORE); + + jws.setKey(signingCred.getFirst()); // TODO: - jws.setCertificateChainHeaderValue(signCertChain); - jws.setX509CertSha256ThumbprintHeaderValue(signCertChain[0]); + jws.setCertificateChainHeaderValue(signingCred.getSecond()); + jws.setX509CertSha256ThumbprintHeaderValue(signingCred.getSecond()[0]); return jws.getCompactSerialization(); - } catch (final JoseException e) { + } catch (final JoseException | EaafKeyAccessException e) { log.warn("Can NOT sign SL2.0 command.", e); throw new SlCommandoBuildException("Can NOT sign SL2.0 command.", e); @@ -172,7 +146,7 @@ public class JsonSecurityUtils implements IJoseTools { @Override public VerificationResult validateSignature(final String serializedContent, final KeyStore trustStore, final AlgorithmConstraints algconstraints) throws JoseException, IOException, KeyStoreException { - final List trustedCertificates = readCertsFromKeyStore(trustStore); + final List trustedCertificates = EaafKeyStoreUtils.readCertsFromKeyStore(trustStore); return validateSignature(serializedContent, trustedCertificates, algconstraints); } @@ -244,7 +218,8 @@ public class JsonSecurityUtils implements IJoseTools { SL20Constants.SL20_ALGORITHM_WHITELIST_SIGNING .toArray(new String[SL20Constants.SL20_ALGORITHM_WHITELIST_SIGNING.size()])); - final VerificationResult result = validateSignature(serializedContent, trustedCerts, algConstraints); + final VerificationResult result = + validateSignature(serializedContent, EaafKeyStoreUtils.readCertsFromKeyStore(trustStore), algConstraints); if (!result.isValidSigned()) { log.info("JWS signature invalide. Stopping authentication process ..."); @@ -256,7 +231,7 @@ public class JsonSecurityUtils implements IJoseTools { log.debug("SL2.0 commando signature validation sucessfull"); return result; - } catch (JoseException | JsonParseException e) { + } catch (JoseException | JsonParseException | KeyStoreException e) { log.warn("SL2.0 commando signature validation FAILED", e); throw new SL20SecurityException(new Object[] { e.getMessage() }, e); @@ -284,6 +259,9 @@ public class JsonSecurityUtils implements IJoseTools { // set payload receiverJwe.setCompactSerialization(compactSerialization); + Pair encryptionCred = EaafKeyStoreUtils.getPrivateKeyAndCertificates(keyStore, + getEncryptionKeyAlias(), getEncryptionKeyPassword(), true, FRIENDLYNAME_KEYSTORE); + // validate key from header against key from config final List x5cCerts = receiverJwe.getCertificateChainHeaderValue(); final String x5t256 = receiverJwe.getX509CertSha256ThumbprintHeaderValue(); @@ -292,7 +270,7 @@ public class JsonSecurityUtils implements IJoseTools { log.trace("Sorting received X509 certificates ... "); final List sortedX5cCerts = X509Utils.sortCertificates(x5cCerts); - if (!sortedX5cCerts.get(0).equals(encCertChain[0])) { + if (!sortedX5cCerts.get(0).equals(encryptionCred.getSecond()[0])) { log.info("Certificate from JOSE header does NOT match encryption certificate"); try { @@ -307,7 +285,7 @@ public class JsonSecurityUtils implements IJoseTools { } else if (StringUtils.isNotEmpty(x5t256)) { log.debug("Found x5t256 fingerprint in JOSE header .... "); - final String certFingerPrint = X509Util.x5tS256(encCertChain[0]); + final String certFingerPrint = X509Util.x5tS256(encryptionCred.getSecond()[0]); if (!certFingerPrint.equals(x5t256)) { log.info("X5t256 from JOSE header does NOT match encryption certificate"); log.debug("X5t256 from JOSE header: " + x5t256 + " Encrytption cert: " + certFingerPrint); @@ -324,12 +302,12 @@ public class JsonSecurityUtils implements IJoseTools { } // set key - receiverJwe.setKey(encPrivKey); + receiverJwe.setKey(encryptionCred.getFirst()); // decrypt payload return mapper.getMapper().readTree(receiverJwe.getPlaintextString()); - } catch (final JoseException e) { + } catch (final JoseException | EaafKeyAccessException e) { log.warn("SL2.0 result decryption FAILED", e); throw new SL20SecurityException(new Object[] { e.getMessage() }, e); @@ -340,37 +318,74 @@ public class JsonSecurityUtils implements IJoseTools { } catch (final IOException e) { log.warn("Decrypted SL2.0 result can not be parsed.", e); throw new SlCommandoParserException("Decrypted SL2.0 result can not be parsed", e); + } - } @Override public X509Certificate getEncryptionCertificate() { - // TODO: maybe update after SL2.0 update on encryption certificate parts - if (encCertChain != null && encCertChain.length > 0) { - return encCertChain[0]; - } else { - return null; + Pair encryptionCred; + try { + encryptionCred = EaafKeyStoreUtils.getPrivateKeyAndCertificates(keyStore, + getEncryptionKeyAlias(), getEncryptionKeyPassword(), false, FRIENDLYNAME_KEYSTORE); + if (encryptionCred != null && encryptionCred.getSecond().length > 0) { + return encryptionCred.getSecond()[0]; + + } + + } catch (EaafKeyAccessException e) { + log.trace("Exception is skipped because Encryption is not mandatory on this level", e); + } + + return null; + } - private String getKeyStoreFilePath() throws EaafConfigurationException, MalformedURLException { - return FileUtils.makeAbsoluteUrl(authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_KEYSTORE_PATH), - authConfig.getConfigurationRootDirectory()); + private KeyStoreConfiguration buildKeyStoreConfiguration() throws EaafConfigurationException { + KeyStoreConfiguration config = new KeyStoreConfiguration(); + config.setFriendlyName(FRIENDLYNAME_KEYSTORE); + + config.setKeyStoreType(authConfig.getBasicConfiguration( + authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_KEYSTORE_TYPE), + KeyStoreType.JKS.getKeyStoreType())); + config.setKeyStoreName( + authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_KEYSTORE_NAME)); + config.setSoftKeyStoreFilePath( + authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_KEYSTORE_PATH)); + config.setSoftKeyStorePassword( + authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_KEYSTORE_PASSWORD)); + + //validate configuration state + config.validate(); + + return config; + } - - private String getKeyStorePassword() { - String value = authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_KEYSTORE_PASSWORD); - if (value != null) { - value = value.trim(); - } - - return value; - + + private KeyStoreConfiguration buildTrustStoreConfiguration() throws EaafConfigurationException { + KeyStoreConfiguration config = new KeyStoreConfiguration(); + config.setFriendlyName(FRIENDLYNAME_TRUSTSTORE); + + config.setKeyStoreType(authConfig.getBasicConfiguration( + authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_TRUSTSTORE_TYPE), + KeyStoreType.JKS.getKeyStoreType())); + config.setKeyStoreName( + authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_TRUSTSTORE_NAME)); + config.setSoftKeyStoreFilePath( + authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_TRUSTSTORE_PATH)); + config.setSoftKeyStorePassword( + authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_TRUSTSTORE_PASSWORD)); + + //validate configuration state + config.validate(); + + return config; } + private String getSigningKeyAlias() { - String value = authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_KEYSTORE_KEY_SIGN_ALIAS).trim(); + String value = authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_KEYSTORE_KEY_SIGN_ALIAS); if (value != null) { value = value.trim(); } @@ -378,18 +393,17 @@ public class JsonSecurityUtils implements IJoseTools { return value; } - private String getSigningKeyPassword() { - String value = authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_KEYSTORE_KEY_SIGN_PASSWORD).trim(); + private char[] getSigningKeyPassword() { + String value = authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_KEYSTORE_KEY_SIGN_PASSWORD); if (value != null) { - value = value.trim(); + return value.trim().toCharArray(); } - return value; + return null; } private String getEncryptionKeyAlias() { - String value = authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_KEYSTORE_KEY_ENCRYPTION_ALIAS) - .trim(); + String value = authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_KEYSTORE_KEY_ENCRYPTION_ALIAS); if (value != null) { value = value.trim(); } @@ -397,36 +411,13 @@ public class JsonSecurityUtils implements IJoseTools { return value; } - private String getEncryptionKeyPassword() { - String value = authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_KEYSTORE_KEY_ENCRYPTION_PASSWORD) - .trim(); + private char[] getEncryptionKeyPassword() { + String value = authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_KEYSTORE_KEY_ENCRYPTION_PASSWORD); if (value != null) { - value = value.trim(); - } - - return value; - } - - @Nonnull - private List readCertsFromKeyStore(@Nonnull final KeyStore keyStore) throws KeyStoreException { - final List result = new ArrayList<>(); - - final Enumeration aliases = keyStore.aliases(); - while (aliases.hasMoreElements()) { - final String el = aliases.nextElement(); - log.trace("Process TrustStoreEntry: " + el); - if (keyStore.isCertificateEntry(el)) { - final Certificate cert = keyStore.getCertificate(el); - if (cert != null && cert instanceof X509Certificate) { - result.add((X509Certificate) cert); - } else { - log.info("Can not process entry: {}. Reason: {}", el, cert != null ? cert.getType() : "cert is null"); - } - - } + return value.trim().toCharArray(); } - return Collections.unmodifiableList(result); + return null; } } diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/AbstractCredentialProvider.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/AbstractCredentialProvider.java index 6477d8ff..cd77228c 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/AbstractCredentialProvider.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/AbstractCredentialProvider.java @@ -73,7 +73,13 @@ public abstract class AbstractCredentialProvider implements IPvp2CredentialProvi * @return keyStore friendlyName */ public final String getFriendlyName() { - return getBasicKeyStoreConfig().getFriendlyName(); + try { + return getBasicKeyStoreConfig().getFriendlyName(); + + } catch (EaafConfigurationException e) { + return "No KeyStoreName"; + + } } @@ -81,9 +87,10 @@ public abstract class AbstractCredentialProvider implements IPvp2CredentialProvi * Get the basic KeyStore configuration object for this SAML2 credential. * * @return KeyStore configuration object + * @throws EaafConfigurationException In case of a configuration error */ @Nonnull - public abstract KeyStoreConfiguration getBasicKeyStoreConfig(); + public abstract KeyStoreConfiguration getBasicKeyStoreConfig() throws EaafConfigurationException; /** * Get alias of key for metadata signing. -- cgit v1.2.3