From 6f83f2be63617af4e050d4c91cc258b9d7472d2e Mon Sep 17 00:00:00 2001 From: luk-kaminski Date: Tue, 30 May 2023 14:40:27 +0200 Subject: [PATCH] Refactoring and additional tests (#15640) * Refactoring and additional tests * One more try-with-resources --- .../configuration/ConfigurationProvider.java | 12 +- .../configuration/RootCertificateFinder.java | 19 +-- .../configuration/TruststoreCreator.java | 16 ++- .../configuration/TruststoreCreatorTest.java | 121 ++++++++++++++++++ 4 files changed, 146 insertions(+), 22 deletions(-) create mode 100644 data-node/src/test/java/org/graylog/datanode/configuration/TruststoreCreatorTest.java diff --git a/data-node/src/main/java/org/graylog/datanode/configuration/ConfigurationProvider.java b/data-node/src/main/java/org/graylog/datanode/configuration/ConfigurationProvider.java index d4729b98d0..4174ba5ca9 100644 --- a/data-node/src/main/java/org/graylog/datanode/configuration/ConfigurationProvider.java +++ b/data-node/src/main/java/org/graylog/datanode/configuration/ConfigurationProvider.java @@ -34,9 +34,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; +import java.security.GeneralSecurityException; import java.security.cert.X509Certificate; import java.util.Collections; import java.util.HashMap; @@ -61,7 +59,7 @@ public class ConfigurationProvider implements Provider TlsConfigurationSupplier tlsConfigurationSupplier, TruststoreCreator truststoreCreator, RootCertificateFinder rootCertificateFinder - ) throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException { + ) throws IOException, GeneralSecurityException { Map rootCerts = new HashMap<>(); final String truststorePassword = UUID.randomUUID().toString(); final var cfg = sharedConfiguration.test(); @@ -128,7 +126,7 @@ public class ConfigurationProvider implements Provider ); rootCerts.put("transport-chain-CA-root", rootCertificateFinder.findRootCert(transportKeystorePath, - localConfiguration.getDatanodeTransportCertificatePassword(), + localConfiguration.getDatanodeTransportCertificatePassword().toCharArray(), CertConstants.DATANODE_KEY_ALIAS)); tlsConfigurationSupplier.addTransportTlsConfig(config, @@ -156,7 +154,7 @@ public class ConfigurationProvider implements Provider "http_certificate_password has to be configured for the keystore " + httpKeystorePath ); rootCerts.put("http-chain-CA-root", rootCertificateFinder.findRootCert(httpKeystorePath, - localConfiguration.getDatanodeHttpCertificatePassword(), + localConfiguration.getDatanodeHttpCertificatePassword().toCharArray(), CertConstants.DATANODE_KEY_ALIAS)); tlsConfigurationSupplier.addHttpTlsConfig(config, @@ -171,7 +169,7 @@ public class ConfigurationProvider implements Provider if (!rootCerts.isEmpty()) { final Path trustStorePath = opensearchConfigDir.resolve(TRUSTSTORE_FILENAME); truststoreCreator.createTruststore(rootCerts, - truststorePassword, + truststorePassword.toCharArray(), trustStorePath ); System.setProperty("javax.net.ssl.trustStore", trustStorePath.toAbsolutePath().toString()); diff --git a/data-node/src/main/java/org/graylog/datanode/configuration/RootCertificateFinder.java b/data-node/src/main/java/org/graylog/datanode/configuration/RootCertificateFinder.java index cc58c6c52b..f30f5df246 100644 --- a/data-node/src/main/java/org/graylog/datanode/configuration/RootCertificateFinder.java +++ b/data-node/src/main/java/org/graylog/datanode/configuration/RootCertificateFinder.java @@ -16,22 +16,23 @@ */ package org.graylog.datanode.configuration; +import org.graylog.security.certutil.CertConstants; + import java.io.FileInputStream; import java.io.IOException; import java.nio.file.Path; +import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; import java.security.cert.Certificate; -import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Arrays; public class RootCertificateFinder { public X509Certificate findRootCert(Path keystorePath, - String password, - final String alias) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { + char[] password, + final String alias) throws IOException, GeneralSecurityException { final KeyStore keystore = loadKeystore(keystorePath, password); final Certificate[] certs = keystore.getCertificateChain(alias); @@ -47,10 +48,12 @@ public class RootCertificateFinder { return cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal()); } - private KeyStore loadKeystore(Path keystorePath, String password) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { - KeyStore nodeKeystore = KeyStore.getInstance("PKCS12"); - final FileInputStream is = new FileInputStream(keystorePath.toFile()); - nodeKeystore.load(is, password.toCharArray()); + private KeyStore loadKeystore(final Path keystorePath, + final char[] password) throws IOException, GeneralSecurityException { + KeyStore nodeKeystore = KeyStore.getInstance(CertConstants.PKCS12); + try (final FileInputStream is = new FileInputStream(keystorePath.toFile())) { + nodeKeystore.load(is, password); + } return nodeKeystore; } } diff --git a/data-node/src/main/java/org/graylog/datanode/configuration/TruststoreCreator.java b/data-node/src/main/java/org/graylog/datanode/configuration/TruststoreCreator.java index 545ec23558..393cb9bda1 100644 --- a/data-node/src/main/java/org/graylog/datanode/configuration/TruststoreCreator.java +++ b/data-node/src/main/java/org/graylog/datanode/configuration/TruststoreCreator.java @@ -16,29 +16,31 @@ */ package org.graylog.datanode.configuration; +import org.graylog.security.certutil.CertConstants; + import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Path; +import java.security.GeneralSecurityException; import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Map; public class TruststoreCreator { public void createTruststore(final Map rootCerts, - final String truststorePassword, - final Path truststorePath) throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException { - KeyStore trustStore = KeyStore.getInstance("PKCS12"); + final char[] truststorePassword, + final Path truststorePath) throws IOException, GeneralSecurityException { + KeyStore trustStore = KeyStore.getInstance(CertConstants.PKCS12); trustStore.load(null, null); for (Map.Entry cert : rootCerts.entrySet()) { trustStore.setCertificateEntry(cert.getKey(), cert.getValue()); } - trustStore.store(new FileOutputStream(truststorePath.toFile()), truststorePassword.toCharArray()); + try (final FileOutputStream fileOutputStream = new FileOutputStream(truststorePath.toFile())) { + trustStore.store(fileOutputStream, truststorePassword); + } } } diff --git a/data-node/src/test/java/org/graylog/datanode/configuration/TruststoreCreatorTest.java b/data-node/src/test/java/org/graylog/datanode/configuration/TruststoreCreatorTest.java new file mode 100644 index 0000000000..bd2083ef75 --- /dev/null +++ b/data-node/src/test/java/org/graylog/datanode/configuration/TruststoreCreatorTest.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2020 Graylog, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + */ +package org.graylog.datanode.configuration; + +import com.google.common.collect.ImmutableList; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.graylog.security.certutil.keystore.storage.KeystoreFileStorage; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.math.BigInteger; +import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Date; +import java.util.Map; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.graylog.security.certutil.CertConstants.KEY_GENERATION_ALGORITHM; +import static org.graylog.security.certutil.CertConstants.SIGNING_ALGORITHM; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +public class TruststoreCreatorTest { + + private TruststoreCreator toTest; + + @BeforeEach + void setUp() { + toTest = new TruststoreCreator(); + } + + @Test + void testTrustStoreCreation(@TempDir Path tempDir) throws Exception { + final Path trustStoreFile = tempDir.resolve("truststore.sec"); + final char[] truststorePassword = "caramba! caramba!".toCharArray(); + + toTest.createTruststore( + Map.of( + "root", prepareSimpleCert("CN=ROOT", BigInteger.ONE), + "boot", prepareSimpleCert("CN=BOOT", BigInteger.TWO) + ), + truststorePassword, + trustStoreFile); + + assertTrue(trustStoreFile.toFile().exists()); + + KeystoreFileStorage keystoreFileStorage = new KeystoreFileStorage(); + final Optional keyStoreOptional = keystoreFileStorage.readKeyStore(trustStoreFile, truststorePassword); + + assertTrue(keyStoreOptional.isPresent()); + + final KeyStore keyStore = keyStoreOptional.get(); + assertThat(ImmutableList.copyOf(keyStore.aliases().asIterator())) + .containsOnly("root", "boot"); + + final Certificate rootCert = keyStore.getCertificate("root"); + verifyCertificate(rootCert, "CN=ROOT", BigInteger.ONE); + final Certificate bootCert = keyStore.getCertificate("boot"); + verifyCertificate(bootCert, "CN=BOOT", BigInteger.TWO); + + } + + private void verifyCertificate(final Certificate rootCert, final String cnName, final BigInteger serialNumber) { + assertThat(rootCert) + .isNotNull() + .isInstanceOf(X509Certificate.class); + + final X509Certificate x509Certificate = (X509Certificate) rootCert; + assertEquals(serialNumber, x509Certificate.getSerialNumber()); + assertEquals(cnName, x509Certificate.getIssuerX500Principal().getName()); + } + + private X509Certificate prepareSimpleCert(final String cnName, final BigInteger serialNumber) throws GeneralSecurityException, OperatorCreationException { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance(KEY_GENERATION_ALGORITHM); + java.security.KeyPair certKeyPair = keyGen.generateKeyPair(); + X500Name name = new X500Name(cnName); + + JcaX509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder( + name, + serialNumber, + Date.from(Instant.now()), + Date.from(Instant.now().plus(13, ChronoUnit.DAYS)), + name, + certKeyPair.getPublic()); + + ContentSigner signer = new JcaContentSignerBuilder(SIGNING_ALGORITHM).build(certKeyPair.getPrivate()); + X509CertificateHolder certHolder = builder.build(signer); + return new JcaX509CertificateConverter().getCertificate(certHolder); + + } + +}