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.nio.charset.StandardCharsets; 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), StandardCharsets.UTF_8); 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(); } }