Add RPC WS options to specify password file for keystore and truststore (#7970)

* Add RPC WS options to specify password file for keystore and truststore

Signed-off-by: Bhanu Pulluri <bhanu.pulluri@kaleido.io>

* update changelog

Signed-off-by: Bhanu Pulluri <bhanu.pulluri@kaleido.io>

---------

Signed-off-by: Bhanu Pulluri <bhanu.pulluri@kaleido.io>
Signed-off-by: Bhanu Pulluri <59369753+pullurib@users.noreply.github.com>
Co-authored-by: Bhanu Pulluri <bhanu.pulluri@kaleido.io>
Co-authored-by: Fabio Di Fabio <fabio.difabio@consensys.net>
This commit is contained in:
Bhanu Pulluri
2024-12-04 11:53:40 -05:00
committed by GitHub
parent 6a546c5e6f
commit 1b7b6e8025
6 changed files with 129 additions and 24 deletions

View File

@@ -43,9 +43,11 @@
- Add a method to check if a metric category is enabled to the plugin API [#7832](https://github.com/hyperledger/besu/pull/7832)
- Add a new metric collector for counters which get their value from suppliers [#7894](https://github.com/hyperledger/besu/pull/7894)
- Add account and state overrides to `eth_call` [#7801](https://github.com/hyperledger/besu/pull/7801) and `eth_estimateGas` [#7890](https://github.com/hyperledger/besu/pull/7890)
- Add RPC WS options to specify password file for keystore and truststore [#7970](https://github.com/hyperledger/besu/pull/7970)
- Prometheus Java Metrics library upgraded to version 1.3.3 [#7880](https://github.com/hyperledger/besu/pull/7880)
- Add histogram to Prometheus metrics system [#7944](https://github.com/hyperledger/besu/pull/7944)
### Bug fixes
- Fix registering new metric categories from plugins [#7825](https://github.com/hyperledger/besu/pull/7825)
- Fix CVE-2024-47535 [7878](https://github.com/hyperledger/besu/pull/7878)

View File

@@ -37,6 +37,35 @@ import picocli.CommandLine;
/** This class represents the WebSocket options for the RPC. */
public class RpcWebsocketOptions {
static class KeystorePasswordOptions {
@CommandLine.Option(
names = {"--rpc-ws-ssl-keystore-password"},
paramLabel = "<PASSWORD>",
description = "Password for the WebSocket RPC keystore file")
private String rpcWsKeyStorePassword;
@CommandLine.Option(
names = {"--rpc-ws-ssl-keystore-password-file"},
paramLabel = "<FILE>",
description = "File containing the password for WebSocket keystore.")
private String rpcWsKeystorePasswordFile;
}
static class TruststorePasswordOptions {
@CommandLine.Option(
names = {"--rpc-ws-ssl-truststore-password"},
paramLabel = "<PASSWORD>",
description = "Password for the WebSocket RPC truststore file")
private String rpcWsTrustStorePassword;
@CommandLine.Option(
names = {"--rpc-ws-ssl-truststore-password-file"},
paramLabel = "<FILE>",
description = "File containing the password for WebSocket truststore.")
private String rpcWsTruststorePasswordFile;
}
@CommandLine.Option(
names = {"--rpc-ws-authentication-jwt-algorithm"},
description =
@@ -131,12 +160,6 @@ public class RpcWebsocketOptions {
description = "Path to the keystore file for the WebSocket RPC service")
private String rpcWsKeyStoreFile = null;
@CommandLine.Option(
names = {"--rpc-ws-ssl-keystore-password"},
paramLabel = "<PASSWORD>",
description = "Password for the WebSocket RPC keystore file")
private String rpcWsKeyStorePassword = null;
@CommandLine.Option(
names = {"--rpc-ws-ssl-key-file"},
paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP,
@@ -167,12 +190,6 @@ public class RpcWebsocketOptions {
description = "Path to the truststore file for the WebSocket RPC service")
private String rpcWsTrustStoreFile = null;
@CommandLine.Option(
names = {"--rpc-ws-ssl-truststore-password"},
paramLabel = "<PASSWORD>",
description = "Password for the WebSocket RPC truststore file")
private String rpcWsTrustStorePassword = null;
@CommandLine.Option(
names = {"--rpc-ws-ssl-trustcert-file"},
paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP,
@@ -185,6 +202,12 @@ public class RpcWebsocketOptions {
description = "Type of the truststore (JKS, PKCS12, PEM)")
private String rpcWsTrustStoreType = null;
@CommandLine.ArgGroup(exclusive = true, multiplicity = "1")
private KeystorePasswordOptions keystorePasswordOptions;
@CommandLine.ArgGroup(exclusive = true, multiplicity = "1")
private TruststorePasswordOptions truststorePasswordOptions;
/** Default Constructor. */
public RpcWebsocketOptions() {}
@@ -292,7 +315,7 @@ public class RpcWebsocketOptions {
commandLine,
"--rpc-ws-ssl-keystore-file",
rpcWsKeyStoreFile == null,
List.of("--rpc-ws-ssl-keystore-password"));
List.of("--rpc-ws-ssl-keystore-password", "--rpc-ws-ssl-keystore-password-file"));
}
}
@@ -302,7 +325,7 @@ public class RpcWebsocketOptions {
commandLine,
"--rpc-ws-ssl-truststore-file",
rpcWsTrustStoreFile == null,
List.of("--rpc-ws-ssl-truststore-password"));
List.of("--rpc-ws-ssl-truststore-password", "--rpc-ws-ssl-truststore-password-file"));
}
if (isRpcWsAuthenticationEnabled) {
@@ -343,16 +366,27 @@ public class RpcWebsocketOptions {
webSocketConfiguration.setTimeoutSec(wsTimoutSec);
webSocketConfiguration.setSslEnabled(isRpcWsSslEnabled);
webSocketConfiguration.setKeyStorePath(rpcWsKeyStoreFile);
webSocketConfiguration.setKeyStorePassword(rpcWsKeyStorePassword);
webSocketConfiguration.setKeyStoreType(rpcWsKeyStoreType);
webSocketConfiguration.setClientAuthEnabled(isRpcWsClientAuthEnabled);
webSocketConfiguration.setTrustStorePath(rpcWsTrustStoreFile);
webSocketConfiguration.setTrustStorePassword(rpcWsTrustStorePassword);
webSocketConfiguration.setTrustStoreType(rpcWsTrustStoreType);
webSocketConfiguration.setKeyPath(rpcWsKeyFile);
webSocketConfiguration.setCertPath(rpcWsCertFile);
webSocketConfiguration.setTrustCertPath(rpcWsTrustCertFile);
if (keystorePasswordOptions != null) {
webSocketConfiguration.setKeyStorePassword(keystorePasswordOptions.rpcWsKeyStorePassword);
webSocketConfiguration.setKeyStorePasswordFile(
keystorePasswordOptions.rpcWsKeystorePasswordFile);
}
if (truststorePasswordOptions != null) {
webSocketConfiguration.setTrustStorePassword(
truststorePasswordOptions.rpcWsTrustStorePassword);
webSocketConfiguration.setTrustStorePasswordFile(
truststorePasswordOptions.rpcWsTruststorePasswordFile);
}
return webSocketConfiguration;
}

View File

@@ -123,10 +123,12 @@ rpc-ws-authentication-jwt-public-key-file="none"
rpc-ws-ssl-enabled=false
rpc-ws-ssl-keystore-file="none.pfx"
rpc-ws-ssl-keystore-password="none.passwd"
rpc-ws-ssl-keystore-password-file="none.txt"
rpc-ws-ssl-keystore-type="none"
rpc-ws-ssl-client-auth-enabled=false
rpc-ws-ssl-truststore-file="none.pfx"
rpc-ws-ssl-truststore-password="none.passwd"
rpc-ws-ssl-truststore-password-file="none.txt"
rpc-ws-ssl-truststore-type="none"
rpc-ws-ssl-key-file="none.pfx"
rpc-ws-ssl-cert-file="none.pfx"

View File

@@ -20,6 +20,10 @@ import org.hyperledger.besu.ethereum.api.handlers.TimeoutOptions;
import org.hyperledger.besu.ethereum.api.jsonrpc.authentication.JwtAlgorithm;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -54,11 +58,13 @@ public class WebSocketConfiguration {
private Optional<String> keyStorePath = Optional.empty();
private Optional<String> keyStorePassword = Optional.empty();
private Optional<String> keyStoreType = Optional.of("JKS"); // Default to JKS
private Optional<String> keyStorePasswordFile = Optional.empty();
private boolean clientAuthEnabled = false;
private Optional<String> trustStorePath = Optional.empty();
private Optional<String> trustStorePassword = Optional.empty();
private Optional<String> trustStoreType = Optional.of("JKS"); // Default to JKS
private Optional<String> trustStorePasswordFile = Optional.empty();
// For PEM format
private Optional<String> keyPath = Optional.empty();
@@ -191,8 +197,11 @@ public class WebSocketConfiguration {
this.keyStorePath = Optional.ofNullable(keyStorePath);
}
public Optional<String> getKeyStorePassword() {
return keyStorePassword;
public Optional<String> getKeyStorePassword() throws IOException {
if (keyStorePassword.isPresent()) {
return keyStorePassword;
}
return Optional.ofNullable(getKeystorePasswordFromFile());
}
public void setKeyStorePassword(final String keyStorePassword) {
@@ -245,8 +254,11 @@ public class WebSocketConfiguration {
}
// Truststore Password
public Optional<String> getTrustStorePassword() {
return trustStorePassword;
public Optional<String> getTrustStorePassword() throws IOException {
if (trustStorePassword.isPresent()) {
return trustStorePassword;
}
return Optional.ofNullable(getTruststorePasswordFromFile());
}
public void setTrustStorePassword(final String trustStorePassword) {
@@ -258,6 +270,38 @@ public class WebSocketConfiguration {
return trustStoreType;
}
public void setKeyStorePasswordFile(final String keyStorePasswordFile) {
this.keyStorePasswordFile = Optional.ofNullable(keyStorePasswordFile);
}
public void setTrustStorePasswordFile(final String trustStorePasswordFile) {
this.trustStorePasswordFile = Optional.ofNullable(trustStorePasswordFile);
}
private String loadPasswordFromFile(final String passwordFile) throws IOException {
if (passwordFile != null) {
Path path = Path.of(passwordFile);
if (Files.exists(path)) {
return Files.readString(path, StandardCharsets.UTF_8).trim();
}
}
return null;
}
public String getKeystorePasswordFromFile() throws IOException {
if (keyStorePasswordFile.isPresent()) {
return loadPasswordFromFile(keyStorePasswordFile.get());
}
return null;
}
public String getTruststorePasswordFromFile() throws IOException {
if (trustStorePasswordFile.isPresent()) {
return loadPasswordFromFile(trustStorePasswordFile.get());
}
return null;
}
public void setTrustStoreType(final String trustStoreType) {
this.trustStoreType = Optional.ofNullable(trustStoreType);
}

View File

@@ -122,9 +122,16 @@ public class WebSocketService {
// Check if SSL/TLS is enabled in the configuration
if (configuration.isSslEnabled()) {
serverOptions.setSsl(true);
String keystorePassword = null;
String keystorePath = configuration.getKeyStorePath().orElse(null);
String keystorePassword = configuration.getKeyStorePassword().orElse(null);
try {
keystorePassword = configuration.getKeyStorePassword().orElse(null);
} catch (Exception e) {
LOG.error("Error reading keystore password", e);
resultFuture.completeExceptionally(e);
return resultFuture;
}
String keyPath = configuration.getKeyPath().orElse(null);
String certPath = configuration.getCertPath().orElse(null);
@@ -146,9 +153,16 @@ public class WebSocketService {
// Set up truststore for client authentication (mTLS)
if (configuration.isClientAuthEnabled()) {
serverOptions.setClientAuth(ClientAuth.REQUIRED);
String truststorePassword;
String truststorePath = configuration.getTrustStorePath().orElse(null);
String truststorePassword = configuration.getTrustStorePassword().orElse("");
try {
truststorePassword = configuration.getTrustStorePassword().orElse(null);
} catch (Exception e) {
LOG.error("Error reading truststore password", e);
resultFuture.completeExceptionally(e);
return resultFuture;
}
String truststoreType = configuration.getTrustStoreType().orElse("JKS");
String trustCertPath = configuration.getTrustCertPath().orElse(null);

View File

@@ -30,6 +30,9 @@ import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
import java.io.File;
import java.io.FileOutputStream;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.security.KeyStore;
import java.util.Base64;
import java.util.HashMap;
@@ -390,14 +393,20 @@ public class WebSocketServiceTLSTest {
clientTrustStore.store(fos, "password".toCharArray());
}
File tempFile = File.createTempFile("pwdfile", ".txt");
tempFile.deleteOnExit();
try (Writer writer = Files.newBufferedWriter(tempFile.toPath(), Charset.defaultCharset())) {
writer.write("password");
}
// Configure WebSocket with SSL and client authentication enabled
config.setSslEnabled(true);
config.setKeyStorePath(serverKeystoreFile.getAbsolutePath());
config.setKeyStorePassword("password");
config.setKeyStorePasswordFile(tempFile.getAbsolutePath());
config.setKeyStoreType("PKCS12");
config.setClientAuthEnabled(true);
config.setTrustStorePath(serverTruststoreFile.getAbsolutePath());
config.setTrustStorePassword("password");
config.setTrustStorePasswordFile(tempFile.getAbsolutePath());
config.setTrustStoreType("PKCS12");
// Create and start WebSocketService