From 047b680ddcf833834a30b23ffe75eae56907322c Mon Sep 17 00:00:00 2001 From: Lucas Saldanha Date: Tue, 10 Aug 2021 15:23:44 +1200 Subject: [PATCH] PKI Block Creation cli configuration (#2619) * Added CLI options for PKI Block Creation * BesuCommand changes for PKI Block Creation Signed-off-by: Lucas Saldanha --- besu/build.gradle | 1 + .../org/hyperledger/besu/cli/BesuCommand.java | 21 ++- .../unstable/PkiBlockCreationOptions.java | 144 ++++++++++++++++++ .../controller/BesuControllerBuilder.java | 9 ++ .../controller/QbftBesuControllerBuilder.java | 13 +- .../hyperledger/besu/cli/BesuCommandTest.java | 71 +++++++++ .../besu/cli/CommandTestAbstract.java | 23 ++- .../pki/PkiBlockCreationConfiguration.java | 46 ++++++ ...PkiBlockCreationConfigurationProvider.java | 91 +++++++++++ .../consensus/qbft/pki/PkiQbftContext.java | 11 +- ...lockCreationConfigurationProviderTest.java | 71 +++++++++ .../PkiKeyStoreConfiguration.java} | 29 +++- 12 files changed, 505 insertions(+), 25 deletions(-) create mode 100644 besu/src/main/java/org/hyperledger/besu/cli/options/unstable/PkiBlockCreationOptions.java create mode 100644 consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiBlockCreationConfiguration.java create mode 100644 consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiBlockCreationConfigurationProvider.java create mode 100644 consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/pki/PkiBlockCreationConfigurationProviderTest.java rename pki/src/main/java/org/hyperledger/besu/pki/{PkiConfiguration.java => config/PkiKeyStoreConfiguration.java} (85%) diff --git a/besu/build.gradle b/besu/build.gradle index f9f5d34b55..4f6e799f55 100644 --- a/besu/build.gradle +++ b/besu/build.gradle @@ -90,4 +90,5 @@ dependencies { testImplementation 'org.assertj:assertj-core' testImplementation 'org.awaitility:awaitility' testImplementation 'org.mockito:mockito-core' + testImplementation 'commons-io:commons-io' } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java index 3dcc1baa45..338b00189b 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -59,6 +59,7 @@ import org.hyperledger.besu.cli.options.unstable.MiningOptions; import org.hyperledger.besu.cli.options.unstable.NatOptions; import org.hyperledger.besu.cli.options.unstable.NativeLibraryOptions; import org.hyperledger.besu.cli.options.unstable.NetworkingOptions; +import org.hyperledger.besu.cli.options.unstable.PkiBlockCreationOptions; import org.hyperledger.besu.cli.options.unstable.PrivacyPluginOptions; import org.hyperledger.besu.cli.options.unstable.RPCOptions; import org.hyperledger.besu.cli.options.unstable.SynchronizerOptions; @@ -78,6 +79,8 @@ import org.hyperledger.besu.cli.util.VersionProvider; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.config.GoQuorumOptions; +import org.hyperledger.besu.consensus.qbft.pki.PkiBlockCreationConfiguration; +import org.hyperledger.besu.consensus.qbft.pki.PkiBlockCreationConfigurationProvider; import org.hyperledger.besu.controller.BesuController; import org.hyperledger.besu.controller.BesuControllerBuilder; import org.hyperledger.besu.controller.TargetingGasLimitCalculator; @@ -286,6 +289,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { new PreSynchronizationTaskRunner(); private final Set allocatedPorts = new HashSet<>(); + private final PkiBlockCreationConfigurationProvider pkiBlockCreationConfigProvider; // CLI options defined by user at runtime. // Options parsing is done with CLI library Picocli https://picocli.info/ @@ -1097,6 +1101,8 @@ public class BesuCommand implements DefaultCommandValues, Runnable { @Mixin private P2PTLSConfigOptions p2pTLSConfigOptions; + @Mixin private PkiBlockCreationOptions pkiBlockCreationOptions; + private EthNetworkConfig ethNetworkConfig; private JsonRpcConfiguration jsonRpcConfiguration; private GraphQLConfiguration graphQLConfiguration; @@ -1136,7 +1142,8 @@ public class BesuCommand implements DefaultCommandValues, Runnable { new StorageServiceImpl(), new SecurityModuleServiceImpl(), new PermissioningServiceImpl(), - new PrivacyPluginServiceImpl()); + new PrivacyPluginServiceImpl(), + new PkiBlockCreationConfigurationProvider()); } @VisibleForTesting @@ -1152,7 +1159,8 @@ public class BesuCommand implements DefaultCommandValues, Runnable { final StorageServiceImpl storageService, final SecurityModuleServiceImpl securityModuleService, final PermissioningServiceImpl permissioningService, - final PrivacyPluginServiceImpl privacyPluginPluginService) { + final PrivacyPluginServiceImpl privacyPluginPluginService, + final PkiBlockCreationConfigurationProvider pkiBlockCreationConfigProvider) { this.logger = logger; this.rlpBlockImporter = rlpBlockImporter; this.rlpBlockExporterFactory = rlpBlockExporterFactory; @@ -1167,6 +1175,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { this.privacyPluginPluginService = privacyPluginPluginService; pluginCommonConfiguration = new BesuCommandConfigurationService(); besuPluginContext.addService(BesuConfiguration.class, pluginCommonConfiguration); + this.pkiBlockCreationConfigProvider = pkiBlockCreationConfigProvider; } public void parse( @@ -1441,6 +1450,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { validateNetStatsParams(); validateDnsOptionsParams(); p2pTLSConfigOptions.checkP2PTLSOptionsDependencies(logger, commandLine); + pkiBlockCreationOptions.checkPkiBlockCreationOptionsDependencies(logger, commandLine); } @SuppressWarnings("ConstantConditions") @@ -1711,6 +1721,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { .metricsSystem(metricsSystem.get()) .messagePermissioningProviders(permissioningService.getMessagePermissioningProviders()) .privacyParameters(privacyParameters(storageProvider)) + .pkiBlockCreationConfiguration(maybePkiBlockCreationConfiguration()) .clock(Clock.systemUTC()) .isRevertReasonEnabled(isRevertReasonEnabled) .storageProvider(storageProvider) @@ -2269,6 +2280,12 @@ public class BesuCommand implements DefaultCommandValues, Runnable { return this.keyValueStorageProvider; } + private Optional maybePkiBlockCreationConfiguration() { + return pkiBlockCreationOptions + .asDomainConfig(commandLine) + .map(pkiBlockCreationConfigProvider::load); + } + private SynchronizerConfiguration buildSyncConfig() { return unstableSynchronizerOptions .toDomainObject() diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/PkiBlockCreationOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/PkiBlockCreationOptions.java new file mode 100644 index 0000000000..5bf9501f6b --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/PkiBlockCreationOptions.java @@ -0,0 +1,144 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.hyperledger.besu.cli.options.unstable; + +import static java.util.Arrays.asList; +import static org.hyperledger.besu.cli.DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP; + +import org.hyperledger.besu.cli.util.CommandLineUtils; +import org.hyperledger.besu.ethereum.api.tls.FileBasedPasswordProvider; +import org.hyperledger.besu.pki.config.PkiKeyStoreConfiguration; + +import java.nio.file.Path; +import java.util.Optional; + +import org.apache.logging.log4j.Logger; +import picocli.CommandLine; +import picocli.CommandLine.Option; +import picocli.CommandLine.ParameterException; + +public class PkiBlockCreationOptions { + + @Option( + names = {"--Xpki-block-creation-enabled"}, + hidden = true, + description = "Enable PKI integration (default: ${DEFAULT-VALUE})") + Boolean enabled = false; + + @Option( + names = {"--Xpki-block-creation-keystore-type"}, + hidden = true, + paramLabel = "", + description = "PKI service keystore type. Required if PKI Block Creation is enabled.") + @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) + String keyStoreType = PkiKeyStoreConfiguration.DEFAULT_KEYSTORE_TYPE; + + @Option( + names = {"--Xpki-block-creation-keystore-file"}, + hidden = true, + paramLabel = MANDATORY_FILE_FORMAT_HELP, + description = "Keystore containing key/certificate for PKI Block Creation.") + Path keyStoreFile = null; + + @Option( + names = {"--Xpki-block-creation-keystore-password-file"}, + hidden = true, + paramLabel = MANDATORY_FILE_FORMAT_HELP, + description = + "File containing password to unlock keystore for PKI Integration. Required if PKI Block Creation is enabled.") + Path keyStorePasswordFile = null; + + @Option( + names = {"--Xpki-block-creation-keystore-certificate-alias"}, + hidden = true, + paramLabel = "", + description = + "Alias of the certificate that will be included in the blocks proposed by this validator.") + @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) + String certificateAlias = PkiKeyStoreConfiguration.DEFAULT_CERTIFICATE_ALIAS; + + @Option( + names = {"--Xpki-block-creation-truststore-type"}, + hidden = true, + paramLabel = "", + description = "PKI Integration truststore type.") + @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) + String trustStoreType = PkiKeyStoreConfiguration.DEFAULT_KEYSTORE_TYPE; + + @Option( + names = {"--Xpki-block-creation-truststore-file"}, + hidden = true, + paramLabel = MANDATORY_FILE_FORMAT_HELP, + description = "Truststore containing trusted certificates for PKI Block Creation.") + Path trustStoreFile = null; + + @Option( + names = {"--Xpki-block-creation-truststore-password-file"}, + hidden = true, + paramLabel = MANDATORY_FILE_FORMAT_HELP, + description = "File containing password to unlock truststore for PKI Block Creation.") + Path trustStorePasswordFile = null; + + @Option( + names = {"--Xpki-block-creation-crl-file"}, + hidden = true, + paramLabel = MANDATORY_FILE_FORMAT_HELP, + description = "File with all CRLs for PKI Block Creation.") + Path crlFile = null; + + public Optional asDomainConfig(final CommandLine commandLine) { + if (!enabled) { + return Optional.empty(); + } + + if (keyStoreFile == null) { + throw new ParameterException( + commandLine, "KeyStore file is required when PKI Block Creation is enabled"); + } + + if (keyStorePasswordFile == null) { + throw new ParameterException( + commandLine, + "File containing password to unlock keystore is required when PKI Block Creation is enabled"); + } + + return Optional.of( + new PkiKeyStoreConfiguration.Builder() + .withKeyStoreType(keyStoreType) + .withKeyStorePath(keyStoreFile) + .withKeyStorePasswordSupplier(new FileBasedPasswordProvider(keyStorePasswordFile)) + .withCertificateAlias(certificateAlias) + .withTrustStoreType(trustStoreType) + .withTrustStorePath(trustStoreFile) + .withTrustStorePasswordSupplier( + null == trustStorePasswordFile + ? null + : new FileBasedPasswordProvider(trustStorePasswordFile)) + .withCrlFilePath(crlFile) + .build()); + } + + public void checkPkiBlockCreationOptionsDependencies( + final Logger logger, final CommandLine commandLine) { + CommandLineUtils.checkOptionDependencies( + logger, + commandLine, + "--Xpki-block-creation-enabled", + !enabled, + asList( + "--Xpki-block-creation-keystore-file", "--Xpki-block-creation-keystore-password-file")); + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java index a56638c504..7420df7161 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -17,6 +17,7 @@ package org.hyperledger.besu.controller; import static com.google.common.base.Preconditions.checkNotNull; import org.hyperledger.besu.config.GenesisConfigFile; +import org.hyperledger.besu.consensus.qbft.pki.PkiBlockCreationConfiguration; import org.hyperledger.besu.crypto.NodeKey; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.methods.JsonRpcMethods; @@ -91,6 +92,8 @@ public abstract class BesuControllerBuilder { protected MiningParameters miningParameters; protected ObservableMetricsSystem metricsSystem; protected PrivacyParameters privacyParameters; + protected Optional pkiBlockCreationConfiguration = + Optional.empty(); protected Path dataDirectory; protected Clock clock; protected NodeKey nodeKey; @@ -160,6 +163,12 @@ public abstract class BesuControllerBuilder { return this; } + public BesuControllerBuilder pkiBlockCreationConfiguration( + final Optional pkiBlockCreationConfiguration) { + this.pkiBlockCreationConfiguration = pkiBlockCreationConfiguration; + return this; + } + public BesuControllerBuilder dataDirectory(final Path dataDirectory) { this.dataDirectory = dataDirectory; return this; diff --git a/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java index 709525ebdc..4d3ddd6473 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java @@ -73,13 +73,11 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.p2p.config.SubProtocolConfiguration; import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; -import org.hyperledger.besu.pki.keystore.KeyStoreWrapper; import org.hyperledger.besu.util.Subscribers; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -93,14 +91,12 @@ public class QbftBesuControllerBuilder extends BftBesuControllerBuilder { private BftEventQueue bftEventQueue; private QbftConfigOptions qbftConfig; private ValidatorPeers peers; - // TODO initialize this in BesuCommand as part of the PKI setup (will be done in a follow up PR) - private final Optional pkiKeyStore = Optional.empty(); @Override protected Supplier bftExtraDataCodec() { return Suppliers.memoize( () -> { - if (pkiKeyStore.isPresent()) { + if (pkiBlockCreationConfiguration.isPresent()) { return new PkiQbftExtraDataCodec(); } else { return new QbftExtraDataCodec(); @@ -295,9 +291,12 @@ public class QbftBesuControllerBuilder extends BftBesuControllerBuilder { validatorProvider = new TransactionValidatorProvider(blockchain, validatorContractController); } - if (pkiKeyStore.isPresent()) { + if (pkiBlockCreationConfiguration.isPresent()) { return new PkiQbftContext( - validatorProvider, epochManager, bftBlockInterface().get(), pkiKeyStore.get()); + validatorProvider, + epochManager, + bftBlockInterface().get(), + pkiBlockCreationConfiguration.get()); } else { return new BftContext(validatorProvider, epochManager, bftBlockInterface().get()); } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java index 82bf82490e..d3412e0c45 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java @@ -75,6 +75,7 @@ import org.hyperledger.besu.ethereum.worldstate.PrunerConfiguration; import org.hyperledger.besu.metrics.StandardMetricCategory; import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; import org.hyperledger.besu.nat.NatMethod; +import org.hyperledger.besu.pki.config.PkiKeyStoreConfiguration; import org.hyperledger.besu.plugin.data.EnodeURL; import org.hyperledger.besu.util.number.Fraction; import org.hyperledger.besu.util.number.Percentage; @@ -99,6 +100,7 @@ import java.util.stream.Stream; import com.google.common.collect.Lists; import com.google.common.io.Resources; import io.vertx.core.json.JsonObject; +import org.apache.commons.io.FileUtils; import org.apache.commons.text.StringEscapeUtils; import org.apache.logging.log4j.Level; import org.apache.tuweni.bytes.Bytes; @@ -4312,4 +4314,73 @@ public class BesuCommandTest extends CommandTestAbstract { assertThat(AbstractAltBnPrecompiledContract.isNative()).isTrue(); verify(mockLogger).info("Using LibEthPairings native alt bn128"); } + + @Test + public void pkiBlockCreationIsDisabledByDefault() { + parseCommand(); + + verifyNoInteractions(mockPkiBlockCreationConfigProvider); + } + + @Test + public void pkiBlockCreationKeyStoreFileRequired() { + parseCommand( + "--Xpki-block-creation-enabled", + "--Xpki-block-creation-keystore-password-file", + "/tmp/pwd"); + + assertThat(commandErrorOutput.toString()) + .contains("KeyStore file is required when PKI Block Creation is enabled"); + } + + @Test + public void pkiBlockCreationPasswordFileRequired() { + parseCommand( + "--Xpki-block-creation-enabled", "--Xpki-block-creation-keystore-file", "/tmp/keystore"); + + assertThat(commandErrorOutput.toString()) + .contains( + "File containing password to unlock keystore is required when PKI Block Creation is enabled"); + } + + @Rule public TemporaryFolder pkiTempFolder = new TemporaryFolder(); + + @Test + public void pkiBlockCreationFullConfig() throws Exception { + // Create temp file with password + final File pwdFile = pkiTempFolder.newFile("pwd"); + FileUtils.writeStringToFile(pwdFile, "foo", UTF_8); + + parseCommand( + "--Xpki-block-creation-enabled", + "--Xpki-block-creation-keystore-type", + "JKS", + "--Xpki-block-creation-keystore-file", + "/tmp/keystore", + "--Xpki-block-creation-keystore-password-file", + pwdFile.getAbsolutePath(), + "--Xpki-block-creation-keystore-certificate-alias", + "anAlias", + "--Xpki-block-creation-truststore-type", + "JKS", + "--Xpki-block-creation-truststore-file", + "/tmp/truststore", + "--Xpki-block-creation-truststore-password-file", + pwdFile.getAbsolutePath(), + "--Xpki-block-creation-crl-file", + "/tmp/crl"); + + final PkiKeyStoreConfiguration pkiKeyStoreConfig = + pkiKeyStoreConfigurationArgumentCaptor.getValue(); + + assertThat(pkiKeyStoreConfig).isNotNull(); + assertThat(pkiKeyStoreConfig.getKeyStoreType()).isEqualTo("JKS"); + assertThat(pkiKeyStoreConfig.getKeyStorePath()).isEqualTo(Path.of("/tmp/keystore")); + assertThat(pkiKeyStoreConfig.getKeyStorePassword()).isEqualTo("foo"); + assertThat(pkiKeyStoreConfig.getCertificateAlias()).isEqualTo("anAlias"); + assertThat(pkiKeyStoreConfig.getTrustStoreType()).isEqualTo("JKS"); + assertThat(pkiKeyStoreConfig.getTrustStorePath()).isEqualTo(Path.of("/tmp/truststore")); + assertThat(pkiKeyStoreConfig.getTrustStorePassword()).isEqualTo("foo"); + assertThat(pkiKeyStoreConfig.getCrlFilePath()).hasValue(Path.of("/tmp/crl")); + } } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java b/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java index 6570bc9ee4..e1a48ea8f2 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java @@ -37,6 +37,8 @@ import org.hyperledger.besu.cli.options.unstable.MetricsCLIOptions; import org.hyperledger.besu.cli.options.unstable.NetworkingOptions; import org.hyperledger.besu.cli.options.unstable.SynchronizerOptions; import org.hyperledger.besu.cli.options.unstable.TransactionPoolOptions; +import org.hyperledger.besu.consensus.qbft.pki.PkiBlockCreationConfiguration; +import org.hyperledger.besu.consensus.qbft.pki.PkiBlockCreationConfigurationProvider; import org.hyperledger.besu.controller.BesuController; import org.hyperledger.besu.controller.BesuControllerBuilder; import org.hyperledger.besu.controller.NoopPluginServiceFactory; @@ -62,6 +64,7 @@ import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; +import org.hyperledger.besu.pki.config.PkiKeyStoreConfiguration; import org.hyperledger.besu.plugin.services.BesuConfiguration; import org.hyperledger.besu.plugin.services.PicoCLIOptions; import org.hyperledger.besu.plugin.services.StorageService; @@ -156,6 +159,9 @@ public abstract class CommandTestAbstract { @Mock protected Logger mockLogger; + @Mock protected PkiBlockCreationConfigurationProvider mockPkiBlockCreationConfigProvider; + @Mock protected PkiBlockCreationConfiguration mockPkiBlockCreationConfiguration; + @Captor protected ArgumentCaptor> bytesCollectionCollector; @Captor protected ArgumentCaptor pathArgumentCaptor; @Captor protected ArgumentCaptor stringArgumentCaptor; @@ -170,6 +176,7 @@ public abstract class CommandTestAbstract { @Captor protected ArgumentCaptor storageProviderArgumentCaptor; @Captor protected ArgumentCaptor ethProtocolConfigurationArgumentCaptor; @Captor protected ArgumentCaptor dataStorageConfigurationArgumentCaptor; + @Captor protected ArgumentCaptor pkiKeyStoreConfigurationArgumentCaptor; @Captor protected ArgumentCaptor> @@ -196,6 +203,8 @@ public abstract class CommandTestAbstract { when(mockControllerBuilder.messagePermissioningProviders(any())) .thenReturn(mockControllerBuilder); when(mockControllerBuilder.privacyParameters(any())).thenReturn(mockControllerBuilder); + when(mockControllerBuilder.pkiBlockCreationConfiguration(any())) + .thenReturn(mockControllerBuilder); when(mockControllerBuilder.clock(any())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.isRevertReasonEnabled(false)).thenReturn(mockControllerBuilder); when(mockControllerBuilder.storageProvider(any())).thenReturn(mockControllerBuilder); @@ -286,6 +295,11 @@ public abstract class CommandTestAbstract { lenient() .when(mockBesuPluginContext.getService(StorageService.class)) .thenReturn(Optional.of(storageService)); + + lenient() + .doReturn(mockPkiBlockCreationConfiguration) + .when(mockPkiBlockCreationConfigProvider) + .load(pkiKeyStoreConfigurationArgumentCaptor.capture()); } // Display outputs for debug purpose @@ -335,7 +349,8 @@ public abstract class CommandTestAbstract { mockBesuPluginContext, environment, storageService, - securityModuleService); + securityModuleService, + mockPkiBlockCreationConfigProvider); besuCommands.add(besuCommand); besuCommand.setBesuConfiguration(commonPluginConfiguration); @@ -369,7 +384,8 @@ public abstract class CommandTestAbstract { final BesuPluginContextImpl besuPluginContext, final Map environment, final StorageServiceImpl storageService, - final SecurityModuleServiceImpl securityModuleService) { + final SecurityModuleServiceImpl securityModuleService, + final PkiBlockCreationConfigurationProvider pkiBlockCreationConfigProvider) { super( mockLogger, mockBlockImporter, @@ -382,7 +398,8 @@ public abstract class CommandTestAbstract { storageService, securityModuleService, new PermissioningServiceImpl(), - new PrivacyPluginServiceImpl()); + new PrivacyPluginServiceImpl(), + pkiBlockCreationConfigProvider); this.mockNodeKey = mockNodeKey; this.keyPair = keyPair; } diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiBlockCreationConfiguration.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiBlockCreationConfiguration.java new file mode 100644 index 0000000000..ed82b452e4 --- /dev/null +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiBlockCreationConfiguration.java @@ -0,0 +1,46 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.hyperledger.besu.consensus.qbft.pki; + +import org.hyperledger.besu.pki.keystore.KeyStoreWrapper; + +public class PkiBlockCreationConfiguration { + + private final KeyStoreWrapper keyStore; + private final KeyStoreWrapper trustStore; + private final String certificateAlias; + + public PkiBlockCreationConfiguration( + final KeyStoreWrapper keyStore, + final KeyStoreWrapper trustStore, + final String certificateAlias) { + this.keyStore = keyStore; + this.trustStore = trustStore; + this.certificateAlias = certificateAlias; + } + + public KeyStoreWrapper getKeyStore() { + return keyStore; + } + + public KeyStoreWrapper getTrustStore() { + return trustStore; + } + + public String getCertificateAlias() { + return certificateAlias; + } +} diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiBlockCreationConfigurationProvider.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiBlockCreationConfigurationProvider.java new file mode 100644 index 0000000000..6fd89a93c2 --- /dev/null +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiBlockCreationConfigurationProvider.java @@ -0,0 +1,91 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.hyperledger.besu.consensus.qbft.pki; + +import static com.google.common.base.Preconditions.checkNotNull; + +import org.hyperledger.besu.pki.config.PkiKeyStoreConfiguration; +import org.hyperledger.besu.pki.keystore.KeyStoreWrapper; +import org.hyperledger.besu.pki.keystore.SoftwareKeyStoreWrapper; + +import java.nio.file.Path; + +import com.google.common.annotations.VisibleForTesting; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class PkiBlockCreationConfigurationProvider { + + private static final Logger LOG = LogManager.getLogger(); + + private final KeyStoreWrapperProvider keyStoreWrapperProvider; + + public PkiBlockCreationConfigurationProvider() { + this(SoftwareKeyStoreWrapper::new); + } + + @VisibleForTesting + PkiBlockCreationConfigurationProvider(final KeyStoreWrapperProvider keyStoreWrapperProvider) { + this.keyStoreWrapperProvider = checkNotNull(keyStoreWrapperProvider); + } + + public PkiBlockCreationConfiguration load( + final PkiKeyStoreConfiguration pkiKeyStoreConfiguration) { + KeyStoreWrapper keyStore; + try { + keyStore = + keyStoreWrapperProvider.apply( + pkiKeyStoreConfiguration.getKeyStoreType(), + pkiKeyStoreConfiguration.getKeyStorePath(), + pkiKeyStoreConfiguration.getKeyStorePassword(), + null); + LOG.info("Loaded PKI Block Creation KeyStore {}", pkiKeyStoreConfiguration.getKeyStorePath()); + } catch (Exception e) { + final String message = "Error loading PKI Block Creation KeyStore"; + LOG.error(message, e); + throw new RuntimeException(message, e); + } + + KeyStoreWrapper trustStore; + try { + trustStore = + keyStoreWrapperProvider.apply( + pkiKeyStoreConfiguration.getTrustStoreType(), + pkiKeyStoreConfiguration.getTrustStorePath(), + pkiKeyStoreConfiguration.getTrustStorePassword(), + pkiKeyStoreConfiguration.getCrlFilePath().orElse(null)); + LOG.info( + "Loaded PKI Block Creation TrustStore {}", pkiKeyStoreConfiguration.getTrustStorePath()); + } catch (Exception e) { + final String message = "Error loading PKI Block Creation TrustStore"; + LOG.error(message, e); + throw new RuntimeException(message, e); + } + + return new PkiBlockCreationConfiguration( + keyStore, trustStore, pkiKeyStoreConfiguration.getCertificateAlias()); + } + + @FunctionalInterface + interface KeyStoreWrapperProvider { + + KeyStoreWrapper apply( + final String keyStoreType, + final Path keyStorePath, + final String keyStorePassword, + final Path crl); + } +} diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftContext.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftContext.java index 33cc0f5655..13ea947335 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftContext.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftContext.java @@ -19,22 +19,21 @@ import org.hyperledger.besu.consensus.common.EpochManager; import org.hyperledger.besu.consensus.common.bft.BftBlockInterface; import org.hyperledger.besu.consensus.common.bft.BftContext; import org.hyperledger.besu.consensus.common.validator.ValidatorProvider; -import org.hyperledger.besu.pki.keystore.KeyStoreWrapper; public class PkiQbftContext extends BftContext { - private final KeyStoreWrapper keyStoreWrapper; + private final PkiBlockCreationConfiguration pkiBlockCreationConfiguration; public PkiQbftContext( final ValidatorProvider validatorProvider, final EpochManager epochManager, final BftBlockInterface blockInterface, - final KeyStoreWrapper keyStoreWrapper) { + final PkiBlockCreationConfiguration pkiBlockCreationConfiguration) { super(validatorProvider, epochManager, blockInterface); - this.keyStoreWrapper = keyStoreWrapper; + this.pkiBlockCreationConfiguration = pkiBlockCreationConfiguration; } - public KeyStoreWrapper getKeyStoreWrapper() { - return keyStoreWrapper; + public PkiBlockCreationConfiguration getPkiBlockCreationConfiguration() { + return pkiBlockCreationConfiguration; } } diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/pki/PkiBlockCreationConfigurationProviderTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/pki/PkiBlockCreationConfigurationProviderTest.java new file mode 100644 index 0000000000..74382a0eb9 --- /dev/null +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/pki/PkiBlockCreationConfigurationProviderTest.java @@ -0,0 +1,71 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.hyperledger.besu.consensus.qbft.pki; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.consensus.qbft.pki.PkiBlockCreationConfigurationProvider.KeyStoreWrapperProvider; +import org.hyperledger.besu.pki.config.PkiKeyStoreConfiguration; +import org.hyperledger.besu.pki.keystore.KeyStoreWrapper; + +import java.nio.file.Path; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class PkiBlockCreationConfigurationProviderTest { + + @Mock KeyStoreWrapperProvider keyStoreWrapperProvider; + @Mock KeyStoreWrapper keyStoreWrapper; + @Mock KeyStoreWrapper trustStoreWrapper; + + @Test + public void pkiBlockCreationConfigurationIsLoadedCorrectly() { + when(keyStoreWrapperProvider.apply(any(), eq(Path.of("/tmp/keystore")), eq("pwd"), isNull())) + .thenReturn(keyStoreWrapper); + when(keyStoreWrapperProvider.apply( + any(), eq(Path.of("/tmp/truststore")), eq("pwd"), eq(Path.of("/tmp/crl")))) + .thenReturn(trustStoreWrapper); + + final PkiKeyStoreConfiguration pkiKeyStoreConfiguration = + new PkiKeyStoreConfiguration.Builder() + .withKeyStorePath(Path.of("/tmp/keystore")) + .withKeyStorePasswordSupplier(() -> "pwd") + .withTrustStorePath(Path.of("/tmp/truststore")) + .withTrustStorePasswordSupplier(() -> "pwd") + .withCertificateAlias("anAlias") + .withCrlFilePath(Path.of("/tmp/crl")) + .build(); + + final PkiBlockCreationConfigurationProvider pkiBlockCreationConfigProvider = + new PkiBlockCreationConfigurationProvider(keyStoreWrapperProvider); + + final PkiBlockCreationConfiguration pkiBlockCreationConfiguration = + pkiBlockCreationConfigProvider.load(pkiKeyStoreConfiguration); + + assertThat(pkiBlockCreationConfiguration).isNotNull(); + assertThat(pkiBlockCreationConfiguration.getKeyStore()).isNotNull(); + assertThat(pkiBlockCreationConfiguration.getTrustStore()).isNotNull(); + assertThat(pkiBlockCreationConfiguration.getCertificateAlias()).isEqualTo("anAlias"); + } +} diff --git a/pki/src/main/java/org/hyperledger/besu/pki/PkiConfiguration.java b/pki/src/main/java/org/hyperledger/besu/pki/config/PkiKeyStoreConfiguration.java similarity index 85% rename from pki/src/main/java/org/hyperledger/besu/pki/PkiConfiguration.java rename to pki/src/main/java/org/hyperledger/besu/pki/config/PkiKeyStoreConfiguration.java index 5b107fda2e..0db52287ec 100644 --- a/pki/src/main/java/org/hyperledger/besu/pki/PkiConfiguration.java +++ b/pki/src/main/java/org/hyperledger/besu/pki/config/PkiKeyStoreConfiguration.java @@ -12,14 +12,15 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.pki; +package org.hyperledger.besu.pki.config; import static java.util.Objects.requireNonNull; import java.nio.file.Path; +import java.util.Optional; import java.util.function.Supplier; -public class PkiConfiguration { +public class PkiKeyStoreConfiguration { public static String DEFAULT_KEYSTORE_TYPE = "PKCS12"; public static String DEFAULT_CERTIFICATE_ALIAS = "validator"; @@ -31,15 +32,17 @@ public class PkiConfiguration { private final String trustStoreType; private final Path trustStorePath; private final Supplier trustStorePasswordSupplier; + private final Optional crlFilePath; - public PkiConfiguration( + public PkiKeyStoreConfiguration( final String keyStoreType, final Path keyStorePath, final Supplier keyStorePasswordSupplier, final String certificateAlias, final String trustStoreType, final Path trustStorePath, - final Supplier trustStorePasswordSupplier) { + final Supplier trustStorePasswordSupplier, + final Optional crlFilePath) { this.keyStoreType = keyStoreType; this.keyStorePath = keyStorePath; this.keyStorePasswordSupplier = keyStorePasswordSupplier; @@ -47,6 +50,7 @@ public class PkiConfiguration { this.trustStoreType = trustStoreType; this.trustStorePath = trustStorePath; this.trustStorePasswordSupplier = trustStorePasswordSupplier; + this.crlFilePath = crlFilePath; } public String getKeyStoreType() { @@ -77,6 +81,10 @@ public class PkiConfiguration { return trustStorePasswordSupplier.get(); } + public Optional getCrlFilePath() { + return crlFilePath; + } + public static final class Builder { private String keyStoreType = DEFAULT_KEYSTORE_TYPE; @@ -86,6 +94,7 @@ public class PkiConfiguration { private String trustStoreType = DEFAULT_KEYSTORE_TYPE; private Path trustStorePath; private Supplier trustStorePasswordSupplier; + private Path crlFilePath; public Builder() {} @@ -125,17 +134,23 @@ public class PkiConfiguration { return this; } - public PkiConfiguration build() { + public Builder withCrlFilePath(final Path filePath) { + this.crlFilePath = filePath; + return this; + } + + public PkiKeyStoreConfiguration build() { requireNonNull(keyStoreType, "Key Store Type must not be null"); requireNonNull(keyStorePasswordSupplier, "Key Store password supplier must not be null"); - return new PkiConfiguration( + return new PkiKeyStoreConfiguration( keyStoreType, keyStorePath, keyStorePasswordSupplier, certificateAlias, trustStoreType, trustStorePath, - trustStorePasswordSupplier); + trustStorePasswordSupplier, + Optional.ofNullable(crlFilePath)); } } }