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/InlineKeyStoreBuilder.java | 165 +++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/inline/InlineKeyStoreBuilder.java (limited to 'eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/inline/InlineKeyStoreBuilder.java') 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(); + + } +} -- cgit v1.2.3