Skip to content

Commit

Permalink
JSON-RPC clients: prefer SSLContext over SSLSocketFactory
Browse files Browse the repository at this point in the history
As a step towards migration to the java.net.http JSON-RPC client, we
need to migrate our SSL configuration from using SSLSocketFactory to
using SSLContext.  (SSLContext can return an SSLSocketFactory, but
java.net.http.HttpClient needs to be configured with an SSLContext)

Provide deprecated constructors for AbstractRpcClient subclasses that
had used SSLSocketFactory, but migrate the CLI-related classes in
a breaking way.
  • Loading branch information
msgilligan committed Jul 27, 2023
1 parent b99796b commit e383af5
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
import org.consensusj.jsonrpc.JsonRpcException;
import org.consensusj.jsonrpc.cli.BaseJsonRpcTool;

import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.SSLContext;
import java.io.PrintWriter;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.NoSuchAlgorithmException;

import static org.consensusj.bitcoin.jsonrpc.RpcURI.RPCPORT_REGTEST;
import static org.consensusj.bitcoin.jsonrpc.RpcURI.RPCPORT_TESTNET;
Expand Down Expand Up @@ -69,12 +70,12 @@ public BitcoinCLICall(BitcoinCLITool tool, PrintWriter out, PrintWriter err, Str
}

