Skip to content

Commit

Permalink
[CORDA:936]: Enable RPC layer to work with SSL
Browse files Browse the repository at this point in the history
  • Loading branch information
sollecitom authored Jan 23, 2018
1 parent 70f1fde commit 142f52f
Show file tree
Hide file tree
Showing 55 changed files with 1,563 additions and 366 deletions.
15 changes: 0 additions & 15 deletions .idea/runConfigurations/Explorer___demo_nodes.xml

This file was deleted.

15 changes: 0 additions & 15 deletions .idea/runConfigurations/Explorer___demo_nodes__simulation_.xml

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,15 @@ class NodeMonitorModelTest {
vaultUpdates = monitor.vaultUpdates.bufferUntilSubscribed()
networkMapUpdates = monitor.networkMap.bufferUntilSubscribed()

monitor.register(aliceNodeHandle.configuration.rpcAddress!!, cashUser.username, cashUser.password)
monitor.register(aliceNodeHandle.configuration.rpcOptions.address!!, cashUser.username, cashUser.password)
rpc = monitor.proxyObservable.value!!
notaryParty = defaultNotaryIdentity

val bobNodeHandle = startNode(providedName = BOB_NAME, rpcUsers = listOf(cashUser)).getOrThrow()
bobNode = bobNodeHandle.nodeInfo
val monitorBob = NodeMonitorModel()
stateMachineUpdatesBob = monitorBob.stateMachineUpdates.bufferUntilSubscribed()
monitorBob.register(bobNodeHandle.configuration.rpcAddress!!, cashUser.username, cashUser.password)
monitorBob.register(bobNodeHandle.configuration.rpcOptions.address!!, cashUser.username, cashUser.password)
rpcBob = monitorBob.proxyObservable.value!!
runTest()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ private void login(String username, String password) {
@Before
public void setUp() throws Exception {
node = startNode(ALICE_NAME, 1, singletonList(rpcUser));
client = new CordaRPCClient(requireNonNull(node.getInternals().getConfiguration().getRpcAddress()));
client = new CordaRPCClient(requireNonNull(node.getInternals().getConfiguration().getRpcOptions().getAddress()));
}

@After
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
@Before
fun setUp() {
node = startNode(ALICE_NAME, rpcUsers = listOf(rpcUser))
client = CordaRPCClient(node.internals.configuration.rpcAddress!!)
client = CordaRPCClient(node.internals.configuration.rpcOptions.address!!)
identity = node.info.identityFromX500Name(ALICE_NAME)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import net.corda.core.serialization.internal.effectiveSerializationEnv
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.nodeapi.ArtemisTcpTransport.Companion.tcpTransport
import net.corda.nodeapi.ConnectionDirection
import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.serialization.KRYO_RPC_CLIENT_CONTEXT
import java.time.Duration

Expand Down Expand Up @@ -67,10 +68,12 @@ data class CordaRPCClientConfiguration(val connectionMaxRetryInterval: Duration)
*
* @param hostAndPort The network address to connect to.
* @param configuration An optional configuration used to tweak client behaviour.
* @param sslConfiguration An optional [SSLConfiguration] used to enable secure communication with the server.
*/
class CordaRPCClient @JvmOverloads constructor(
hostAndPort: NetworkHostAndPort,
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
sslConfiguration: SSLConfiguration? = null
) {
init {
try {
Expand All @@ -85,7 +88,7 @@ class CordaRPCClient @JvmOverloads constructor(
}

private val rpcClient = RPCClient<CordaRPCOps>(
tcpTransport(ConnectionDirection.Outbound(), hostAndPort, config = null),
tcpTransport(ConnectionDirection.Outbound(), hostAndPort, config = sslConfiguration),
configuration.toRpcClientConfiguration(),
KRYO_RPC_CLIENT_CONTEXT
)
Expand Down
14 changes: 14 additions & 0 deletions docs/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ from the previous milestone release.

UNRELEASED
----------
* Separated our pre-existing Artemis broker into an RPC broker and a P2P broker.

* Refactored ``NodeConfiguration`` to expose ``NodeRpcOptions`` (using top-level "rpcAddress" property still works with warning).

* Modified ``CordaRPCClient`` constructor to take a ``SSLConfiguration?`` additional parameter, defaulted to ``null``.

* Introduced ``CertificateChainCheckPolicy.UsernameMustMatchCommonName`` sub-type, allowing customers to optionally enforce username == CN condition on RPC SSL certificates.

* Modified ``DriverDSL`` and sub-types to allow specifying RPC settings for the Node.

* Modified the ``DriverDSL`` to start Cordformation nodes allowing automatic generation of "rpcSettings.adminAddress" in case "rcpSettings.useSsl" is ``false`` (the default).

* Introduced ``UnsafeCertificatesFactory`` allowing programmatic generation of X509 certificates for test purposes.

* JPA Mapping annotations for States extending ``CommonSchemaV1.LinearState`` and ``CommonSchemaV1.FungibleState`` on the
`participants` collection need to be moved to the actual class. This allows to properly specify the unique table name per a collection.
See: DummyDealStateSchemaV1.PersistentDummyDealState
Expand Down
5 changes: 5 additions & 0 deletions docs/source/clientrpc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,11 @@ Wire protocol
-------------
The client RPC wire protocol is defined and documented in ``net/corda/client/rpc/RPCApi.kt``.

Wire security
-------------
``CordaRPCClient`` has an optional constructor parameter of type ``SSLConfiguration``, defaulted to ``null``, which allows
communication with the node using SSL. Default ``null`` value means no SSL used in the context of RPC.

Whitelisting classes with the Corda node
----------------------------------------
CorDapps must whitelist any classes used over RPC with Corda's serialization framework, unless they are whitelisted by
Expand Down
23 changes: 21 additions & 2 deletions docs/source/corda-configuration-file.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,12 @@ Simple Notary configuration file.
keyStorePassword : "cordacadevpass"
trustStorePassword : "trustpass"
p2pAddress : "localhost:12345"
rpcAddress : "localhost:12346"
rpcSettings = {
useSsl = false
standAloneBroker = false
address : "my-corda-node:10003"
adminAddress : "my-corda-node:10004"
}
webAddress : "localhost:12347"
notary : {
validating : false
Expand Down Expand Up @@ -87,7 +92,21 @@ path to the node's base directory.
here must be externally accessible when running nodes across a cluster of machines. If the provided host is unreachable,
the node will try to auto-discover its public one.

:rpcAddress: The address of the RPC system on which RPC requests can be made to the node. If not provided then the node will run without RPC.
:rpcAddress: The address of the RPC system on which RPC requests can be made to the node. If not provided then the node will run without RPC. This is now deprecated in favour of the ``rpcSettings`` block.

:rpcSettings: Options for the RPC server.

:useSsl: (optional) boolean, indicates whether the node should require clients to use SSL for RPC connections, defaulted to ``false``.
:standAloneBroker: (optional) boolean, indicates whether the node will connect to a standalone broker for RPC, defaulted to ``false``.
:address: (optional) host and port for the RPC server binding, if any.
:adminAddress: (optional) host and port for the RPC admin binding (only required when ``useSsl`` is ``false``, because the node connects to Artemis using SSL to ensure admin privileges are not accessible outside the node).
:ssl: (optional) SSL settings for the RPC server.

:keyStorePassword: password for the key store.
:trustStorePassword: password for the trust store.
:certificatesDirectory: directory in which the stores will be searched, unless absolute paths are provided.
:sslKeystore: absolute path to the ssl key store, defaulted to ``certificatesDirectory / "sslkeystore.jks"``.
:trustStoreFile: absolute path to the trust store, defaulted to ``certificatesDirectory / "truststore.jks"``.

:security: Contains various nested fields controlling user authentication/authorization, in particular for RPC accesses. See
:doc:`clientrpc` for details.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ dataSourceProperties : {
"dataSource.password" : ""
}
p2pAddress : "my-corda-node:10002"
rpcAddress : "my-corda-node:10003"
rpcSettings = {
useSsl = false
standAloneBroker = false
address : "my-corda-node:10003"
adminAddress : "my-corda-node:10004"
}
webAddress : "localhost:10004"
rpcUsers : [
{ username=user1, password=letmein, permissions=[ StartFlow.net.corda.protocols.CashProtocol ] }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,22 @@ fun SSLConfiguration.createDevKeyStores(legalName: CordaX500Name,
intermediateCa: CertificateAndKeyPair = DEV_INTERMEDIATE_CA) {
val (nodeCaCert, nodeCaKeyPair) = createDevNodeCa(intermediateCa, legalName)

loadOrCreateKeyStore(nodeKeystore, keyStorePassword).apply {
addOrReplaceKey(
X509Utilities.CORDA_CLIENT_CA,
nodeCaKeyPair.private,
keyStorePassword.toCharArray(),
arrayOf(nodeCaCert, intermediateCa.certificate, rootCert))
save(nodeKeystore, keyStorePassword)
}
createDevKeyStores(rootCert, intermediateCa, nodeCaCert, nodeCaKeyPair, legalName)
}

/**
* Create the node and SSL key stores needed by a node. The node key store will be populated with a node CA cert (using
* the given legal name), and the SSL key store will store the TLS cert which is a sub-cert of the node CA.
*/
fun SSLConfiguration.createDevKeyStores(rootCert: X509Certificate, intermediateCa: CertificateAndKeyPair, nodeCaCert: X509Certificate, nodeCaKeyPair: KeyPair, legalName: CordaX500Name) {
createNodeKeyStore(nodeCaCert, nodeCaKeyPair, intermediateCa, rootCert)
createSslKeyStore(nodeCaCert, nodeCaKeyPair, legalName, intermediateCa, rootCert)
}

/**
* Create the SSL key store needed by a node.
*/
fun SSLConfiguration.createSslKeyStore(nodeCaCert: X509Certificate, nodeCaKeyPair: KeyPair, legalName: CordaX500Name, intermediateCa: CertificateAndKeyPair, rootCert: X509Certificate) {
val tlsKeyPair = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, nodeCaCert, nodeCaKeyPair, legalName.x500Principal, tlsKeyPair.public)

Expand All @@ -45,6 +52,20 @@ fun SSLConfiguration.createDevKeyStores(legalName: CordaX500Name,
}
}

/**
* Create the node key store needed by a node.
*/
fun SSLConfiguration.createNodeKeyStore(nodeCaCert: X509Certificate, nodeCaKeyPair: KeyPair, intermediateCa: CertificateAndKeyPair, rootCert: X509Certificate) {
loadOrCreateKeyStore(nodeKeystore, keyStorePassword).apply {
addOrReplaceKey(
X509Utilities.CORDA_CLIENT_CA,
nodeCaKeyPair.private,
keyStorePassword.toCharArray(),
arrayOf(nodeCaCert, intermediateCa.certificate, rootCert))
save(nodeKeystore, keyStorePassword)
}
}

fun createDevNetworkMapCa(rootCa: CertificateAndKeyPair = DEV_ROOT_CA): CertificateAndKeyPair {
val keyPair = generateKeyPair()
val cert = X509Utilities.createCertificate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ operator fun <T : Any> Config.getValue(receiver: Any, metadata: KProperty<*>): T
}

fun <T : Any> Config.parseAs(clazz: KClass<T>): T {
require(clazz.isData) { "Only Kotlin data classes can be parsed" }
require(clazz.isData) { "Only Kotlin data classes can be parsed. Offending: ${clazz.qualifiedName}" }
val constructor = clazz.primaryConstructor!!
val args = constructor.parameters
.filterNot { it.isOptional && !hasPath(it.name!!) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package net.corda.nodeapi.internal.crypto

import net.corda.core.crypto.Crypto
import net.corda.core.internal.createDirectories
import net.corda.core.internal.exists
import net.corda.core.internal.read
import net.corda.core.internal.write
Expand Down Expand Up @@ -30,6 +31,7 @@ fun loadOrCreateKeyStore(keyStoreFilePath: Path, storePassword: String): KeyStor
keyStoreFilePath.read { keyStore.load(it, pass) }
} else {
keyStore.load(null, pass)
keyStoreFilePath.parent.createDirectories()
keyStoreFilePath.write { keyStore.store(it, pass) }
}
return keyStore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ class AuthDBTests : NodeBasedTest() {
)

node = startNode(ALICE_NAME, rpcUsers = emptyList(), configOverrides = securityConfig)
client = CordaRPCClient(node.internals.configuration.rpcAddress!!)
client = CordaRPCClient(node.internals.configuration.rpcOptions.address!!)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,6 @@ class AMQPBridgeTest {
artemisServer.stop()
artemisLegacyClient.stop()
artemisLegacyServer.stop()

}

private fun createArtemis(sourceQueueName: String?): Pair<ArtemisMessagingServer, ArtemisMessagingClient> {
Expand All @@ -170,6 +169,7 @@ class AMQPBridgeTest {
doReturn(ALICE_NAME).whenever(it).myLegalName
doReturn("trustpass").whenever(it).trustStorePassword
doReturn("cordacadevpass").whenever(it).keyStorePassword
doReturn(artemisAddress).whenever(it).p2pAddress
doReturn("").whenever(it).exportJMXto
doReturn(emptyList<CertChainPolicyConfig>()).whenever(it).certificateChainCheckPolicies
doReturn(true).whenever(it).useAMQPBridges
Expand All @@ -180,7 +180,7 @@ class AMQPBridgeTest {
doReturn(listOf(NodeInfo(listOf(amqpAddress), listOf(BOB.identity), 1, 1L))).whenever(it).getNodesByOwningKeyIndex(any())
}
val userService = rigorousMock<RPCSecurityManager>()
val artemisServer = ArtemisMessagingServer(artemisConfig, artemisPort, null, networkMap, userService, MAX_MESSAGE_SIZE)
val artemisServer = ArtemisMessagingServer(artemisConfig, artemisPort, networkMap, userService, MAX_MESSAGE_SIZE)
val artemisClient = ArtemisMessagingClient(artemisConfig, artemisAddress, MAX_MESSAGE_SIZE)
artemisServer.start()
artemisClient.start()
Expand All @@ -198,6 +198,7 @@ class AMQPBridgeTest {
doReturn(BOB_NAME).whenever(it).myLegalName
doReturn("trustpass").whenever(it).trustStorePassword
doReturn("cordacadevpass").whenever(it).keyStorePassword
doReturn(artemisAddress).whenever(it).p2pAddress
doReturn("").whenever(it).exportJMXto
doReturn(emptyList<CertChainPolicyConfig>()).whenever(it).certificateChainCheckPolicies
doReturn(false).whenever(it).useAMQPBridges
Expand All @@ -209,7 +210,7 @@ class AMQPBridgeTest {
doReturn(listOf(NodeInfo(listOf(artemisAddress), listOf(ALICE.identity), 1, 1L))).whenever(it).getNodesByOwningKeyIndex(any())
}
val userService = rigorousMock<RPCSecurityManager>()
val artemisServer = ArtemisMessagingServer(artemisConfig, artemisPort2, null, networkMap, userService, MAX_MESSAGE_SIZE)
val artemisServer = ArtemisMessagingServer(artemisConfig, artemisPort2, networkMap, userService, MAX_MESSAGE_SIZE)
val artemisClient = ArtemisMessagingClient(artemisConfig, artemisAddress2, MAX_MESSAGE_SIZE)
artemisServer.start()
artemisClient.start()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ class ProtonWrapperTests {
doReturn(CHARLIE_NAME).whenever(it).myLegalName
doReturn("trustpass").whenever(it).trustStorePassword
doReturn("cordacadevpass").whenever(it).keyStorePassword
doReturn(NetworkHostAndPort("0.0.0.0", artemisPort)).whenever(it).p2pAddress
doReturn("").whenever(it).exportJMXto
doReturn(emptyList<CertChainPolicyConfig>()).whenever(it).certificateChainCheckPolicies
doReturn(true).whenever(it).useAMQPBridges
Expand All @@ -236,7 +237,7 @@ class ProtonWrapperTests {
doReturn(never<NetworkMapCache.MapChange>()).whenever(it).changed
}
val userService = rigorousMock<RPCSecurityManager>()
val server = ArtemisMessagingServer(artemisConfig, artemisPort, null, networkMap, userService, MAX_MESSAGE_SIZE)
val server = ArtemisMessagingServer(artemisConfig, artemisPort, networkMap, userService, MAX_MESSAGE_SIZE)
val client = ArtemisMessagingClient(artemisConfig, NetworkHostAndPort("localhost", artemisPort), MAX_MESSAGE_SIZE)
server.start()
client.start()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class NetworkMapTest {
val testSerialization = SerializationEnvironmentRule(true)

private val cacheTimeout = 1.seconds
private val portAllocation = PortAllocation.Incremental(10000)
private val portAllocation = PortAllocation.RandomFree

private lateinit var networkMapServer: NetworkMapServer
private lateinit var compatibilityZone: CompatibilityZoneParams
Expand Down
Loading

0 comments on commit 142f52f

Please sign in to comment.