From 1e5c2de3a4aafb476070478b27a18caf9efc051b Mon Sep 17 00:00:00 2001 From: Thomas <> Date: Mon, 8 May 2023 17:24:41 +0200 Subject: 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 --- .../credential/inline/InlineKeyStoreParser.java | 149 +++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/inline/InlineKeyStoreParser.java (limited to 'eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/inline/InlineKeyStoreParser.java') 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> 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 queryParams 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> queryParams, final String[] certificateFiles) + throws GeneralSecurityException, IOException, EaafConfigurationException { + final List privateKeyList = queryParams.get("private"); + final List 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> parseQuery(String query) { + if (query != null) { + final Map> 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(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(); + + } + } + +} -- cgit v1.2.3