Skip to content

Commit

Permalink
Add RPC WS options to specify password file for keystore and truststo…
Browse files Browse the repository at this point in the history
…re (#7970)

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

Signed-off-by: Bhanu Pulluri <[email protected]>

* update changelog

Signed-off-by: Bhanu Pulluri <[email protected]>

---------

Signed-off-by: Bhanu Pulluri <[email protected]>
Signed-off-by: Bhanu Pulluri <[email protected]>
Co-authored-by: Bhanu Pulluri <[email protected]>
Co-authored-by: Fabio Di Fabio <[email protected]>
  • Loading branch information
3 people authored Dec 4, 2024
1 parent 6a546c5 commit 1b7b6e8
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 24 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,35 @@

/** 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 =
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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() {}

Expand Down Expand Up @@ -292,7 +315,7 @@ private void checkOptionDependencies(final Logger logger, final CommandLine comm
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"));
}
}

Expand All @@ -302,7 +325,7 @@ private void checkOptionDependencies(final Logger logger, final CommandLine comm
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) {
Expand Down Expand Up @@ -343,16 +366,27 @@ public WebSocketConfiguration webSocketConfiguration(
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;
}

Expand Down
2 changes: 2 additions & 0 deletions besu/src/test/resources/everything_config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -191,8 +197,11 @@ public void setKeyStorePath(final String keyStorePath) {
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) {
Expand Down Expand Up @@ -245,8 +254,11 @@ public void setTrustStorePath(final String trustStorePath) {
}

// 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) {
Expand All @@ -258,6 +270,38 @@ public Optional<String> getTrustStoreType() {
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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,16 @@ public CompletableFuture<?> start() {
// 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);

Expand All @@ -146,9 +153,16 @@ public CompletableFuture<?> start() {
// 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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@

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;
Expand Down Expand Up @@ -390,14 +393,20 @@ public void shouldAuthenticateClient(final VertxTestContext testContext) throws
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
Expand Down

0 comments on commit 1b7b6e8

Please sign in to comment.