@Override
public BitcoinClient rpcClient(SSLSocketFactory sslSocketFactory) {
public BitcoinClient rpcClient(SSLContext sslContext) {
// Not threadsafe
// This needs work if there are ever going to be multiple clients calling this method
if (client == null) {
RpcConfig config = getRPCConfig();
client = createClient(sslSocketFactory, config);
client = createClient(sslContext, config);
if (rpcWait) {
// TODO: Add logging here to replace the System.out.println
//System.out.println("Connecting to: " + getRPCConfig().getURI() + " with -rpcWait");
Expand All @@ -96,16 +97,22 @@ public BitcoinClient rpcClient(SSLSocketFactory sslSocketFactory) {

@Override
public BitcoinClient rpcClient() {
return rpcClient((SSLSocketFactory) SSLSocketFactory.getDefault());
SSLContext sslContext;
try {
sslContext = SSLContext.getDefault();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
return rpcClient(sslContext);
}

/**
* Override this method to customize the client implementation subclass
* @param config Configuration information for connecting to server
* @return a newly constructed BitcoinClient with specified config
*/
protected BitcoinClient createClient(SSLSocketFactory sslSocketFactory, RpcConfig config) {
return new BitcoinClient( sslSocketFactory, config.network(),
protected BitcoinClient createClient(SSLContext sslContext, RpcConfig config) {
return new BitcoinClient( sslContext, config.network(),
config.getURI(),
config.getUsername(),
config.getPassword());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,14 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import java.io.EOFException;
import java.io.IOException;
import java.net.SocketException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -118,6 +120,20 @@ public class BitcoinClient extends JsonRpcClientHttpUrlConnection implements Cha
private boolean isAddressIndexSuccessfullyTested = false;
private boolean isAddressIndexEnabled;

public BitcoinClient(SSLContext sslContext, Network network, URI server, String rpcuser, String rpcpassword) {
super(sslContext, JsonRpcMessage.Version.V2, server, rpcuser, rpcpassword);
this.network = network;
ThreadFactory threadFactory = new BitcoinClientThreadFactory(new Context(), "Bitcoin RPC Client");
// TODO: Tune and/or make configurable the thread pool size.
// Current pool size of 5 is chosen to minimize simultaneous active RPC
// calls in `bitcoind` -- which is not designed for serving multiple clients.
executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE, threadFactory);
if (network != null) {
initMapper();
}
}

@Deprecated
public BitcoinClient(SSLSocketFactory sslSocketFactory, Network network, URI server, String rpcuser, String rpcpassword) {
super(sslSocketFactory, JsonRpcMessage.Version.V2, server, rpcuser, rpcpassword);
this.network = network;
Expand Down Expand Up @@ -149,13 +165,13 @@ private void initMapper() {
* <p>
* When using this constructor, it is recommended that {@link #getNetwork()} be called after construction
* and before any other methods are called, to allow the Bitcoin network type to be initialized.
* @param sslSocketFactory Custom socket factory
* @param sslContext Custom socket factory
* @param server URI of the Bitcoin RPC server
* @param rpcuser Username (if required)
* @param rpcpassword Password (if required)
*/
public BitcoinClient(SSLSocketFactory sslSocketFactory, URI server, String rpcuser, String rpcpassword) {
this(sslSocketFactory, (Network) null, server, rpcuser, rpcpassword);
public BitcoinClient(SSLContext sslContext, URI server, String rpcuser, String rpcpassword) {
this(sslContext, (Network) null, server, rpcuser, rpcpassword);
}

/**
Expand All @@ -168,7 +184,7 @@ public BitcoinClient(SSLSocketFactory sslSocketFactory, URI server, String rpcus
* @param rpcpassword Password (if required)
*/
public BitcoinClient(URI server, String rpcuser, String rpcpassword) {
this((SSLSocketFactory) SSLSocketFactory.getDefault(), (Network) null, server, rpcuser, rpcpassword);
this(getDefaultSSLContext(), (Network) null, server, rpcuser, rpcpassword);
}

/**
Expand All @@ -179,7 +195,7 @@ public BitcoinClient(URI server, String rpcuser, String rpcpassword) {
* @param rpcpassword Password (if required)
*/
public BitcoinClient(Network network, URI server, String rpcuser, String rpcpassword) {
this((SSLSocketFactory) SSLSocketFactory.getDefault(), network, server, rpcuser, rpcpassword);
this(getDefaultSSLContext(), network, server, rpcuser, rpcpassword);
}

@Deprecated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.math.BigInteger;
Expand Down Expand Up @@ -69,6 +70,12 @@ public Integer getDefaultMaxConf() {
return defaultMaxConf;
}


public BitcoinExtendedClient(SSLContext sslContext, Network network, URI server, String rpcuser, String rpcpassword) {
super(sslContext, network, server, rpcuser, rpcpassword);
}

@Deprecated
public BitcoinExtendedClient(SSLSocketFactory sslSocketFactory, Network network, URI server, String rpcuser, String rpcpassword) {
super(sslSocketFactory, network, server, rpcuser, rpcpassword);
}
Expand All @@ -79,16 +86,16 @@ public BitcoinExtendedClient(SSLSocketFactory sslSocketFactory, NetworkParameter
}

public BitcoinExtendedClient(Network network, URI server, String rpcuser, String rpcpassword) {
this((SSLSocketFactory) SSLSocketFactory.getDefault(), network, server, rpcuser, rpcpassword);
this(getDefaultSSLContext(), network, server, rpcuser, rpcpassword);
}

@Deprecated
public BitcoinExtendedClient(NetworkParameters netParams, URI server, String rpcuser, String rpcpassword) {
this((SSLSocketFactory) SSLSocketFactory.getDefault(), netParams, server, rpcuser, rpcpassword);
this(getDefaultSSLContext(), netParams.network(), server, rpcuser, rpcpassword);
}

public BitcoinExtendedClient(URI server, String rpcuser, String rpcpassword) {
this((SSLSocketFactory) SSLSocketFactory.getDefault(), (Network) null, server, rpcuser, rpcpassword);
this(getDefaultSSLContext(), (Network) null, server, rpcuser, rpcpassword);
}

public BitcoinExtendedClient(RpcConfig config) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.SSLContext;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
Expand Down Expand Up @@ -85,7 +85,7 @@ public void run(CommonsCLICall call) {
if (call.line.hasOption("V1")) {
jsonRpcVersion = JsonRpcMessage.Version.V1;
}
SSLSocketFactory sslSocketFactory = socketFactory(call.line);
SSLContext sslSocketFactory = sslContext(call.line);
AbstractRpcClient client = call.rpcClient(sslSocketFactory);
CliParameterParser parser = new CliParameterParser(jsonRpcVersion, client.getMapper());
JsonRpcRequest request = parser.parse(args);
Expand All @@ -103,14 +103,14 @@ public void run(CommonsCLICall call) {
call.out.println(resultForPrinting);
}

SSLSocketFactory socketFactory(CommandLine line) {
SSLSocketFactory sslSocketFactory;
SSLContext sslContext(CommandLine line) {
SSLContext sslContext;
if (line.hasOption("add-truststore")) {
// Create SSL sockets using additional truststore and CompositeTrustManager
String trustStorePathString = line.getOptionValue("add-truststore");
Path trustStorePath = Path.of(trustStorePathString);
try {
sslSocketFactory = CompositeTrustManager.getCompositeSSLSocketFactory(trustStorePath);
sslContext = CompositeTrustManager.getCompositeSSLContext(trustStorePath);
} catch (NoSuchAlgorithmException | KeyManagementException | FileNotFoundException e) {
throw new ToolException(1, e.getMessage());
}
Expand All @@ -119,15 +119,19 @@ SSLSocketFactory socketFactory(CommandLine line) {
String trustStorePathString = line.getOptionValue("alt-truststore");
Path trustStorePath = Path.of(trustStorePathString);
try {
sslSocketFactory = CompositeTrustManager.getAlternateSSLSocketFactory(trustStorePath);
sslContext = CompositeTrustManager.getAlternateSSLContext(trustStorePath);
} catch (NoSuchAlgorithmException | KeyManagementException | CertificateException | KeyStoreException | IOException e) {
throw new ToolException(1, e.getMessage());
}
} else {
// Otherwise, use the default SSLSocketFactory
sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
try {
sslContext = SSLContext.getDefault();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
return sslSocketFactory;
return sslContext;
}

private String formatResponse(JsonRpcResponse<?> response, ObjectMapper mapper) {
Expand Down Expand Up @@ -200,7 +204,7 @@ public CommonsCLICall(BaseJsonRpcTool parentTool, PrintWriter out, PrintWriter e
}

@Override
public AbstractRpcClient rpcClient(SSLSocketFactory sslSocketFactory) {
public AbstractRpcClient rpcClient(SSLContext sslContext) {
if (client == null) {
URI uri;
String urlString;
Expand All @@ -221,14 +225,20 @@ public AbstractRpcClient rpcClient(SSLSocketFactory sslSocketFactory) {
rpcUser = split[0];
rpcPassword = split[1];
}
client = new JsonRpcClientHttpUrlConnection(sslSocketFactory, rpcTool.jsonRpcVersion, uri, rpcUser, rpcPassword);
client = new JsonRpcClientHttpUrlConnection(sslContext, rpcTool.jsonRpcVersion, uri, rpcUser, rpcPassword);
}
return client;
}

@Override
public AbstractRpcClient rpcClient() {
return rpcClient((SSLSocketFactory) SSLSocketFactory.getDefault());
SSLContext sslContext;
try {
sslContext = SSLContext.getDefault();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
return rpcClient(sslContext);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.consensusj.jsonrpc.AbstractRpcClient;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
Expand Down Expand Up @@ -52,7 +53,7 @@ public Call(PrintWriter out, PrintWriter err, String[] args) {
}

abstract public AbstractRpcClient rpcClient();
abstract public AbstractRpcClient rpcClient(SSLSocketFactory sslSocketFactory);
abstract public AbstractRpcClient rpcClient(SSLContext sslContext);
}

class ToolException extends RuntimeException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;

import javax.net.ssl.SSLContext;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

// TODO: Rather than implementing transport (HttpUrlConnection vs. java.net.http) with subclasses use composition
Expand Down Expand Up @@ -77,6 +80,18 @@ public JavaType getDefaultType() {
return defaultType;
}

/**
* Return the default {@link SSLContext} without declaring a checked exception
* @return The default {@code SSLContext}
*/
protected static SSLContext getDefaultSSLContext() {
try {
return SSLContext.getDefault();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}

/**
* Encode username password as Base64 for basic authentication
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,42 +62,74 @@ public CompositeTrustManager(Path trustStorePath) throws FileNotFoundException {
this(new FileInputStream(trustStorePath.toFile()));
}

/**
* Used to create an SSLSocketFactory using {@link CompositeTrustManager}
* See: https://stackoverflow.com/questions/859111/how-can-i-use-different-certificates-on-specific-connections
*/
@Deprecated
public static SSLSocketFactory getCompositeSSLSocketFactory(Path trustStorePath) throws NoSuchAlgorithmException, KeyManagementException, FileNotFoundException {
TrustManager tm = new CompositeTrustManager(trustStorePath);
return getSocketFactory(tm);
}


@Deprecated
public static SSLSocketFactory getCompositeSSLSocketFactory(InputStream trustStoreStream) throws NoSuchAlgorithmException, KeyManagementException {
TrustManager tm = new CompositeTrustManager(trustStoreStream);
return getSocketFactory(tm);
}

/**
* Create an SSLSocketFactory using an alternative Trust Store
*/
@Deprecated
public static SSLSocketFactory getAlternateSSLSocketFactory(Path trustStorePath) throws KeyStoreException, IOException, NoSuchAlgorithmException, KeyManagementException, CertificateException {
return getAlternateSSLSocketFactory(new FileInputStream(trustStorePath.toFile()));
}

@Deprecated
public static SSLSocketFactory getAlternateSSLSocketFactory(InputStream trustStoreStream) throws KeyStoreException, IOException, NoSuchAlgorithmException, KeyManagementException, CertificateException {
X509TrustManager trustManager = getCustomTrustManager(trustStoreStream);
return getSocketFactory( trustManager );
}

/**
*
* Used to create an SSLContext using {@link CompositeTrustManager}
* See: https://stackoverflow.com/questions/859111/how-can-i-use-different-certificates-on-specific-connections
*/
public static SSLContext getCompositeSSLContext(Path trustStorePath) throws NoSuchAlgorithmException, KeyManagementException, FileNotFoundException {
TrustManager tm = new CompositeTrustManager(trustStorePath);
return getSSLContext(tm);
}


/**
* Used to create an SSLContext using {@link CompositeTrustManager}
* See: https://stackoverflow.com/questions/859111/how-can-i-use-different-certificates-on-specific-connections
*/
public static SSLContext getCompositeSSLContext(InputStream trustStoreStream) throws NoSuchAlgorithmException, KeyManagementException {
TrustManager tm = new CompositeTrustManager(trustStoreStream);
return getSSLContext(tm);
}

/**
* Create an SSLContext using an alternative Trust Store
*/
public static SSLContext getAlternateSSLContext(Path trustStorePath) throws KeyStoreException, IOException, NoSuchAlgorithmException, KeyManagementException, CertificateException {
return getAlternateSSLContext(new FileInputStream(trustStorePath.toFile()));
}

/**
* Create an SSLContext using an alternative Trust Store
* @param trustStoreStream inputStream with Trust Store (password must be 'changeit')
* @return
*/
public static SSLSocketFactory getAlternateSSLSocketFactory(InputStream trustStoreStream) throws KeyStoreException, IOException, NoSuchAlgorithmException, KeyManagementException, CertificateException {
public static SSLContext getAlternateSSLContext(InputStream trustStoreStream) throws KeyStoreException, IOException, NoSuchAlgorithmException, KeyManagementException, CertificateException {
X509TrustManager trustManager = getCustomTrustManager(trustStoreStream);
return getSocketFactory( trustManager );
return getSSLContext( trustManager );
}

@Deprecated
private static SSLSocketFactory getSocketFactory(TrustManager trustManager) throws NoSuchAlgorithmException, KeyManagementException {
return getSSLContext(trustManager).getSocketFactory();
}

private static SSLContext getSSLContext(TrustManager trustManager) throws NoSuchAlgorithmException, KeyManagementException {
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, new TrustManager[] {trustManager}, null);
return ctx.getSocketFactory();
return ctx;
}

@Override
Expand Down
Loading

0 comments on commit e383af5

Please sign in to comment.