summaryrefslogtreecommitdiff
path: root/eaaf_core_utils/src/main
diff options
context:
space:
mode:
authorThomas <>2023-05-08 17:24:41 +0200
committerThomas <>2023-05-08 17:24:41 +0200
commit1e5c2de3a4aafb476070478b27a18caf9efc051b (patch)
tree57abbdf5554a35725f49c3c7f0458aebea0faeea /eaaf_core_utils/src/main
parent632a2a06d450da92685811325e7967a4f7471cae (diff)
downloadEAAF-Components-1e5c2de3a4aafb476070478b27a18caf9efc051b.tar.gz
EAAF-Components-1e5c2de3a4aafb476070478b27a18caf9efc051b.tar.bz2
EAAF-Components-1e5c2de3a4aafb476070478b27a18caf9efc051b.zip
feat(core): add in-line method to KeyStoreFactory
The keystore type 'inline' can be used to build a keystore by using PEM encoded certificate and key files. Example: pkcs12:keystore?private=certs/key.pem&cert=certs/certificate.pem
Diffstat (limited to 'eaaf_core_utils/src/main')
-rw-r--r--eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/EaafKeyStoreFactory.java39
-rw-r--r--eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/KeyStoreConfiguration.java7
-rw-r--r--eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/inline/InlineKeyStoreBuilder.java165
-rw-r--r--eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/inline/InlineKeyStoreParser.java149
-rw-r--r--eaaf_core_utils/src/main/resources/messages/eaaf_utils_message.properties5
5 files changed, 363 insertions, 2 deletions
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 623e9d2c..fc3fa19d 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
@@ -5,6 +5,9 @@ import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStore.LoadStoreParameter;
@@ -39,6 +42,7 @@ 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.credential.inline.InlineKeyStoreParser;
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;
@@ -64,6 +68,9 @@ public class EaafKeyStoreFactory {
public static final String ERRORCODE_07 = "internal.keystore.07";
public static final String ERRORCODE_10 = "internal.keystore.10";
public static final String ERRORCODE_11 = "internal.keystore.11";
+ public static final String ERRORCODE_12 = "internal.keystore.12";
+ public static final String ERRORCODE_13 = "internal.keystore.13";
+ public static final String ERRORCODE_14 = "internal.keystore.14";
public static final String ERRORCODE_KEY_00 = "internal.key.00";
@@ -142,6 +149,9 @@ public class EaafKeyStoreFactory {
|| KeyStoreType.JKS.equals(config.getKeyStoreType())) {
return getKeyStoreFromFileSystem(config);
+ } else if (KeyStoreType.INLINE.equals(config.getKeyStoreType())) {
+ return getKeyStoreFromInlineConfiguration(config);
+
} else if (KeyStoreType.HSMFACADE.equals(config.getKeyStoreType())) {
if (isHsmFacadeInitialized) {
return getKeyStoreFromHsmFacade(config);
@@ -339,6 +349,33 @@ public class EaafKeyStoreFactory {
}
@Nonnull
+ private Pair<KeyStore, Provider> getKeyStoreFromInlineConfiguration(KeyStoreConfiguration config)
+ throws EaafConfigurationException {
+ try {
+ log.debug("Loading keystore from in-line configuration URL ... ");
+ return Pair.newInstance(
+ InlineKeyStoreParser.buildKeyStore(
+ new URL(null,
+ config.getSoftKeyStoreFilePath(),
+ new InlineKeyStoreParser()),
+ resourceLoader,
+ basicConfig.getConfigurationRootDirectory()),
+ null);
+
+ } catch (MalformedURLException e) {
+ log.error("Inline KeyStore URL has no valid form.", e);
+ throw new EaafConfigurationException(ERRORCODE_13,
+ new Object[] { config.getSoftKeyStoreFilePath(), e.getMessage() }, e);
+
+ } catch (IOException | GeneralSecurityException e) {
+ log.error("Inline KeyStore initialization FAILED with an generic error.", e);
+ throw new EaafConfigurationException(ERRORCODE_13, new Object[] { e.getMessage() }, e);
+
+ }
+
+ }
+
+ @Nonnull
private Pair<KeyStore, Provider> getKeyStoreFromFileSystem(KeyStoreConfiguration config)
throws EaafConfigurationException, EaafFactoryException {
try {
@@ -384,7 +421,7 @@ public class EaafKeyStoreFactory {
} catch (final Exception e) {
log.error("Software KeyStore initialization FAILED with an generic error.", e);
- throw new EaafConfigurationException(ERRORCODE_03, new Object[] { e.getMessage() }, e);
+ throw new EaafConfigurationException(ERRORCODE_12, new Object[] { e.getMessage() }, e);
}
}
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 c1a1d917..7e66ca86 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
@@ -154,6 +154,11 @@ public class KeyStoreConfiguration {
checkConfigurationValue(keyStoreName, EaafKeyStoreFactory.ERRORCODE_07,
friendlyName, "Missing 'KeyName' for HSM-Facade");
+ } else if (KeyStoreType.INLINE.equals(keyStoreType)) {
+ log.trace("Validate in-line KeyStore ... ");
+ checkConfigurationValue(softKeyStoreFilePath, EaafKeyStoreFactory.ERRORCODE_07,
+ friendlyName, "Missing 'KeyPath' for in-line keystore");
+
} else if (KeyStoreType.PKCS12.equals(keyStoreType)
|| KeyStoreType.JKS.equals(keyStoreType)) {
log.trace("Validate software KeyStore ... ");
@@ -169,7 +174,7 @@ public class KeyStoreConfiguration {
}
public enum KeyStoreType {
- PKCS12("pkcs12"), JKS("jks"), HSMFACADE("hsmfacade"), PKCS11("pkcs11");
+ PKCS12("pkcs12"), JKS("jks"), HSMFACADE("hsmfacade"), PKCS11("pkcs11"), INLINE("inline");
private final String keyStoreType;
diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/inline/InlineKeyStoreBuilder.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/inline/InlineKeyStoreBuilder.java
new file mode 100644
index 00000000..a1e3a824
--- /dev/null
+++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/inline/InlineKeyStoreBuilder.java
@@ -0,0 +1,165 @@
+package at.gv.egiz.eaaf.core.impl.credential.inline;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URI;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Objects;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.openssl.PEMKeyPair;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+
+import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException;
+import at.gv.egiz.eaaf.core.impl.utils.FileUtils;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Convenience class to load keys and certificates in PEM-format into a
+ * {@link KeyStore} by providing the file paths.
+ */
+@Slf4j
+public class InlineKeyStoreBuilder {
+ private final KeyStore keyStore;
+ private final ResourceLoader loader;
+ private final URI configRootDir;
+
+ /**
+ * Create a new instance.
+ *
+ * @param type the type of the KeyStore to be built, e.g.
+ * "PKCS12"
+ * @param configRootDirectory Root directory for configuration
+ * @param resourceLoader Spring ResourceLoader
+ * @throws GeneralSecurityException if the empty KeyStore object could not be
+ * created
+ * @throws IOException if the empty KeyStore object could not be
+ * created
+ */
+ public InlineKeyStoreBuilder(String type, ResourceLoader resourceLoader,
+ URI configRootDirectory)
+ throws GeneralSecurityException, IOException {
+ loader = resourceLoader;
+ configRootDir = configRootDirectory;
+
+ keyStore = KeyStore.getInstance(type);
+ keyStore.load(null, null);
+
+ }
+
+ /**
+ * Sets a key entry, i.e. build a key store.
+ *
+ * @param privateKeyFile the path to the private key file
+ * @param certificateFiles the paths to the certificate files
+ * @throws GeneralSecurityException if a file could not be parsed
+ * @throws IOException if a file could not be read
+ * @throws EaafConfigurationException if a file could not be found
+ */
+ public void setKeyEntry(String privateKeyFile, String[] certificateFiles)
+ throws GeneralSecurityException, IOException, EaafConfigurationException {
+ final X509Certificate[] certificates = readCertificates(certificateFiles);
+ final PrivateKey privateKey = readPrivateKey(privateKeyFile);
+ keyStore.setKeyEntry("keyEntry", privateKey, new char[0], certificates);
+
+ }
+
+ /**
+ * Sets certificate entries, i.e. build a trust store.
+ *
+ * @param certificateFiles the path to the certificate files
+ * @throws GeneralSecurityException if a file could not be parsed
+ */
+ public void setCertificateEntries(String[] certificateFiles) throws GeneralSecurityException {
+
+ final X509Certificate[] certificates = readCertificates(certificateFiles);
+
+ for (int i = 0; i < certificates.length; i++) {
+ keyStore.setCertificateEntry("certificateEntry" + i, certificates[i]);
+ }
+ }
+
+ /**
+ * Sets a secret key, i.e. builds a keystore.
+ *
+ * @param secret the secret encoded as a Base64 string
+ * @throws KeyStoreException if the secret key could not be added to the store
+ */
+ public void setSecretEntry(final String secret) throws KeyStoreException {
+ final byte[] decodedKey = Base64.getDecoder().decode(secret);
+ final SecretKey key = new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
+ keyStore.setEntry("secret", new KeyStore.SecretKeyEntry(key),
+ new KeyStore.PasswordProtection(new char[] {}));
+ }
+
+ public KeyStore getKeyStore() {
+ return keyStore;
+ }
+
+ private X509Certificate[] readCertificates(String[] certificateFiles) throws CertificateException {
+ final CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
+
+ return Arrays.stream(certificateFiles).map(certificateFile -> {
+ try (InputStream is = readResourceFromFile(certificateFile)) {
+ return (X509Certificate) certificateFactory.generateCertificate(is);
+
+ } catch (CertificateException | IOException | EaafConfigurationException e) {
+ log.error("Failed to load certificate file {}.", certificateFile, e);
+ return null;
+
+ }
+ }).filter(Objects::nonNull).toArray(X509Certificate[]::new);
+ }
+
+ private PrivateKey readPrivateKey(String filePath) throws IOException, EaafConfigurationException {
+ try (
+ Reader fileReader = new InputStreamReader(readResourceFromFile(filePath));
+ PEMParser pemParser = new PEMParser(fileReader)) {
+
+ final Object object = pemParser.readObject();
+ final JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
+
+ if (object instanceof PrivateKeyInfo) {
+ return converter.getPrivateKey((PrivateKeyInfo) object);
+
+ } else if (object instanceof PEMKeyPair) {
+ return converter.getKeyPair((PEMKeyPair) object).getPrivate();
+
+ } else {
+ throw new IllegalArgumentException("Unsupported object: " + object.getClass());
+
+ }
+ }
+ }
+
+ private InputStream readResourceFromFile(String filePath) throws IOException, EaafConfigurationException {
+ final String absKeyStorePath = FileUtils.makeAbsoluteUrl(filePath, configRootDir);
+ log.debug("Use filepath from config: {}", absKeyStorePath);
+ Resource ressource = loader.getResource(absKeyStorePath);
+
+ if (!ressource.exists()) {
+ throw new EaafConfigurationException("internal.keystore.15",
+ new Object[] { "RessourceLoader does NOT find File at: " + ressource.getURI() });
+
+ }
+
+ return ressource.getInputStream();
+
+ }
+}
diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/inline/InlineKeyStoreParser.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/inline/InlineKeyStoreParser.java
new file mode 100644
index 00000000..0ddc2680
--- /dev/null
+++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/inline/InlineKeyStoreParser.java
@@ -0,0 +1,149 @@
+package at.gv.egiz.eaaf.core.impl.credential.inline;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLDecoder;
+import java.net.URLStreamHandler;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.util.AbstractMap;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.springframework.core.io.ResourceLoader;
+
+import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Parse in-line keystore configuration in BRZ format.
+ *
+ * @author tlenz
+ *
+ */
+@Slf4j
+public class InlineKeyStoreParser extends URLStreamHandler {
+
+ @Override
+ protected URLConnection openConnection(URL u) throws IOException {
+ log.error("openConnection is not implemented by {}", InlineKeyStoreParser.class);
+ return null;
+
+ }
+
+ /**
+ * Build a {@link KeyStore} object from an in-line configuration {@link URL}.
+ *
+ * @param configUrl configuration URL
+ * @param configRootDirectory Root directory for configuration
+ * @param resourceLoader Spring ResourceLoader
+ * @return KeyStore created by configuration URL
+ * @throws IOException In case of a configuration read error
+ * @throws GeneralSecurityException In case of a KeyStore configuration error
+ * @throws EaafConfigurationException If keyfile can not be found
+ */
+ public static KeyStore buildKeyStore(URL configUrl, ResourceLoader resourceLoader, URI configRootDirectory)
+ throws IOException, GeneralSecurityException, EaafConfigurationException {
+ log.trace("Parsing keystore config from URL: {}", configUrl);
+ InlineKeyStoreBuilder keyStoreBuilder = new InlineKeyStoreBuilder(
+ configUrl.getProtocol(), resourceLoader, configRootDirectory);
+
+ // parse basis properties from URL
+ final Map<String, List<String>> queryParams = parseQuery(configUrl.getQuery());
+ final String[] certificateFiles = queryParams.getOrDefault("cert", Collections.emptyList())
+ .toArray(String[]::new);
+
+ if ("keystore".equalsIgnoreCase(configUrl.getPath())) {
+ parseKeyStore(keyStoreBuilder, queryParams, certificateFiles);
+
+ } else if ("truststore".equalsIgnoreCase(configUrl.getPath())) {
+ keyStoreBuilder.setCertificateEntries(certificateFiles);
+
+ } else {
+ throw new IllegalArgumentException("Unknown store type: " + configUrl.getPath());
+
+ }
+
+ return keyStoreBuilder.getKeyStore();
+ }
+
+ /**
+ * Takes the <i>queryParams</i> and builds based on them a {@link KeyStore} with
+ * secret and/or private key.
+ *
+ * @param keyStoreBuilder
+ *
+ * @param queryParams a map of all query params
+ * @param certificateFiles list of certificates
+ * @throws GeneralSecurityException if private or secret key cannot be parsed
+ * @throws IOException if private or secret key cannot be parsed
+ * @throws EaafConfigurationException if keyfile can not be found
+ */
+ private static void parseKeyStore(InlineKeyStoreBuilder keyStoreBuilder,
+ final Map<String, List<String>> queryParams, final String[] certificateFiles)
+ throws GeneralSecurityException, IOException, EaafConfigurationException {
+ final List<String> privateKeyList = queryParams.get("private");
+ final List<String> secretKeyList = queryParams.get("inlineSecret");
+
+ if (privateKeyList == null && secretKeyList == null) {
+ throw new IllegalArgumentException("Neither secret key nor private key are configured!");
+
+ }
+
+ if (privateKeyList != null) {
+ if (privateKeyList.size() == 1) {
+ final String privateKeyFile = queryParams.get("private").get(0);
+ keyStoreBuilder.setKeyEntry(privateKeyFile, certificateFiles);
+
+ } else {
+ throw new IllegalArgumentException("Exactly one private key must be specified!");
+
+ }
+ }
+
+ if (secretKeyList != null) {
+ if (secretKeyList.size() == 1) {
+ final String secret = URLDecoder.decode(secretKeyList.get(0), StandardCharsets.UTF_8);
+ keyStoreBuilder.setSecretEntry(secret);
+
+ } else {
+ throw new IllegalArgumentException("Exactly one secret key must be specified!");
+
+ }
+ }
+ }
+
+ private static Map<String, List<String>> parseQuery(String query) {
+ if (query != null) {
+ final Map<String, List<String>> queryParams = Arrays
+ .stream(query.split("&")) // generate a stream of key=value pairs
+ .map(keyValuePair -> keyValuePair.split("=")) // map key=value strings to arrays
+ .map(keyValuePair -> new AbstractMap.SimpleEntry<String, String>(keyValuePair[0],
+ keyValuePair[1])) // map arrays to Entry objects
+ .collect(Collectors
+ .groupingBy(AbstractMap.SimpleEntry::getKey)) // group entries with identical keys to a map of
+ // lists
+ .entrySet().stream() // stream the map again
+ .map(entry -> new AbstractMap.SimpleEntry<>(entry.getKey(),
+ entry.getValue().stream().map(AbstractMap.SimpleEntry::getValue).collect(Collectors
+ .toList()))) // map the lists of Entry objects to lists of Strings
+ .collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey,
+ AbstractMap.SimpleEntry::getValue)); // collect the stream to a map
+
+ log.trace("Query map: {}", queryParams);
+ return queryParams;
+
+ } else {
+ log.warn("Empty query string");
+ return Collections.emptyMap();
+
+ }
+ }
+
+}
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 79f82af8..2cc4e22e 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
@@ -13,6 +13,11 @@ internal.keystore.08=Can not access Key: {1} in KeyStore: {0}
internal.keystore.09=Can not access Key: {1} in KeyStore: {0} Reason: {2}
internal.keystore.10=HSM-Facade NOT INITIALIZED. Find HSM-Facade class: {0} put that looks WRONG.
internal.keystore.11=KeyStore: {0} has a wrong configuration. Property: {0} Reason:{1}
+internal.keystore.12=Software KeyStore initialization failed with a generic error: {0}
+internal.keystore.13=Can not build KeyStore from in-line configuration. Reason: {0}
+internal.keystore.14=KeyStore with in-line configuration: {0} is not a valid URL. Reason: {1}
+internal.keystore.15=KeyStore with in-line configuration has an unknown query-parameter path. Reason: {0}
+
internal.key.00=Can not generate passphrase based symmetric-key: {0} Reason: {1}
internal.key.01=Can not use key from Keystore: {0} Reason: {1}