Go quorum privacy configuration (#1718)

* add goquorum privacy configuration

Signed-off-by: Stefan Pingel <stefan.pingel@consensys.net>
This commit is contained in:
Stefan Pingel
2020-12-17 13:03:42 +10:00
committed by GitHub
parent cdf6554141
commit 14789af5e5
5 changed files with 146 additions and 31 deletions

View File

@@ -84,6 +84,7 @@ import org.hyperledger.besu.crypto.KeyPairUtil;
import org.hyperledger.besu.crypto.NodeKey;
import org.hyperledger.besu.crypto.SECP256K1;
import org.hyperledger.besu.enclave.EnclaveFactory;
import org.hyperledger.besu.enclave.GoQuorumEnclave;
import org.hyperledger.besu.ethereum.api.ApiConfiguration;
import org.hyperledger.besu.ethereum.api.ImmutableApiConfiguration;
import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration;
@@ -97,6 +98,7 @@ import org.hyperledger.besu.ethereum.api.tls.TlsConfiguration;
import org.hyperledger.besu.ethereum.blockcreation.GasLimitCalculator;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.GoQuorumPrivacyParameters;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.core.MiningParameters;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
@@ -163,6 +165,7 @@ import java.net.UnknownHostException;
import java.nio.file.Path;
import java.time.Clock;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
@@ -181,6 +184,7 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Files;
import com.google.common.io.Resources;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
@@ -1406,7 +1410,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
try {
final GenesisConfigFile genesisConfigFile = GenesisConfigFile.fromConfig(genesisConfig());
genesisConfigOptions = genesisConfigFile.getConfigOptions(genesisConfigOverrides);
} catch (Exception e) {
} catch (final Exception e) {
throw new IllegalStateException("Unable to read genesis file for GoQuorum options", e);
}
return genesisConfigOptions;
@@ -1502,10 +1506,53 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
.ifPresent(p -> ensureAllNodesAreInAllowlist(staticNodes, p));
metricsConfiguration = metricsConfiguration();
if (isGoQuorumCompatibilityMode) {
configureGoQuorumPrivacy();
}
logger.info("Security Module: {}", securityModuleName);
return this;
}
private void configureGoQuorumPrivacy() {
GoQuorumPrivacyParameters.isEnabled = true;
GoQuorumPrivacyParameters.goQuorumEnclave = createGoQuorumEnclave();
GoQuorumPrivacyParameters.enclaveKey = readEnclaveKey();
}
private GoQuorumEnclave createGoQuorumEnclave() {
final EnclaveFactory enclaveFactory = new EnclaveFactory(Vertx.vertx());
if (privacyKeyStoreFile != null) {
return enclaveFactory.createGoQuorumEnclave(
privacyUrl, privacyKeyStoreFile, privacyKeyStorePasswordFile, privacyTlsKnownEnclaveFile);
} else {
return enclaveFactory.createGoQuorumEnclave(privacyUrl);
}
}
private String readEnclaveKey() {
final String key;
try {
key = Files.asCharSource(privacyPublicKeyFile, UTF_8).read();
} catch (final Exception e) {
throw new ParameterException(
this.commandLine,
"--privacy-public-key-file must be set when --goquorum-compatibility-enabled is set to true.",
e);
}
if (key.length() != 44) {
throw new IllegalArgumentException(
"Contents of enclave public key file needs to be 44 characters long to decode to a valid 32 byte public key.");
}
// throws exception if invalid base 64
Base64.getDecoder().decode(key);
return key;
}
private NetworkName getNetwork() {
// noinspection ConstantConditions network is not always null but injected by
// PicoCLI if used
@@ -1913,7 +1960,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
final OptionalLong qip714BlockNumber = genesisConfigOptions.getQip714BlockNumber();
return Optional.of(
QuorumPermissioningConfiguration.enabled(qip714BlockNumber.orElse(QIP714_DEFAULT_BLOCK)));
} catch (Exception e) {
} catch (final Exception e) {
throw new IllegalStateException("Error reading GoQuorum permissioning options", e);
}
}

View File

