diff --git a/data-node/src/main/java/org/graylog/datanode/bindings/ServerBindings.java b/data-node/src/main/java/org/graylog/datanode/bindings/ServerBindings.java index 4bfe2c6fe0..4ca67d67c8 100644 --- a/data-node/src/main/java/org/graylog/datanode/bindings/ServerBindings.java +++ b/data-node/src/main/java/org/graylog/datanode/bindings/ServerBindings.java @@ -20,12 +20,12 @@ import com.google.common.eventbus.EventBus; import com.google.inject.TypeLiteral; import com.google.inject.multibindings.Multibinder; import com.google.inject.multibindings.OptionalBinder; +import jakarta.ws.rs.container.DynamicFeature; +import jakarta.ws.rs.ext.ExceptionMapper; import org.graylog.datanode.Configuration; import org.graylog.datanode.process.ProcessStateMachine; import org.graylog.datanode.process.ProcessStateMachineProvider; import org.graylog.datanode.shared.system.activities.DataNodeActivityWriter; -import org.graylog.security.certutil.keystore.storage.KeystoreContentMover; -import org.graylog.security.certutil.keystore.storage.SinglePasswordKeystoreContentMover; import org.graylog2.bindings.providers.ClusterEventBusProvider; import org.graylog2.cluster.ClusterConfigServiceImpl; import org.graylog2.cluster.nodes.DataNodeClusterService; @@ -40,9 +40,6 @@ import org.graylog2.plugin.inject.Graylog2Module; import org.graylog2.shared.bindings.providers.EventBusProvider; import org.graylog2.shared.system.activities.ActivityWriter; -import jakarta.ws.rs.container.DynamicFeature; -import jakarta.ws.rs.ext.ExceptionMapper; - public class ServerBindings extends Graylog2Module { private final Configuration configuration; private final boolean isMigrationCommand; @@ -80,7 +77,6 @@ public class ServerBindings extends Graylog2Module { private void bindSingletons() { bind(ClusterConfigService.class).to(ClusterConfigServiceImpl.class).asEagerSingleton(); - bind(KeystoreContentMover.class).to(SinglePasswordKeystoreContentMover.class).asEagerSingleton(); } private void bindInterfaces() { diff --git a/data-node/src/main/java/org/graylog/datanode/bootstrap/preflight/DataNodeConfigurationPeriodical.java b/data-node/src/main/java/org/graylog/datanode/bootstrap/preflight/DataNodeConfigurationPeriodical.java index 4b1923dd4d..31b21a776d 100644 --- a/data-node/src/main/java/org/graylog/datanode/bootstrap/preflight/DataNodeConfigurationPeriodical.java +++ b/data-node/src/main/java/org/graylog/datanode/bootstrap/preflight/DataNodeConfigurationPeriodical.java @@ -17,6 +17,9 @@ package org.graylog.datanode.bootstrap.preflight; import com.google.common.collect.ImmutableList; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import jakarta.inject.Singleton; import org.bouncycastle.operator.OperatorException; import org.graylog.datanode.configuration.DatanodeConfiguration; import org.graylog.security.certutil.CertConstants; @@ -27,7 +30,7 @@ import org.graylog.security.certutil.csr.CertificateAndPrivateKeyMerger; import org.graylog.security.certutil.csr.CsrGenerator; import org.graylog.security.certutil.csr.exceptions.CSRGenerationException; import org.graylog.security.certutil.csr.storage.CsrMongoStorage; -import org.graylog.security.certutil.keystore.storage.SmartKeystoreStorage; +import org.graylog.security.certutil.keystore.storage.KeystoreMongoStorage; import org.graylog.security.certutil.keystore.storage.location.KeystoreMongoCollections; import org.graylog.security.certutil.keystore.storage.location.KeystoreMongoLocation; import org.graylog.security.certutil.privatekey.PrivateKeyEncryptedFileStorage; @@ -42,10 +45,6 @@ import org.graylog2.shared.SuppressForbidden; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import jakarta.inject.Inject; -import jakarta.inject.Named; -import jakarta.inject.Singleton; - import java.io.IOException; import java.net.InetAddress; import java.nio.file.Path; @@ -68,9 +67,10 @@ public class DataNodeConfigurationPeriodical extends Periodical { private final CsrGenerator csrGenerator; private final CertChainStorage certMongoStorage; private final CertificateAndPrivateKeyMerger certificateAndPrivateKeyMerger; - private final SmartKeystoreStorage keystoreStorage; private final char[] passwordSecret; + private final KeystoreMongoStorage mongoKeyStorage; + @Inject public DataNodeConfigurationPeriodical(final DataNodeProvisioningService dataNodeProvisioningService, final NodeService nodeService, @@ -79,9 +79,8 @@ public class DataNodeConfigurationPeriodical extends Periodical { final CsrGenerator csrGenerator, final CertChainMongoStorage certMongoStorage, final CertificateAndPrivateKeyMerger certificateAndPrivateKeyMerger, - final SmartKeystoreStorage keystoreStorage, final @Named("password_secret") String passwordSecret, - final DatanodeConfiguration datanodeConfiguration) throws IOException { + final DatanodeConfiguration datanodeConfiguration, KeystoreMongoStorage mongoKeyStorage) throws IOException { this.dataNodeProvisioningService = dataNodeProvisioningService; this.nodeService = nodeService; this.nodeId = nodeId; @@ -89,10 +88,10 @@ public class DataNodeConfigurationPeriodical extends Periodical { this.csrGenerator = csrGenerator; this.certMongoStorage = certMongoStorage; this.certificateAndPrivateKeyMerger = certificateAndPrivateKeyMerger; - this.keystoreStorage = keystoreStorage; // TODO: merge with real storage this.privateKeyEncryptedStorage = new PrivateKeyEncryptedFileStorage(datanodeConfiguration.datanodeDirectories().createConfigurationFile(Path.of("privateKey.cert"))); this.passwordSecret = passwordSecret.toCharArray(); + this.mongoKeyStorage = mongoKeyStorage; } @Override @@ -133,8 +132,7 @@ public class DataNodeConfigurationPeriodical extends Periodical { CertConstants.DATANODE_KEY_ALIAS ); - final KeystoreMongoLocation location = new KeystoreMongoLocation(nodeId.getNodeId(), KeystoreMongoCollections.DATA_NODE_KEYSTORE_COLLECTION); - keystoreStorage.writeKeyStore(location, nodeKeystore, secret, secret); + mongoKeyStorage.writeKeyStore(KeystoreMongoLocation.datanode(nodeId), nodeKeystore, secret, secret); //should be in one transaction, but we miss transactions... dataNodeProvisioningService.changeState(nodeId.getNodeId(), DataNodeProvisioningConfig.State.STORED); diff --git a/data-node/src/main/java/org/graylog/datanode/configuration/certificates/KeystoreReEncryption.java b/data-node/src/main/java/org/graylog/datanode/configuration/certificates/KeystoreReEncryption.java deleted file mode 100644 index efaecc7782..0000000000 --- a/data-node/src/main/java/org/graylog/datanode/configuration/certificates/KeystoreReEncryption.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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.certificates; - -import org.apache.commons.lang3.RandomStringUtils; -import org.graylog.security.certutil.ca.exceptions.KeyStoreStorageException; -import org.graylog.security.certutil.keystore.storage.SmartKeystoreStorage; -import org.graylog.security.certutil.keystore.storage.location.KeystoreLocation; - -import jakarta.inject.Inject; -import jakarta.inject.Named; - -import java.security.KeyStore; -import java.util.Optional; - -public class KeystoreReEncryption { - - private final SmartKeystoreStorage keystoreStorage; - private final String passwordSecret; - - @Inject - public KeystoreReEncryption(final SmartKeystoreStorage keystoreStorage, - final @Named("password_secret") String passwordSecret) { - this.keystoreStorage = keystoreStorage; - this.passwordSecret = passwordSecret; - } - - public void reEncyptWithSecret(final KeystoreLocation originalLocation, - final char[] originalPassword, - final KeystoreLocation targetLocation - ) throws KeyStoreStorageException { - reEncypt(originalLocation, originalPassword, targetLocation, passwordSecret.toCharArray()); - } - - public char[] reEncyptWithOtp(final KeystoreLocation originalLocation, - final char[] originalPassword, - final KeystoreLocation targetLocation - ) throws KeyStoreStorageException { - final char[] oneTimePassword = RandomStringUtils.randomAlphabetic(256).toCharArray(); - reEncypt(originalLocation, originalPassword, targetLocation, oneTimePassword); - return oneTimePassword; - } - - private void reEncypt(final KeystoreLocation originalLocation, - final char[] originalPassword, - final KeystoreLocation targetLocation, - final char[] newPassword - ) throws KeyStoreStorageException { - - final Optional keyStore = keystoreStorage.readKeyStore(originalLocation, originalPassword); - if (keyStore.isPresent()) { - final KeyStore originalKeystore = keyStore.get(); - keystoreStorage.writeKeyStore(targetLocation, originalKeystore, originalPassword, newPassword); - } else { - throw new KeyStoreStorageException("No keystore present in : " + originalLocation); - } - } -} diff --git a/data-node/src/main/java/org/graylog/datanode/configuration/variants/MongoCertSecureConfiguration.java b/data-node/src/main/java/org/graylog/datanode/configuration/variants/MongoCertSecureConfiguration.java index 25ad310f58..5faffd52fe 100644 --- a/data-node/src/main/java/org/graylog/datanode/configuration/variants/MongoCertSecureConfiguration.java +++ b/data-node/src/main/java/org/graylog/datanode/configuration/variants/MongoCertSecureConfiguration.java @@ -16,46 +16,49 @@ */ package org.graylog.datanode.configuration.variants; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import jakarta.validation.constraints.NotNull; import org.graylog.datanode.Configuration; import org.graylog.datanode.configuration.DatanodeConfiguration; -import org.graylog.datanode.configuration.certificates.KeystoreReEncryption; import org.graylog.security.certutil.ca.exceptions.KeyStoreStorageException; -import org.graylog.security.certutil.keystore.storage.location.KeystoreFileLocation; -import org.graylog.security.certutil.keystore.storage.location.KeystoreMongoCollections; +import org.graylog.security.certutil.keystore.storage.KeystoreFileStorage; +import org.graylog.security.certutil.keystore.storage.KeystoreMongoStorage; import org.graylog.security.certutil.keystore.storage.location.KeystoreMongoLocation; import org.graylog2.cluster.certificates.CertificatesService; import org.graylog2.plugin.system.NodeId; -import jakarta.inject.Inject; -import jakarta.inject.Named; - import java.io.IOException; +import java.nio.file.Path; import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.util.Optional; public final class MongoCertSecureConfiguration extends SecureConfiguration { - private final KeystoreReEncryption keystoreReEncryption; + private final NodeId nodeId; private final CertificatesService certificatesService; private final char[] secret; - private final KeystoreMongoLocation mongoLocation; private final char[] mongoKeystorePassword; + private final KeystoreMongoStorage mongoKeyStorage; + private final KeystoreFileStorage fileKeyStorage; + @Inject - public MongoCertSecureConfiguration(final Configuration localConfiguration, - final DatanodeConfiguration datanodeConfiguration, - final KeystoreReEncryption keystoreReEncryption, + public MongoCertSecureConfiguration(final DatanodeConfiguration datanodeConfiguration, final NodeId nodeId, final @Named("password_secret") String passwordSecret, - final CertificatesService certificatesService + final CertificatesService certificatesService, KeystoreMongoStorage mongoKeyStore, KeystoreFileStorage fileKeyStore ) { super(datanodeConfiguration); - this.keystoreReEncryption = keystoreReEncryption; + this.nodeId = nodeId; this.certificatesService = certificatesService; - this.mongoLocation = new KeystoreMongoLocation(nodeId.getNodeId(), KeystoreMongoCollections.DATA_NODE_KEYSTORE_COLLECTION); this.secret = passwordSecret.toCharArray(); + this.mongoKeyStorage = mongoKeyStore; + this.fileKeyStorage = fileKeyStore; //TODO: matches line 123 of DataNodePreflightGeneratePeriodical, but both need to be changed this.mongoKeystorePassword = secret; @@ -63,22 +66,44 @@ public final class MongoCertSecureConfiguration extends SecureConfiguration { @Override public boolean isConfigured(Configuration localConfiguration) { - return certificatesService.hasCert(mongoLocation); + return certificatesService.hasCert(getMongoKeystoreLocation()); } @Override public OpensearchSecurityConfiguration build() throws KeyStoreStorageException, IOException, GeneralSecurityException { - final KeystoreFileLocation targetTransportKeystoreLocation = getTransportKeystoreLocation(); - final KeystoreFileLocation targetHttpKeystoreLocation = getHttpKeystoreLocation(); + final Path targetTransportKeystoreLocation = getTransportKeystoreLocation(); + final Path targetHttpKeystoreLocation = getHttpKeystoreLocation(); + // this will take the mongodb-stored keys and persist them on a disk, in the opensearch configuration directory - keystoreReEncryption.reEncyptWithSecret(mongoLocation, mongoKeystorePassword, targetTransportKeystoreLocation); - keystoreReEncryption.reEncyptWithSecret(mongoLocation, mongoKeystorePassword, targetHttpKeystoreLocation); + final KeystoreMongoLocation mongoKeystoreLocation = getMongoKeystoreLocation(); + reEncypt(mongoKeystoreLocation, mongoKeystorePassword, targetTransportKeystoreLocation, secret); + reEncypt(mongoKeystoreLocation, mongoKeystorePassword, targetHttpKeystoreLocation, secret); return new OpensearchSecurityConfiguration( - new KeystoreInformation(targetTransportKeystoreLocation.keystorePath().toAbsolutePath(), secret), - new KeystoreInformation(targetHttpKeystoreLocation.keystorePath().toAbsolutePath(), secret) + new KeystoreInformation(targetTransportKeystoreLocation.toAbsolutePath(), secret), + new KeystoreInformation(targetHttpKeystoreLocation.toAbsolutePath(), secret) ); } + + @NotNull + private KeystoreMongoLocation getMongoKeystoreLocation() { + return KeystoreMongoLocation.datanode(nodeId); + } + + private void reEncypt(final KeystoreMongoLocation originalLocation, + final char[] originalPassword, + final Path targetLocation, + final char[] newPassword + ) throws KeyStoreStorageException { + + final Optional keyStore = mongoKeyStorage.readKeyStore(originalLocation, originalPassword); + if (keyStore.isPresent()) { + final KeyStore originalKeystore = keyStore.get(); + fileKeyStorage.writeKeyStore(targetLocation, originalKeystore, originalPassword, newPassword); + } else { + throw new KeyStoreStorageException("No keystore present in : " + originalLocation); + } + } } diff --git a/data-node/src/main/java/org/graylog/datanode/configuration/variants/SecureConfiguration.java b/data-node/src/main/java/org/graylog/datanode/configuration/variants/SecureConfiguration.java index ec346b7fc6..4c8e6b9ed5 100644 --- a/data-node/src/main/java/org/graylog/datanode/configuration/variants/SecureConfiguration.java +++ b/data-node/src/main/java/org/graylog/datanode/configuration/variants/SecureConfiguration.java @@ -18,7 +18,6 @@ package org.graylog.datanode.configuration.variants; import com.google.common.base.Suppliers; import org.graylog.datanode.configuration.DatanodeConfiguration; -import org.graylog.security.certutil.keystore.storage.location.KeystoreFileLocation; import java.io.IOException; import java.nio.file.Path; @@ -41,14 +40,13 @@ public sealed abstract class SecureConfiguration implements SecurityConfiguratio */ private static final Path TARGET_DATANODE_TRANSPORT_KEYSTORE_FILENAME = Path.of("transport-keystore.p12"); - private final Supplier httpKeystoreLocation; - private final Supplier transportKeystoreLocation; + private final Supplier httpKeystoreLocation; + private final Supplier transportKeystoreLocation; public SecureConfiguration(final DatanodeConfiguration datanodeConfiguration) { this.httpKeystoreLocation = Suppliers.memoize(() -> { try { - final Path filePath = datanodeConfiguration.datanodeDirectories().createOpensearchProcessConfigurationFile(TARGET_DATANODE_HTTP_KEYSTORE_FILENAME); - return new KeystoreFileLocation(filePath); + return datanodeConfiguration.datanodeDirectories().createOpensearchProcessConfigurationFile(TARGET_DATANODE_HTTP_KEYSTORE_FILENAME); } catch (IOException e) { throw new RuntimeException("Failed to create http keystore file", e); } @@ -56,20 +54,19 @@ public sealed abstract class SecureConfiguration implements SecurityConfiguratio this.transportKeystoreLocation = Suppliers.memoize(() -> { try { - final Path filePath = datanodeConfiguration.datanodeDirectories().createOpensearchProcessConfigurationFile(TARGET_DATANODE_TRANSPORT_KEYSTORE_FILENAME); - return new KeystoreFileLocation(filePath); + return datanodeConfiguration.datanodeDirectories().createOpensearchProcessConfigurationFile(TARGET_DATANODE_TRANSPORT_KEYSTORE_FILENAME); } catch (IOException e) { throw new RuntimeException("Failed to create transport keystore file", e); } }); } - KeystoreFileLocation getHttpKeystoreLocation() { + Path getHttpKeystoreLocation() { return httpKeystoreLocation.get(); } - KeystoreFileLocation getTransportKeystoreLocation() { + Path getTransportKeystoreLocation() { return transportKeystoreLocation.get(); } } diff --git a/data-node/src/main/java/org/graylog/datanode/configuration/variants/UploadedCertFilesSecureConfiguration.java b/data-node/src/main/java/org/graylog/datanode/configuration/variants/UploadedCertFilesSecureConfiguration.java index 3d8dc9b767..5f69575f2e 100644 --- a/data-node/src/main/java/org/graylog/datanode/configuration/variants/UploadedCertFilesSecureConfiguration.java +++ b/data-node/src/main/java/org/graylog/datanode/configuration/variants/UploadedCertFilesSecureConfiguration.java @@ -16,18 +16,19 @@ */ package org.graylog.datanode.configuration.variants; +import jakarta.inject.Inject; +import org.apache.commons.lang3.RandomStringUtils; import org.graylog.datanode.Configuration; import org.graylog.datanode.configuration.DatanodeConfiguration; import org.graylog.datanode.configuration.OpensearchConfigurationException; -import org.graylog.datanode.configuration.certificates.KeystoreReEncryption; import org.graylog.security.certutil.ca.exceptions.KeyStoreStorageException; -import org.graylog.security.certutil.keystore.storage.location.KeystoreFileLocation; - -import jakarta.inject.Inject; +import org.graylog.security.certutil.keystore.storage.KeystoreFileStorage; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; import java.security.GeneralSecurityException; +import java.security.KeyStore; import java.util.LinkedList; import java.util.List; import java.util.Optional; @@ -41,16 +42,16 @@ public final class UploadedCertFilesSecureConfiguration extends SecureConfigurat private final String uploadedHttpKeystoreFileName; private final String datanodeTransportCertificatePassword; private final String datanodeHttpCertificatePassword; - private final KeystoreReEncryption keystoreReEncryption; + private final KeystoreFileStorage keystoreFileStorage; private final DatanodeConfiguration datanodeConfiguration; @Inject public UploadedCertFilesSecureConfiguration(final Configuration localConfiguration, final DatanodeConfiguration datanodeConfiguration, - final KeystoreReEncryption keystoreReEncryption) { + KeystoreFileStorage keystoreFileStorage) { super(datanodeConfiguration); this.datanodeConfiguration = datanodeConfiguration; - this.keystoreReEncryption = keystoreReEncryption; + this.keystoreFileStorage = keystoreFileStorage; this.uploadedTransportKeystoreFileName = localConfiguration.getDatanodeTransportCertificate(); this.uploadedHttpKeystoreFileName = localConfiguration.getDatanodeHttpCertificate(); @@ -116,20 +117,45 @@ public final class UploadedCertFilesSecureConfiguration extends SecureConfigurat @Override public OpensearchSecurityConfiguration build() throws KeyStoreStorageException, IOException, GeneralSecurityException { - final KeystoreFileLocation targetTransportKeystoreLocation = getTransportKeystoreLocation(); - final KeystoreFileLocation targetHttpKeystoreLocation = getHttpKeystoreLocation(); + final Path targetTransportKeystoreLocation = getTransportKeystoreLocation(); + final Path targetHttpKeystoreLocation = getHttpKeystoreLocation(); - final char[] transportOTP = keystoreReEncryption.reEncyptWithOtp(new KeystoreFileLocation(datanodeConfiguration.datanodeDirectories().resolveConfigurationSourceFile(uploadedTransportKeystoreFileName).orElseThrow(() -> new RuntimeException("This should not happen, certificate expected"))), + final char[] transportOTP = reEncyptWithOtp(datanodeConfiguration.datanodeDirectories().resolveConfigurationSourceFile(uploadedTransportKeystoreFileName).orElseThrow(() -> new RuntimeException("This should not happen, certificate expected")), datanodeTransportCertificatePassword.toCharArray(), targetTransportKeystoreLocation); - final char[] httpOTP = keystoreReEncryption.reEncyptWithOtp(new KeystoreFileLocation(datanodeConfiguration.datanodeDirectories().resolveConfigurationSourceFile(uploadedHttpKeystoreFileName).orElseThrow(() -> new RuntimeException("This should not happen, certificate expected"))), + final char[] httpOTP = reEncyptWithOtp(datanodeConfiguration.datanodeDirectories().resolveConfigurationSourceFile(uploadedHttpKeystoreFileName).orElseThrow(() -> new RuntimeException("This should not happen, certificate expected")), datanodeHttpCertificatePassword.toCharArray(), targetHttpKeystoreLocation); return new OpensearchSecurityConfiguration( - new KeystoreInformation(targetTransportKeystoreLocation.keystorePath().toAbsolutePath(), transportOTP), - new KeystoreInformation(targetHttpKeystoreLocation.keystorePath().toAbsolutePath(), httpOTP) + new KeystoreInformation(targetTransportKeystoreLocation.toAbsolutePath(), transportOTP), + new KeystoreInformation(targetHttpKeystoreLocation.toAbsolutePath(), httpOTP) ); } + + public char[] reEncyptWithOtp(final Path originalLocation, + final char[] originalPassword, + final Path targetLocation + ) throws KeyStoreStorageException { + // caution! This password changes during each configuration request!!! + final char[] oneTimePassword = RandomStringUtils.randomAlphabetic(256).toCharArray(); + reEncypt(originalLocation, originalPassword, targetLocation, oneTimePassword); + return oneTimePassword; + } + + private void reEncypt(final Path originalLocation, + final char[] originalPassword, + final Path targetLocation, + final char[] newPassword + ) throws KeyStoreStorageException { + + final Optional keyStore = keystoreFileStorage.readKeyStore(originalLocation, originalPassword); + if (keyStore.isPresent()) { + final KeyStore originalKeystore = keyStore.get(); + keystoreFileStorage.writeKeyStore(targetLocation, originalKeystore, originalPassword, newPassword); + } else { + throw new KeyStoreStorageException("No keystore present in : " + originalLocation); + } + } } 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 index 3c3acd67b3..df9d120c46 100644 --- a/data-node/src/test/java/org/graylog/datanode/configuration/TruststoreCreatorTest.java +++ b/data-node/src/test/java/org/graylog/datanode/configuration/TruststoreCreatorTest.java @@ -27,9 +27,7 @@ import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.graylog.datanode.configuration.variants.KeystoreInformation; import org.graylog.security.certutil.CertConstants; -import org.graylog.security.certutil.keystore.storage.KeystoreContentMover; import org.graylog.security.certutil.keystore.storage.KeystoreFileStorage; -import org.graylog.security.certutil.keystore.storage.location.KeystoreFileLocation; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -52,7 +50,6 @@ import static org.graylog.security.certutil.CertConstants.KEY_GENERATION_ALGORIT 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; -import static org.mockito.Mockito.mock; public class TruststoreCreatorTest { @@ -69,8 +66,8 @@ public class TruststoreCreatorTest { assertTrue(truststore.location().toFile().exists()); - KeystoreFileStorage keystoreFileStorage = new KeystoreFileStorage(mock(KeystoreContentMover.class)); - final Optional keyStoreOptional = keystoreFileStorage.readKeyStore(new KeystoreFileLocation(truststore.location()), truststore.password()); + KeystoreFileStorage keystoreFileStorage = new KeystoreFileStorage(); + final Optional keyStoreOptional = keystoreFileStorage.readKeyStore(truststore.location(), truststore.password()); assertTrue(keyStoreOptional.isPresent()); diff --git a/graylog2-server/src/main/java/org/graylog/security/certutil/CaServiceImpl.java b/graylog2-server/src/main/java/org/graylog/security/certutil/CaServiceImpl.java index 148eb4f8bd..b8aae9a7fe 100644 --- a/graylog2-server/src/main/java/org/graylog/security/certutil/CaServiceImpl.java +++ b/graylog2-server/src/main/java/org/graylog/security/certutil/CaServiceImpl.java @@ -16,14 +16,17 @@ */ package org.graylog.security.certutil; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import jakarta.inject.Singleton; import org.glassfish.jersey.media.multipart.BodyPart; import org.glassfish.jersey.media.multipart.FormDataBodyPart; import org.graylog.security.certutil.ca.CAKeyPair; import org.graylog.security.certutil.ca.PemCaReader; import org.graylog.security.certutil.ca.exceptions.CACreationException; import org.graylog.security.certutil.ca.exceptions.KeyStoreStorageException; -import org.graylog.security.certutil.keystore.storage.SmartKeystoreStorage; -import org.graylog.security.certutil.keystore.storage.location.KeystoreFileLocation; +import org.graylog.security.certutil.keystore.storage.KeystoreFileStorage; +import org.graylog.security.certutil.keystore.storage.KeystoreMongoStorage; import org.graylog.security.certutil.keystore.storage.location.KeystoreMongoCollections; import org.graylog.security.certutil.keystore.storage.location.KeystoreMongoLocation; import org.graylog2.Configuration; @@ -31,15 +34,11 @@ import org.graylog2.bootstrap.preflight.web.resources.model.CA; import org.graylog2.bootstrap.preflight.web.resources.model.CAType; import org.graylog2.cluster.certificates.CertificatesService; import org.graylog2.events.ClusterEventBus; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nullable; - -import jakarta.inject.Inject; -import jakarta.inject.Named; -import jakarta.inject.Singleton; - import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -61,10 +60,11 @@ import static org.graylog.security.certutil.CertConstants.PKCS12; public class CaServiceImpl implements CaService { private static final Logger LOG = LoggerFactory.getLogger(CaServiceImpl.class); - public final String KEYSTORE_ID = "GRAYLOG CA"; - private final SmartKeystoreStorage keystoreStorage; - private final KeystoreMongoLocation mongoDbCaLocation; - private final KeystoreFileLocation manuallyProvidedCALocation; + + + private final KeystoreMongoStorage mongoKeystore; + private final KeystoreFileStorage fileKeystore; + private final PemCaReader pemCaReader; private final CaConfiguration configuration; private final CertificatesService certificatesService; @@ -73,18 +73,16 @@ public class CaServiceImpl implements CaService { private final ClusterEventBus eventBus; @Inject - public CaServiceImpl(final Configuration configuration, - final SmartKeystoreStorage keystoreStorage, + public CaServiceImpl(KeystoreMongoStorage keystoreMongoStorage, KeystoreFileStorage keystoreFileStorage, final Configuration configuration, final PemCaReader pemCaReader, final CertificatesService certificatesService, final @Named("password_secret") String passwordSecret, ClusterEventBus eventBus) { - this.keystoreStorage = keystoreStorage; + this.mongoKeystore = keystoreMongoStorage; + this.fileKeystore = keystoreFileStorage; this.pemCaReader = pemCaReader; this.configuration = configuration; this.certificatesService = certificatesService; this.passwordSecret = configuration.getCaPassword() != null ? configuration.getCaPassword() : passwordSecret; - this.mongoDbCaLocation = new KeystoreMongoLocation(KEYSTORE_ID, KeystoreMongoCollections.GRAYLOG_CA_KEYSTORE_COLLECTION); - this.manuallyProvidedCALocation = new KeystoreFileLocation(configuration.getCaKeystoreFile()); this.eventBus = eventBus; } @@ -93,8 +91,8 @@ public class CaServiceImpl implements CaService { if (configuration.configuredCaExists()) { return new CA("local CA", CAType.LOCAL); } else { - var keystore = keystoreStorage.readKeyStore(mongoDbCaLocation, passwordSecret.toCharArray()); - return keystore.map(c -> new CA(KEYSTORE_ID, CAType.GENERATED)).orElse(null); + var keystore = mongoKeystore.readKeyStore(KeystoreMongoLocation.certificateAuthority(), passwordSecret.toCharArray()); + return keystore.map(c -> new CA(KeystoreMongoLocation.CA_KEYSTORE_ID, CAType.GENERATED)).orElse(null); } } @@ -102,7 +100,7 @@ public class CaServiceImpl implements CaService { public CA create(final String organization, final Integer daysValid, char[] password) throws CACreationException, KeyStoreStorageException, KeyStoreException { final Duration certificateValidity = Duration.ofDays(daysValid == null || daysValid == 0 ? DEFAULT_VALIDITY : daysValid); KeyStore keyStore = CAKeyPair.create(organization, passwordSecret.toCharArray(), certificateValidity).toKeyStore(); - keystoreStorage.writeKeyStore(mongoDbCaLocation, keyStore, passwordSecret.toCharArray(), password); + mongoKeystore.writeKeyStore(KeystoreMongoLocation.certificateAuthority(), keyStore, passwordSecret.toCharArray(), password); LOG.debug("Generated a new CA."); triggerCaChangedEvent(); return get(); @@ -129,7 +127,7 @@ public class CaServiceImpl implements CaService { keyStore.load(bais, passwordCharArray); } } - keystoreStorage.writeKeyStore(mongoDbCaLocation, keyStore, passwordCharArray, passwordSecret.toCharArray()); + mongoKeystore.writeKeyStore(KeystoreMongoLocation.certificateAuthority(), keyStore, passwordCharArray, passwordSecret.toCharArray()); triggerCaChangedEvent(); } catch (IOException | KeyStoreStorageException | NoSuchAlgorithmException | CertificateException | KeyStoreException | NoSuchProviderException ex) { @@ -140,9 +138,10 @@ public class CaServiceImpl implements CaService { @Override public void startOver() { - certificatesService.removeCert(mongoDbCaLocation); + certificatesService.removeCert(KeystoreMongoLocation.certificateAuthority()); } + private void triggerCaChangedEvent() { eventBus.post(new CertificateAuthorityChangedEvent()); } @@ -150,9 +149,9 @@ public class CaServiceImpl implements CaService { @Override public Optional loadKeyStore() throws KeyStoreStorageException { if (configuration.configuredCaExists()) { - return keystoreStorage.readKeyStore(manuallyProvidedCALocation, configuration.getCaPassword().toCharArray()); + return fileKeystore.readKeyStore(configuration.getCaKeystoreFile(), configuration.getCaPassword().toCharArray()); } else { - return keystoreStorage.readKeyStore(mongoDbCaLocation, passwordSecret.toCharArray()); + return mongoKeystore.readKeyStore(KeystoreMongoLocation.certificateAuthority(), passwordSecret.toCharArray()); } } } diff --git a/graylog2-server/src/main/java/org/graylog/security/certutil/CertRenewalServiceImpl.java b/graylog2-server/src/main/java/org/graylog/security/certutil/CertRenewalServiceImpl.java index da7ab71ac1..897c22f3ed 100644 --- a/graylog2-server/src/main/java/org/graylog/security/certutil/CertRenewalServiceImpl.java +++ b/graylog2-server/src/main/java/org/graylog/security/certutil/CertRenewalServiceImpl.java @@ -167,7 +167,7 @@ public class CertRenewalServiceImpl implements CertRenewalService { private Optional loadKeyStoreForNode(Node node) { try { - return keystoreMongoStorage.readKeyStore(new KeystoreMongoLocation(node.getNodeId(), KeystoreMongoCollections.DATA_NODE_KEYSTORE_COLLECTION), passwordSecret); + return keystoreMongoStorage.readKeyStore(KeystoreMongoLocation.datanode(node.getNodeId()), passwordSecret); } catch (KeyStoreStorageException e) { return Optional.empty(); } diff --git a/graylog2-server/src/main/java/org/graylog/security/certutil/CertutilCa.java b/graylog2-server/src/main/java/org/graylog/security/certutil/CertutilCa.java index 9d44c0910a..440c3ceef7 100644 --- a/graylog2-server/src/main/java/org/graylog/security/certutil/CertutilCa.java +++ b/graylog2-server/src/main/java/org/graylog/security/certutil/CertutilCa.java @@ -22,8 +22,6 @@ import org.graylog.security.certutil.ca.CAKeyPair; import org.graylog.security.certutil.console.CommandLineConsole; import org.graylog.security.certutil.console.SystemConsole; import org.graylog.security.certutil.keystore.storage.KeystoreFileStorage; -import org.graylog.security.certutil.keystore.storage.SinglePasswordKeystoreContentMover; -import org.graylog.security.certutil.keystore.storage.location.KeystoreFileLocation; import org.graylog2.bootstrap.CliCommand; import java.nio.file.Path; @@ -42,13 +40,13 @@ public class CertutilCa implements CliCommand { public CertutilCa() { this.console = new SystemConsole(); - this.caKeystoreStorage = new KeystoreFileStorage(new SinglePasswordKeystoreContentMover()); + this.caKeystoreStorage = new KeystoreFileStorage(); } public CertutilCa(String keystoreFilename, CommandLineConsole console) { this.keystoreFilename = keystoreFilename; this.console = console; - this.caKeystoreStorage = new KeystoreFileStorage(new SinglePasswordKeystoreContentMover()); + this.caKeystoreStorage = new KeystoreFileStorage(); } @Override @@ -64,7 +62,7 @@ public class CertutilCa implements CliCommand { console.printLine("Private keys and certificates for root and intermediate CA generated"); final Path keystorePath = Path.of(keystoreFilename); - caKeystoreStorage.writeKeyStore(new KeystoreFileLocation(keystorePath), caKeystore, password, null); + caKeystoreStorage.writeKeyStore(keystorePath, caKeystore, password, null); console.printLine("Keys and certificates stored in " + keystorePath.toAbsolutePath()); } catch (Exception e) { throw new RuntimeException("Failed to generate CA certificate", e); diff --git a/graylog2-server/src/main/java/org/graylog/security/certutil/csr/CertificateAndPrivateKeyMerger.java b/graylog2-server/src/main/java/org/graylog/security/certutil/csr/CertificateAndPrivateKeyMerger.java index 8cb292eabc..e8d68397a4 100644 --- a/graylog2-server/src/main/java/org/graylog/security/certutil/csr/CertificateAndPrivateKeyMerger.java +++ b/graylog2-server/src/main/java/org/graylog/security/certutil/csr/CertificateAndPrivateKeyMerger.java @@ -21,10 +21,9 @@ import org.bouncycastle.pkcs.PKCSException; import org.graylog.security.certutil.CertConstants; import org.graylog.security.certutil.ca.exceptions.KeyStoreStorageException; import org.graylog.security.certutil.cert.CertificateChain; +import org.graylog.security.certutil.keystore.storage.KeystoreUtils; import org.graylog.security.certutil.privatekey.PrivateKeyEncryptedStorage; -import jakarta.inject.Inject; - import java.io.IOException; import java.security.GeneralSecurityException; import java.security.KeyStore; @@ -32,17 +31,9 @@ import java.security.PrivateKey; /** * Merges signed certificate, received after CSR was processed, with private key, that was awaiting in a safe file. - * Saves the result of the merge into Mongo collection. */ public class CertificateAndPrivateKeyMerger { - private final KeyPairChecker keyPairChecker; - - @Inject - public CertificateAndPrivateKeyMerger(final KeyPairChecker keyPairChecker) { - this.keyPairChecker = keyPairChecker; - } - public KeyStore merge(final CertificateChain certificateChain, final PrivateKeyEncryptedStorage privateKeyEncryptedStorage, final char[] privateKeyStoragePassword, @@ -54,7 +45,7 @@ public class CertificateAndPrivateKeyMerger { nodeKeystore.load(null, null); final PrivateKey privateKey = privateKeyEncryptedStorage.readEncryptedKey(privateKeyStoragePassword); - if (!keyPairChecker.matchingKeys(privateKey, certificateChain.signedCertificate().getPublicKey())) { + if (!KeystoreUtils.matchingKeys(privateKey, certificateChain.signedCertificate().getPublicKey())) { throw new GeneralSecurityException("Private key from CSR and public key from certificate do not form a valid pair"); } nodeKeystore.setKeyEntry(alias, privateKey, certFilePassword, certificateChain.toCertificateChainArray()); diff --git a/graylog2-server/src/main/java/org/graylog/security/certutil/csr/KeyPairChecker.java b/graylog2-server/src/main/java/org/graylog/security/certutil/csr/KeyPairChecker.java deleted file mode 100644 index 9124af4f47..0000000000 --- a/graylog2-server/src/main/java/org/graylog/security/certutil/csr/KeyPairChecker.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.security.certutil.csr; - -import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.Signature; - -import static org.graylog.security.certutil.CertConstants.SIGNING_ALGORITHM; - -public class KeyPairChecker { - - private static final String SAMPLE_CHALLANGE = "Grayloggers! Grayloggers! Grayloggers! Grayloggers!"; - - public boolean matchingKeys(final PrivateKey privateKey, - final PublicKey publicKey) throws GeneralSecurityException { - Signature sign = Signature.getInstance(SIGNING_ALGORITHM); - - byte[] bytes = SAMPLE_CHALLANGE.getBytes(StandardCharsets.UTF_8); - - sign.initSign(privateKey); - sign.update(bytes); - byte[] signature = sign.sign(); - - sign.initVerify(publicKey); - sign.update(bytes); - return sign.verify(signature); - } - -} diff --git a/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/KeystoreContentMover.java b/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/KeystoreContentMover.java deleted file mode 100644 index a16124e0bb..0000000000 --- a/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/KeystoreContentMover.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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.security.certutil.keystore.storage; - -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.security.KeyStore; - -public interface KeystoreContentMover { - - KeyStore moveContents(final KeyStore originalKeyStore, - char[] currentPassword, - final char[] newPassword) throws GeneralSecurityException, IOException; -} diff --git a/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/KeystoreFileStorage.java b/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/KeystoreFileStorage.java index 0bfaf2b391..3a51a35594 100644 --- a/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/KeystoreFileStorage.java +++ b/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/KeystoreFileStorage.java @@ -18,9 +18,6 @@ package org.graylog.security.certutil.keystore.storage; import org.graylog.security.certutil.CertConstants; import org.graylog.security.certutil.ca.exceptions.KeyStoreStorageException; -import org.graylog.security.certutil.keystore.storage.location.KeystoreFileLocation; - -import jakarta.inject.Inject; import java.io.FileOutputStream; import java.io.IOException; @@ -30,36 +27,29 @@ import java.security.GeneralSecurityException; import java.security.KeyStore; import java.util.Optional; -public final class KeystoreFileStorage implements KeystoreStorage { +public final class KeystoreFileStorage implements KeystoreStorage { - private final KeystoreContentMover keystoreContentMover; - - @Inject - public KeystoreFileStorage(final KeystoreContentMover keystoreContentMover) { - this.keystoreContentMover = keystoreContentMover; - } @Override - public void writeKeyStore(final KeystoreFileLocation location, + public void writeKeyStore(Path location, final KeyStore keyStore, final char[] currentPassword, final char[] newPassword) throws KeyStoreStorageException { - final Path keystorePath = location.keystorePath(); - try (FileOutputStream store = new FileOutputStream(keystorePath.toFile())) { + try (FileOutputStream store = new FileOutputStream(location.toFile())) { if (newPassword == null) { keyStore.store(store, currentPassword); } else { - KeyStore newKeyStore = keystoreContentMover.moveContents(keyStore, currentPassword, newPassword); + KeyStore newKeyStore = KeystoreUtils.newStoreCopyContent(keyStore, currentPassword, newPassword); newKeyStore.store(store, newPassword); } } catch (Exception ex) { - throw new KeyStoreStorageException("Failed to save keystore to " + keystorePath, ex); + throw new KeyStoreStorageException("Failed to save keystore to " + location, ex); } } @Override - public Optional readKeyStore(final KeystoreFileLocation location, char[] password) throws KeyStoreStorageException { - try (var in = Files.newInputStream(location.keystorePath())) { + public Optional readKeyStore(final Path location, char[] password) throws KeyStoreStorageException { + try (var in = Files.newInputStream(location)) { KeyStore caKeystore = KeyStore.getInstance(CertConstants.PKCS12); caKeystore.load(in, password); return Optional.of(caKeystore); diff --git a/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/KeystoreMongoStorage.java b/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/KeystoreMongoStorage.java index de7bd45b68..9738f655bb 100644 --- a/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/KeystoreMongoStorage.java +++ b/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/KeystoreMongoStorage.java @@ -33,13 +33,10 @@ import static org.graylog.security.certutil.CertConstants.PKCS12; public final class KeystoreMongoStorage implements KeystoreStorage { private final CertificatesService certificatesService; - private final KeystoreContentMover keystoreContentMover; @Inject - public KeystoreMongoStorage(final CertificatesService certificatesService, - final KeystoreContentMover keystoreContentMover) { + public KeystoreMongoStorage(final CertificatesService certificatesService) { this.certificatesService = certificatesService; - this.keystoreContentMover = keystoreContentMover; } @Override @@ -51,7 +48,7 @@ public final class KeystoreMongoStorage implements KeystoreStorage permits KeystoreFileStorage, KeystoreMongoStorage, SmartKeystoreStorage { +public interface KeystoreStorage { void writeKeyStore(final T location, final KeyStore keyStore, diff --git a/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/SinglePasswordKeystoreContentMover.java b/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/KeystoreUtils.java similarity index 66% rename from graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/SinglePasswordKeystoreContentMover.java rename to graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/KeystoreUtils.java index 8351a72128..c4036df058 100644 --- a/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/SinglePasswordKeystoreContentMover.java +++ b/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/KeystoreUtils.java @@ -17,19 +17,21 @@ package org.graylog.security.certutil.keystore.storage; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; import java.util.Enumeration; import static org.graylog.security.certutil.CertConstants.PKCS12; +import static org.graylog.security.certutil.CertConstants.SIGNING_ALGORITHM; -//Assumption: there is one common password, shared between the keystore and its entries -public class SinglePasswordKeystoreContentMover implements KeystoreContentMover { - - @Override - public KeyStore moveContents(KeyStore originalKeyStore, - char[] currentPassword, - final char[] newPassword) throws GeneralSecurityException, IOException { +public class KeystoreUtils { + public static KeyStore newStoreCopyContent(KeyStore originalKeyStore, + char[] currentPassword, + final char[] newPassword) throws GeneralSecurityException, IOException { if (newPassword == null) { throw new IllegalArgumentException("new password cannot be null"); } @@ -57,4 +59,22 @@ public class SinglePasswordKeystoreContentMover implements KeystoreContentMover } return newKeyStore; } + + private static final String SAMPLE_CHALLANGE = "Grayloggers! Grayloggers! Grayloggers! Grayloggers!"; + + public static boolean matchingKeys(final PrivateKey privateKey, + final PublicKey publicKey) throws GeneralSecurityException { + Signature sign = Signature.getInstance(SIGNING_ALGORITHM); + + byte[] bytes = SAMPLE_CHALLANGE.getBytes(StandardCharsets.UTF_8); + + sign.initSign(privateKey); + sign.update(bytes); + byte[] signature = sign.sign(); + + sign.initVerify(publicKey); + sign.update(bytes); + return sign.verify(signature); + } + } diff --git a/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/SmartKeystoreStorage.java b/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/SmartKeystoreStorage.java deleted file mode 100644 index 2da7340157..0000000000 --- a/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/SmartKeystoreStorage.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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.security.certutil.keystore.storage; - -import org.graylog.security.certutil.ca.exceptions.KeyStoreStorageException; -import org.graylog.security.certutil.keystore.storage.location.KeystoreFileLocation; -import org.graylog.security.certutil.keystore.storage.location.KeystoreLocation; -import org.graylog.security.certutil.keystore.storage.location.KeystoreMongoLocation; -import org.graylog2.cluster.certificates.CertificatesService; - -import jakarta.inject.Inject; - -import java.security.KeyStore; -import java.util.Optional; - -public final class SmartKeystoreStorage implements KeystoreStorage { - - private final KeystoreMongoStorage keystoreMongoStorage; - private final KeystoreFileStorage keystoreFileStorage; - - @Inject - public SmartKeystoreStorage(final CertificatesService certificatesService, - final KeystoreContentMover keystoreContentMover) { - keystoreMongoStorage = new KeystoreMongoStorage(certificatesService, keystoreContentMover); - keystoreFileStorage = new KeystoreFileStorage(keystoreContentMover); - } - - @Override - public void writeKeyStore(KeystoreLocation location, - KeyStore keyStore, - char[] currentPassword, - final char[] newPassword) throws KeyStoreStorageException { - if (location instanceof final KeystoreMongoLocation mongoLocation) { - keystoreMongoStorage.writeKeyStore(mongoLocation, keyStore, currentPassword, newPassword); - } else if (location instanceof final KeystoreFileLocation fileLocation) { - keystoreFileStorage.writeKeyStore(fileLocation, keyStore, currentPassword, newPassword); - } - } - - @Override - public Optional readKeyStore(KeystoreLocation location, - char[] password) throws KeyStoreStorageException { - if (location instanceof final KeystoreMongoLocation mongoLocation) { - return keystoreMongoStorage.readKeyStore(mongoLocation, password); - } else if (location instanceof final KeystoreFileLocation fileLocation) { - return keystoreFileStorage.readKeyStore(fileLocation, password); - } else { - return Optional.empty(); - } - } -} diff --git a/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/location/KeystoreFileLocation.java b/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/location/KeystoreFileLocation.java deleted file mode 100644 index bf3c93cdb4..0000000000 --- a/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/location/KeystoreFileLocation.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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.security.certutil.keystore.storage.location; - -import java.nio.file.Path; - -public record KeystoreFileLocation(Path keystorePath) implements KeystoreLocation { - -} diff --git a/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/location/KeystoreLocation.java b/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/location/KeystoreLocation.java index dfda4a3245..f5817480b2 100644 --- a/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/location/KeystoreLocation.java +++ b/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/location/KeystoreLocation.java @@ -16,5 +16,5 @@ */ package org.graylog.security.certutil.keystore.storage.location; -public sealed interface KeystoreLocation permits KeystoreMongoLocation, KeystoreFileLocation { +public interface KeystoreLocation { } diff --git a/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/location/KeystoreMongoLocation.java b/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/location/KeystoreMongoLocation.java index b44294b97c..c3fea71dcd 100644 --- a/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/location/KeystoreMongoLocation.java +++ b/graylog2-server/src/main/java/org/graylog/security/certutil/keystore/storage/location/KeystoreMongoLocation.java @@ -16,5 +16,38 @@ */ package org.graylog.security.certutil.keystore.storage.location; -public record KeystoreMongoLocation(String nodeId, KeystoreMongoCollection collection) implements KeystoreLocation { +import org.graylog2.plugin.system.NodeId; + +public class KeystoreMongoLocation implements KeystoreLocation { + private final String nodeId; + private final KeystoreMongoCollection collection; + + public static final String CA_KEYSTORE_ID = "GRAYLOG CA"; + + private KeystoreMongoLocation(String nodeId, KeystoreMongoCollection collection) { + this.nodeId = nodeId; + this.collection = collection; + } + + + public static KeystoreMongoLocation certificateAuthority() { + return new KeystoreMongoLocation(CA_KEYSTORE_ID, KeystoreMongoCollections.GRAYLOG_CA_KEYSTORE_COLLECTION); + } + + public static KeystoreMongoLocation datanode(NodeId nodeId) { + return datanode(nodeId.getNodeId()); + } + + public static KeystoreMongoLocation datanode(String nodeId) { + return new KeystoreMongoLocation(nodeId, KeystoreMongoCollections.DATA_NODE_KEYSTORE_COLLECTION); + } + + + public String nodeId() { + return nodeId; + } + + public KeystoreMongoCollection collection() { + return collection; + } } diff --git a/graylog2-server/src/main/java/org/graylog2/bootstrap/preflight/PreflightWebModule.java b/graylog2-server/src/main/java/org/graylog2/bootstrap/preflight/PreflightWebModule.java index ebbb985dd7..b01705ea41 100644 --- a/graylog2-server/src/main/java/org/graylog2/bootstrap/preflight/PreflightWebModule.java +++ b/graylog2-server/src/main/java/org/graylog2/bootstrap/preflight/PreflightWebModule.java @@ -24,8 +24,6 @@ import com.google.inject.multibindings.MapBinder; import com.google.inject.multibindings.Multibinder; import org.graylog.security.certutil.CaService; import org.graylog.security.certutil.CaServiceImpl; -import org.graylog.security.certutil.keystore.storage.KeystoreContentMover; -import org.graylog.security.certutil.keystore.storage.SinglePasswordKeystoreContentMover; import org.graylog2.Configuration; import org.graylog2.bindings.providers.ClusterEventBusProvider; import org.graylog2.bindings.providers.MongoConnectionProvider; @@ -75,7 +73,6 @@ public class PreflightWebModule extends Graylog2Module { bind(MongoConnection.class).toProvider(MongoConnectionProvider.class); bind(new TypeLiteral>() {}).to(ServerNodeClusterService.class); bind(new TypeLiteral>() {}).to(DataNodeClusterService.class); - bind(KeystoreContentMover.class).to(SinglePasswordKeystoreContentMover.class).asEagerSingleton(); bind(ClusterConfigService.class).to(ClusterConfigServiceImpl.class); bind(CaService.class).to(CaServiceImpl.class); diff --git a/graylog2-server/src/main/java/org/graylog2/bootstrap/preflight/ServerPreflightChecksModule.java b/graylog2-server/src/main/java/org/graylog2/bootstrap/preflight/ServerPreflightChecksModule.java index a08565c225..9ebdbf49b7 100644 --- a/graylog2-server/src/main/java/org/graylog2/bootstrap/preflight/ServerPreflightChecksModule.java +++ b/graylog2-server/src/main/java/org/graylog2/bootstrap/preflight/ServerPreflightChecksModule.java @@ -20,8 +20,6 @@ import com.google.inject.assistedinject.FactoryModuleBuilder; import okhttp3.OkHttpClient; import org.graylog.security.certutil.CaService; import org.graylog.security.certutil.CaServiceImpl; -import org.graylog.security.certutil.keystore.storage.KeystoreContentMover; -import org.graylog.security.certutil.keystore.storage.SinglePasswordKeystoreContentMover; import org.graylog2.audit.AuditEventSender; import org.graylog2.audit.NullAuditEventSender; import org.graylog2.plugin.inject.Graylog2Module; @@ -47,7 +45,6 @@ public class ServerPreflightChecksModule extends Graylog2Module { bind(X509TrustManager.class).to(CustomCAX509TrustManager.class).asEagerSingleton(); - bind(KeystoreContentMover.class).to(SinglePasswordKeystoreContentMover.class).asEagerSingleton(); bind(OkHttpClient.class).toProvider(OkHttpClientProvider.class).asEagerSingleton(); bind(ElasticsearchVersionProvider.class).asEagerSingleton(); bind(NodeId.class).toProvider(FilePersistedNodeIdProvider.class).asEagerSingleton(); diff --git a/graylog2-server/src/main/java/org/graylog2/shared/security/SecurityBindings.java b/graylog2-server/src/main/java/org/graylog2/shared/security/SecurityBindings.java index 620583f42c..eda644059c 100644 --- a/graylog2-server/src/main/java/org/graylog2/shared/security/SecurityBindings.java +++ b/graylog2-server/src/main/java/org/graylog2/shared/security/SecurityBindings.java @@ -19,8 +19,6 @@ package org.graylog2.shared.security; import com.google.inject.Scopes; import com.google.inject.assistedinject.FactoryModuleBuilder; import com.google.inject.multibindings.OptionalBinder; -import org.graylog.security.certutil.keystore.storage.KeystoreContentMover; -import org.graylog.security.certutil.keystore.storage.SinglePasswordKeystoreContentMover; import org.graylog2.plugin.PluginModule; import org.graylog2.rest.models.system.sessions.responses.DefaultSessionResponseFactory; import org.graylog2.rest.models.system.sessions.responses.SessionResponseFactory; @@ -42,7 +40,6 @@ public class SecurityBindings extends PluginModule { addPermissions(RestPermissions.class); addInitializer(UserSessionTerminationService.class); - bind(KeystoreContentMover.class).to(SinglePasswordKeystoreContentMover.class).asEagerSingleton(); install(new FactoryModuleBuilder() .implement(TrustManager.class, DefaultX509TrustManager.class) .build(TrustManagerProvider.class)); diff --git a/graylog2-server/src/test/java/org/graylog/security/certutil/csr/CertificateAndPrivateKeyMergerTest.java b/graylog2-server/src/test/java/org/graylog/security/certutil/csr/CertificateAndPrivateKeyMergerTest.java index 4489fb0a56..e9d976727e 100644 --- a/graylog2-server/src/test/java/org/graylog/security/certutil/csr/CertificateAndPrivateKeyMergerTest.java +++ b/graylog2-server/src/test/java/org/graylog/security/certutil/csr/CertificateAndPrivateKeyMergerTest.java @@ -23,50 +23,43 @@ import org.graylog.security.certutil.cert.CertificateChain; import org.graylog.security.certutil.privatekey.PrivateKeyEncryptedStorage; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; import java.security.GeneralSecurityException; +import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.cert.X509Certificate; import java.time.Duration; import java.util.Enumeration; import java.util.List; +import static org.graylog.security.certutil.CertConstants.KEY_GENERATION_ALGORITHM; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -@ExtendWith(MockitoExtension.class) public class CertificateAndPrivateKeyMergerTest { - @Mock - KeyPairChecker keyPairChecker; - @Mock - PrivateKeyEncryptedStorage privateKeyEncryptedStorage; - CertificateAndPrivateKeyMerger toTest; @BeforeEach void setUp() { - toTest = new CertificateAndPrivateKeyMerger(keyPairChecker); + toTest = new CertificateAndPrivateKeyMerger(); } @Test void testThrowsExceptionIfPrivateKeyAndCertificateDoNotMatch() throws Exception { - final PrivateKey privateKey = mock(PrivateKey.class); - final PublicKey publicKey = mock(PublicKey.class); - final X509Certificate signedCertificate = mock(X509Certificate.class); - doReturn(publicKey).when(signedCertificate).getPublicKey(); - doReturn(false).when(keyPairChecker).matchingKeys(privateKey, publicKey); - doReturn(privateKey).when(privateKeyEncryptedStorage).readEncryptedKey("privPass".toCharArray()); - assertThrows(GeneralSecurityException.class, () -> toTest.merge(new CertificateChain(signedCertificate, List.of()), - privateKeyEncryptedStorage, + + KeyPairGenerator keyGen1 = KeyPairGenerator.getInstance(KEY_GENERATION_ALGORITHM); + keyGen1.initialize(2048); + final java.security.KeyPair keyPair1 = keyGen1.genKeyPair(); + final PrivateKeyEncryptedStorage keyStorage = mockPrivateKeyStorage(keyPair1.getPrivate()); + + final CertRequest req = CertRequest.selfSigned("localhost") + .validity(Duration.ofHours(24)); + final KeyPair keyPair = CertificateGenerator.generate(req); + + assertThrows(GeneralSecurityException.class, () -> toTest.merge(new CertificateChain(keyPair.certificate(), List.of()), + keyStorage, "privPass".toCharArray(), "certPass".toCharArray(), "data-node") @@ -76,19 +69,17 @@ public class CertificateAndPrivateKeyMergerTest { @Test void testMergingOnRealPairImplementation() throws Exception { final CertRequest req = CertRequest.selfSigned("localhost") - .validity(Duration.ZERO); + .validity(Duration.ofHours(24)); final KeyPair keyPair = CertificateGenerator.generate(req); final char[] privPass = "privPass".toCharArray(); final char[] certPass = "certPass".toCharArray(); final String alias = "data-node"; - - doReturn(keyPair.privateKey()).when(privateKeyEncryptedStorage).readEncryptedKey(privPass); - doReturn(true).when(keyPairChecker).matchingKeys(keyPair.privateKey(), keyPair.publicKey()); + final PrivateKeyEncryptedStorage storage = mockPrivateKeyStorage(keyPair.privateKey()); final KeyStore merged = toTest.merge(new CertificateChain(keyPair.certificate(), List.of()), - privateKeyEncryptedStorage, + storage, privPass, certPass, alias); @@ -100,4 +91,18 @@ public class CertificateAndPrivateKeyMergerTest { assertEquals(alias, aliases.nextElement()); assertFalse(aliases.hasMoreElements()); } + + private static PrivateKeyEncryptedStorage mockPrivateKeyStorage(PrivateKey privateKey) { + return new PrivateKeyEncryptedStorage() { + @Override + public void writeEncryptedKey(char[] password, PrivateKey privateKey) { + // do nothing + } + + @Override + public PrivateKey readEncryptedKey(char[] password) { + return privateKey; + } + }; + } } diff --git a/graylog2-server/src/test/java/org/graylog/security/certutil/csr/KeyPairCheckerTest.java b/graylog2-server/src/test/java/org/graylog/security/certutil/csr/KeyPairCheckerTest.java deleted file mode 100644 index 9895ec467f..0000000000 --- a/graylog2-server/src/test/java/org/graylog/security/certutil/csr/KeyPairCheckerTest.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.security.certutil.csr; - -import org.junit.jupiter.api.Test; - -import java.security.KeyPair; -import java.security.KeyPairGenerator; - -import static org.graylog.security.certutil.CertConstants.KEY_GENERATION_ALGORITHM; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -class KeyPairCheckerTest { - - @Test - void testTrueOnValidKeyPair() throws Exception { - KeyPairChecker toTest = new KeyPairChecker(); - KeyPairGenerator keyGen = KeyPairGenerator.getInstance(KEY_GENERATION_ALGORITHM); - keyGen.initialize(2048); - final KeyPair keyPair = keyGen.genKeyPair(); - assertTrue(toTest.matchingKeys(keyPair.getPrivate(), keyPair.getPublic())); - } - - @Test - void testFalseOnInValidKeyPair() throws Exception { - KeyPairChecker toTest = new KeyPairChecker(); - KeyPairGenerator keyGen1 = KeyPairGenerator.getInstance(KEY_GENERATION_ALGORITHM); - keyGen1.initialize(2048); - final KeyPair keyPair1 = keyGen1.genKeyPair(); - KeyPairGenerator keyGen2 = KeyPairGenerator.getInstance(KEY_GENERATION_ALGORITHM); - keyGen2.initialize(2048); - final KeyPair keyPair2 = keyGen2.genKeyPair(); - //mixing keys from different pairs - assertFalse(toTest.matchingKeys(keyPair1.getPrivate(), keyPair2.getPublic())); - } - - -} diff --git a/graylog2-server/src/test/java/org/graylog/security/certutil/keystore/storage/KeystoreFileStorageTest.java b/graylog2-server/src/test/java/org/graylog/security/certutil/keystore/storage/KeystoreFileStorageTest.java index 1e9017f547..fa59bfb0a0 100644 --- a/graylog2-server/src/test/java/org/graylog/security/certutil/keystore/storage/KeystoreFileStorageTest.java +++ b/graylog2-server/src/test/java/org/graylog/security/certutil/keystore/storage/KeystoreFileStorageTest.java @@ -18,7 +18,6 @@ package org.graylog.security.certutil.keystore.storage; import org.graylog.security.certutil.CertConstants; import org.graylog.security.certutil.ca.exceptions.KeyStoreStorageException; -import org.graylog.security.certutil.keystore.storage.location.KeystoreFileLocation; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -31,8 +30,6 @@ import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verifyNoInteractions; public class KeystoreFileStorageTest { @@ -40,36 +37,31 @@ public class KeystoreFileStorageTest { @Test void testKeyStoreSaveAndRetrieveWithNoPasswordChange(@TempDir Path tmpDir) throws Exception { - final KeystoreContentMover keystoreContentMover = mock(KeystoreContentMover.class); - toTest = new KeystoreFileStorage(keystoreContentMover); + toTest = new KeystoreFileStorage(); final Path keystoreFile = tmpDir.resolve("keystore_file.p12"); KeyStore testKeystore = KeyStore.getInstance(CertConstants.PKCS12); testKeystore.load(new FileInputStream("src/test/resources/org/graylog/security/certutil/keystore/storage/sample_certificate_keystore.p12"), "password".toCharArray()); char[] passwd = "password".toCharArray(); - final KeystoreFileLocation location = new KeystoreFileLocation(keystoreFile); - toTest.writeKeyStore(location, testKeystore, passwd, null); - final Optional keyStore = toTest.readKeyStore(location, passwd); + toTest.writeKeyStore(keystoreFile, testKeystore, passwd, null); + final Optional keyStore = toTest.readKeyStore(keystoreFile, passwd); assertTrue(keyStore.isPresent()); final KeyStore keyStoreToVerify = keyStore.get(); assertEquals(testKeystore.getCertificate("datanode"), keyStoreToVerify.getCertificate("datanode")); assertEquals("RSA", keyStoreToVerify.getKey("datanode", passwd).getAlgorithm()); - verifyNoInteractions(keystoreContentMover); - } @Test void testKeyStoreSaveAndRetrieveWithPasswordChange(@TempDir Path tmpDir) throws Exception { - toTest = new KeystoreFileStorage(new SinglePasswordKeystoreContentMover()); + toTest = new KeystoreFileStorage(); final Path keystoreFile = tmpDir.resolve("keystore_file.p12"); KeyStore testKeystore = KeyStore.getInstance(CertConstants.PKCS12); testKeystore.load(new FileInputStream("src/test/resources/org/graylog/security/certutil/keystore/storage/sample_certificate_keystore.p12"), "password".toCharArray()); char[] passwd = "password".toCharArray(); char[] newPasswd = "secret".toCharArray(); - final KeystoreFileLocation location = new KeystoreFileLocation(keystoreFile); - toTest.writeKeyStore(location, testKeystore, passwd, newPasswd); - final Optional keyStore = toTest.readKeyStore(location, newPasswd); + toTest.writeKeyStore(keystoreFile, testKeystore, passwd, newPasswd); + final Optional keyStore = toTest.readKeyStore(keystoreFile, newPasswd); assertTrue(keyStore.isPresent()); final KeyStore keyStoreToVerify = keyStore.get(); assertEquals(testKeystore.getCertificate("datanode"), keyStoreToVerify.getCertificate("datanode")); @@ -80,17 +72,14 @@ public class KeystoreFileStorageTest { @Test void testKeystoreReadThrowsExceptionWhenUsingWrongPassword(@TempDir Path tmpDir) throws Exception { - final KeystoreContentMover keystoreContentMover = mock(KeystoreContentMover.class); - toTest = new KeystoreFileStorage(keystoreContentMover); + toTest = new KeystoreFileStorage(); final Path keystoreFile = tmpDir.resolve("keystore_file.p12"); KeyStore testKeystore = KeyStore.getInstance(CertConstants.PKCS12); testKeystore.load(null, null); char[] passwd = "password".toCharArray(); - final KeystoreFileLocation fileLocation = new KeystoreFileLocation(keystoreFile); - toTest.writeKeyStore(fileLocation, testKeystore, passwd, null); - assertThrows(KeyStoreStorageException.class, () -> toTest.readKeyStore(fileLocation, "wrong password".toCharArray())); - verifyNoInteractions(keystoreContentMover); + toTest.writeKeyStore(keystoreFile, testKeystore, passwd, null); + assertThrows(KeyStoreStorageException.class, () -> toTest.readKeyStore(keystoreFile, "wrong password".toCharArray())); } } diff --git a/graylog2-server/src/test/java/org/graylog/security/certutil/keystore/storage/SinglePasswordKeystoreContentMoverTest.java b/graylog2-server/src/test/java/org/graylog/security/certutil/keystore/storage/KeystoreUtilsTest.java similarity index 74% rename from graylog2-server/src/test/java/org/graylog/security/certutil/keystore/storage/SinglePasswordKeystoreContentMoverTest.java rename to graylog2-server/src/test/java/org/graylog/security/certutil/keystore/storage/KeystoreUtilsTest.java index 17a097f02a..bd5b60a2cd 100644 --- a/graylog2-server/src/test/java/org/graylog/security/certutil/keystore/storage/SinglePasswordKeystoreContentMoverTest.java +++ b/graylog2-server/src/test/java/org/graylog/security/certutil/keystore/storage/KeystoreUtilsTest.java @@ -16,37 +16,32 @@ */ package org.graylog.security.certutil.keystore.storage; +import org.assertj.core.api.Assertions; import org.graylog.security.certutil.CertRequest; import org.graylog.security.certutil.CertificateGenerator; import org.graylog.security.certutil.KeyPair; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import javax.crypto.spec.SecretKeySpec; import java.security.Key; +import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.time.Duration; +import static org.graylog.security.certutil.CertConstants.KEY_GENERATION_ALGORITHM; import static org.graylog.security.certutil.CertConstants.PKCS12; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -public class SinglePasswordKeystoreContentMoverTest { - - private SinglePasswordKeystoreContentMover toTest; - - @BeforeEach - void setUp() { - toTest = new SinglePasswordKeystoreContentMover(); - } +public class KeystoreUtilsTest { @Test void testThrowsExceptionIfNewPasswordIsNull() throws Exception { KeyStore originalKeyStore = KeyStore.getInstance(PKCS12); - assertThrows(IllegalArgumentException.class, () -> toTest.moveContents(originalKeyStore, "nvmd".toCharArray(), null)); + assertThrows(IllegalArgumentException.class, () -> KeystoreUtils.newStoreCopyContent(originalKeyStore, "nvmd".toCharArray(), null)); } @Test @@ -70,7 +65,7 @@ public class SinglePasswordKeystoreContentMoverTest { final KeyPair keyPair = CertificateGenerator.generate(req); originalKeyStore.setKeyEntry("privkey", keyPair.privateKey(), oldPassword, new Certificate[]{keyPair.certificate()}); - final KeyStore newKeyStore = toTest.moveContents(originalKeyStore, oldPassword, newPassword); + final KeyStore newKeyStore = KeystoreUtils.newStoreCopyContent(originalKeyStore, oldPassword, newPassword); final KeyStore.Entry secretRetrieved = newKeyStore.getEntry("secretEntry", new KeyStore.PasswordProtection(newPassword)); final Certificate certificateRetrieved = newKeyStore.getCertificate("trusted-certificate"); final Key privkeyRetrieved = newKeyStore.getKey("privkey", newPassword); @@ -98,9 +93,29 @@ public class SinglePasswordKeystoreContentMoverTest { final KeyPair keyPair3 = CertificateGenerator.generate(req); originalKeyStore.setKeyEntry("privkey3", keyPair3.privateKey(), oldPassword, new Certificate[]{keyPair3.certificate()}); - final KeyStore newKeyStore = toTest.moveContents(originalKeyStore, oldPassword, newPassword); + final KeyStore newKeyStore = KeystoreUtils.newStoreCopyContent(originalKeyStore, oldPassword, newPassword); assertEquals(keyPair1.privateKey(), newKeyStore.getKey("privkey1", newPassword)); assertEquals(keyPair2.privateKey(), newKeyStore.getKey("privkey2", newPassword)); assertEquals(keyPair3.privateKey(), newKeyStore.getKey("privkey3", newPassword)); } + + @Test + void testMatchingPrivatePublicKeysvalid() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance(KEY_GENERATION_ALGORITHM); + keyGen.initialize(2048); + final java.security.KeyPair keyPair = keyGen.genKeyPair(); + Assertions.assertThat(KeystoreUtils.matchingKeys(keyPair.getPrivate(), keyPair.getPublic())).isTrue(); + } + + @Test + void testMatchingPrivatePublicKeysInvalid() throws Exception { + KeyPairGenerator keyGen1 = KeyPairGenerator.getInstance(KEY_GENERATION_ALGORITHM); + keyGen1.initialize(2048); + final java.security.KeyPair keyPair1 = keyGen1.genKeyPair(); + KeyPairGenerator keyGen2 = KeyPairGenerator.getInstance(KEY_GENERATION_ALGORITHM); + keyGen2.initialize(2048); + final java.security.KeyPair keyPair2 = keyGen2.genKeyPair(); + //mixing keys from different pairs + Assertions.assertThat(KeystoreUtils.matchingKeys(keyPair1.getPrivate(), keyPair2.getPublic())).isFalse(); + } } diff --git a/graylog2-server/src/test/java/org/graylog2/cluster/certificates/CertificatesServiceTest.java b/graylog2-server/src/test/java/org/graylog2/cluster/certificates/CertificatesServiceTest.java index 0b8bd02c60..cd9903bd87 100644 --- a/graylog2-server/src/test/java/org/graylog2/cluster/certificates/CertificatesServiceTest.java +++ b/graylog2-server/src/test/java/org/graylog2/cluster/certificates/CertificatesServiceTest.java @@ -52,25 +52,22 @@ public class CertificatesServiceTest { @Test public void testReadUnExisting() { - final Optional result = certificatesService.readCert( - new KeystoreMongoLocation("there is no node with this id", DATA_NODE_KEYSTORE_COLLECTION)); + final Optional result = certificatesService.readCert(KeystoreMongoLocation.datanode("there is no node with this id")); assertTrue(result.isEmpty()); } @Test public void testReadAndWrite() { - boolean writeResult = certificatesService.writeCert(new KeystoreMongoLocation("node_id_1", DATA_NODE_KEYSTORE_COLLECTION), "Certificate string representation"); + boolean writeResult = certificatesService.writeCert(KeystoreMongoLocation.datanode("node_id_1"), "Certificate string representation"); assertTrue(writeResult); - final Optional readResult = certificatesService.readCert(new KeystoreMongoLocation("node_id_1", DATA_NODE_KEYSTORE_COLLECTION)); + final Optional readResult = certificatesService.readCert(KeystoreMongoLocation.datanode("node_id_1")); assertTrue(readResult.isPresent()); assertEquals("Certificate string representation", readResult.get()); } @Test public void testHasCertReturnsFalseWhenNoMongoEntry() { - assertFalse(certificatesService.hasCert( - new KeystoreMongoLocation("there is no node with this id", DATA_NODE_KEYSTORE_COLLECTION) - )); + assertFalse(certificatesService.hasCert(KeystoreMongoLocation.datanode("there is no node with this id"))); } @Test @@ -78,44 +75,32 @@ public class CertificatesServiceTest { //adding document without encrypted certificate final MongoCollection collection = mongoConnection.getMongoDatabase().getCollection(DATA_NODE_KEYSTORE_COLLECTION.collectionName()); collection.insertOne(new Document(DATA_NODE_KEYSTORE_COLLECTION.identifierField(), "node_id_1")); - assertFalse(certificatesService.hasCert( - new KeystoreMongoLocation("node_id_1", DATA_NODE_KEYSTORE_COLLECTION) - )); + assertFalse(certificatesService.hasCert(KeystoreMongoLocation.datanode("node_id_1"))); } @Test public void testHasCertReturnsTrueWhenProperMongoEntry() { - certificatesService.writeCert(new KeystoreMongoLocation("node_id_1", DATA_NODE_KEYSTORE_COLLECTION), "Certificate string representation"); - assertTrue(certificatesService.hasCert( - new KeystoreMongoLocation("node_id_1", DATA_NODE_KEYSTORE_COLLECTION) - )); + certificatesService.writeCert(KeystoreMongoLocation.datanode("node_id_1"), "Certificate string representation"); + assertTrue(certificatesService.hasCert(KeystoreMongoLocation.datanode("node_id_1"))); } @Test public void testCertRemoval() { - certificatesService.writeCert(new KeystoreMongoLocation("node_id_4", DATA_NODE_KEYSTORE_COLLECTION), "Certificate string representation 1"); - certificatesService.writeCert(new KeystoreMongoLocation("node_id_5", DATA_NODE_KEYSTORE_COLLECTION), "Certificate string representation 2"); + certificatesService.writeCert(KeystoreMongoLocation.datanode("node_id_4"), "Certificate string representation 1"); + certificatesService.writeCert(KeystoreMongoLocation.datanode("node_id_5"), "Certificate string representation 2"); - assertTrue(certificatesService.hasCert( - new KeystoreMongoLocation("node_id_4", DATA_NODE_KEYSTORE_COLLECTION) - )); + assertTrue(certificatesService.hasCert(KeystoreMongoLocation.datanode("node_id_4"))); - assertTrue(certificatesService.hasCert( - new KeystoreMongoLocation("node_id_5", DATA_NODE_KEYSTORE_COLLECTION) - )); + assertTrue(certificatesService.hasCert(KeystoreMongoLocation.datanode("node_id_5"))); - var result = certificatesService.removeCert(new KeystoreMongoLocation("node_id_4", DATA_NODE_KEYSTORE_COLLECTION)); + var result = certificatesService.removeCert(KeystoreMongoLocation.datanode("node_id_4")); assertTrue("Removal of an existing cert should result in 'true'", result); - assertFalse(certificatesService.hasCert( - new KeystoreMongoLocation("node_id_4", DATA_NODE_KEYSTORE_COLLECTION) - )); + assertFalse(certificatesService.hasCert(KeystoreMongoLocation.datanode("node_id_4"))); - assertTrue(certificatesService.hasCert( - new KeystoreMongoLocation("node_id_5", DATA_NODE_KEYSTORE_COLLECTION) - )); + assertTrue(certificatesService.hasCert(KeystoreMongoLocation.datanode("node_id_5"))); - result = certificatesService.removeCert(new KeystoreMongoLocation("node_id_6", DATA_NODE_KEYSTORE_COLLECTION)); + result = certificatesService.removeCert(KeystoreMongoLocation.datanode("node_id_6")); assertFalse("Removal of non existing cert should result in 'false'", result); }