@@ -868,7 +868,7 @@ public class BesuCommandTest extends CommandTestAbstract {
}
@Test
public void dataDirOptionMustBeUsed() throws Exception {
public void dataDirOptionMustBeUsed() {
final Path path = Paths.get(".");
parseCommand("--data-path", path.toString());
@@ -913,7 +913,7 @@ public class BesuCommandTest extends CommandTestAbstract {
verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any());
verify(mockControllerBuilder).build();
EthNetworkConfig config = networkArg.getValue();
final EthNetworkConfig config = networkArg.getValue();
assertThat(config.getBootNodes()).isEmpty();
assertThat(config.getDnsDiscoveryUrl()).isNull();
assertThat(config.getNetworkId()).isEqualTo(BigInteger.valueOf(3141592));
@@ -929,14 +929,14 @@ public class BesuCommandTest extends CommandTestAbstract {
verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any());
verify(mockControllerBuilder).build();
EthNetworkConfig config = networkArg.getValue();
final EthNetworkConfig config = networkArg.getValue();
assertThat(config.getBootNodes()).isEqualTo(MAINNET_BOOTSTRAP_NODES);
assertThat(config.getDnsDiscoveryUrl()).isEqualTo(MAINNET_DISCOVERY_URL);
assertThat(config.getNetworkId()).isEqualTo(BigInteger.valueOf(1));
}
@Test
public void testGenesisPathGoerliEthConfig() throws Exception {
public void testGenesisPathGoerliEthConfig() {
final ArgumentCaptor<EthNetworkConfig> networkArg =
ArgumentCaptor.forClass(EthNetworkConfig.class);
@@ -945,14 +945,14 @@ public class BesuCommandTest extends CommandTestAbstract {
verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any());
verify(mockControllerBuilder).build();
EthNetworkConfig config = networkArg.getValue();
final EthNetworkConfig config = networkArg.getValue();
assertThat(config.getBootNodes()).isEqualTo(GOERLI_BOOTSTRAP_NODES);
assertThat(config.getDnsDiscoveryUrl()).isEqualTo(GOERLI_DISCOVERY_URL);
assertThat(config.getNetworkId()).isEqualTo(BigInteger.valueOf(5));
}
@Test
public void testGenesisPathRinkebyEthConfig() throws Exception {
public void testGenesisPathRinkebyEthConfig() {
final ArgumentCaptor<EthNetworkConfig> networkArg =
ArgumentCaptor.forClass(EthNetworkConfig.class);
@@ -961,7 +961,7 @@ public class BesuCommandTest extends CommandTestAbstract {
verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any());
verify(mockControllerBuilder).build();
EthNetworkConfig config = networkArg.getValue();
final EthNetworkConfig config = networkArg.getValue();
assertThat(config.getBootNodes()).isEqualTo(RINKEBY_BOOTSTRAP_NODES);
assertThat(config.getDnsDiscoveryUrl()).isEqualTo(RINKEBY_DISCOVERY_URL);
assertThat(config.getNetworkId()).isEqualTo(BigInteger.valueOf(4));
@@ -1517,7 +1517,7 @@ public class BesuCommandTest extends CommandTestAbstract {
@Test
public void dnsEnabledOptionIsParsedCorrectly() {
TestBesuCommand besuCommand = parseCommand("--Xdns-enabled", "true");
final TestBesuCommand besuCommand = parseCommand("--Xdns-enabled", "true");
assertThat(besuCommand.getEnodeDnsConfiguration().dnsEnabled()).isTrue();
assertThat(besuCommand.getEnodeDnsConfiguration().updateEnabled()).isFalse();
@@ -1525,7 +1525,7 @@ public class BesuCommandTest extends CommandTestAbstract {
@Test
public void dnsUpdateEnabledOptionIsParsedCorrectly() {
TestBesuCommand besuCommand =
final TestBesuCommand besuCommand =
parseCommand("--Xdns-enabled", "true", "--Xdns-update-enabled", "true");
assertThat(besuCommand.getEnodeDnsConfiguration().dnsEnabled()).isTrue();
@@ -3987,10 +3987,44 @@ public class BesuCommandTest extends CommandTestAbstract {
"--genesis-file",
genesisFile.toString(),
"--min-gas-price",
"0");
"0",
"--privacy-public-key-file",
ENCLAVE_PUBLIC_KEY_PATH);
assertThat(commandErrorOutput.toString()).isEmpty();
}
@Test
public void quorumInteropEnabledFailsIfEnclaveKeyFileDoesNotExist() throws IOException {
final Path genesisFile =
createFakeGenesisFile(VALID_GENESIS_QUORUM_INTEROP_ENABLED_WITH_CHAINID);
parseCommand(
"--goquorum-compatibility-enabled",
"--genesis-file",
genesisFile.toString(),
"--min-gas-price",
"0",
"--privacy-public-key-file",
"ThisFileDoesNotExist");
assertThat(commandErrorOutput.toString())
.contains(
"--privacy-public-key-file must be set when --goquorum-compatibility-enabled is set to true.");
}
@Test
public void quorumInteropEnabledFailsIfEnclaveKeyFileIsNotSet() throws IOException {
final Path genesisFile =
createFakeGenesisFile(VALID_GENESIS_QUORUM_INTEROP_ENABLED_WITH_CHAINID);
parseCommand(
"--goquorum-compatibility-enabled",
"--genesis-file",
genesisFile.toString(),
"--min-gas-price",
"0");
assertThat(commandErrorOutput.toString())
.contains(
"--privacy-public-key-file must be set when --goquorum-compatibility-enabled is set to true.");
}
@Test
public void quorumInteropEnabledFailsWithMainnetDefaultNetwork() throws IOException {
final Path genesisFile = createFakeGenesisFile(INVALID_GENESIS_QUORUM_INTEROP_ENABLED_MAINNET);

View File

@@ -48,6 +48,22 @@ public class EnclaveFactory {
return new Enclave(vertxTransmitter);
}
public Enclave createVertxEnclave(
final URI enclaveUri,
final Path privacyKeyStoreFile,
final Path privacyKeyStorePasswordFile,
final Path privacyAllowlistFile) {
final HttpClientOptions clientOptions =
createTlsClientOptions(
enclaveUri, privacyKeyStoreFile, privacyKeyStorePasswordFile, privacyAllowlistFile);
final RequestTransmitter vertxTransmitter =
new VertxRequestTransmitter(vertx.createHttpClient(clientOptions));
return new Enclave(vertxTransmitter);
}
public GoQuorumEnclave createGoQuorumEnclave(final URI enclaveUri) {
final HttpClientOptions clientOptions = createNonTlsClientOptions(enclaveUri);
@@ -115,22 +131,6 @@ public class EnclaveFactory {
return clientOptions;
}
public Enclave createVertxEnclave(
final URI enclaveUri,
final Path privacyKeyStoreFile,
final Path privacyKeyStorePasswordFile,
final Path privacyAllowlistFile) {
final HttpClientOptions clientOptions =
createTlsClientOptions(
enclaveUri, privacyKeyStoreFile, privacyKeyStorePasswordFile, privacyAllowlistFile);
final RequestTransmitter vertxTransmitter =
new VertxRequestTransmitter(vertx.createHttpClient(clientOptions));
return new Enclave(vertxTransmitter);
}
private static PfxOptions convertFrom(final Path keystoreFile, final Path keystorePasswordFile)
throws IOException {
final String password = readSecretFromFile(keystorePasswordFile);

View File

@@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.MultiTenancyUserUtil.enclavePublicKey;
import org.hyperledger.besu.ethereum.core.GoQuorumPrivacyParameters;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import java.util.Optional;
@@ -28,9 +29,12 @@ public interface EnclavePublicKeyProvider {
String getEnclaveKey(Optional<User> user);
static EnclavePublicKeyProvider build(final PrivacyParameters privacyParameters) {
return privacyParameters.isMultiTenancyEnabled()
? multiTenancyEnclavePublicKeyProvider()
: defaultEnclavePublicKeyProvider(privacyParameters);
if (GoQuorumPrivacyParameters.isEnabled) {
return goQuorumEnclavePublicKeyProvider();
} else if (privacyParameters.isMultiTenancyEnabled()) {
return multiTenancyEnclavePublicKeyProvider();
}
return defaultEnclavePublicKeyProvider(privacyParameters);
}
private static EnclavePublicKeyProvider multiTenancyEnclavePublicKeyProvider() {
@@ -44,4 +48,8 @@ public interface EnclavePublicKeyProvider {
final PrivacyParameters privacyParameters) {
return user -> privacyParameters.getEnclavePublicKey();
}
private static EnclavePublicKeyProvider goQuorumEnclavePublicKeyProvider() {
return user -> GoQuorumPrivacyParameters.enclaveKey;
}
}

View File

@@ -0,0 +1,26 @@
/*
* 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.ethereum.core;
import org.hyperledger.besu.enclave.GoQuorumEnclave;
public class GoQuorumPrivacyParameters {
public static boolean isEnabled = false;
public static GoQuorumEnclave goQuorumEnclave;
public static String enclaveKey;
}