From d01dd22419de5cf985e72c86f8c972bf5e393c09 Mon Sep 17 00:00:00 2001 From: Michele Sollecito Date: Tue, 4 Sep 2018 10:26:10 +0100 Subject: [PATCH 1/3] [CORDA-1937]: Refactor NodeConfiguration hierarchy. (#3856) --- .../net/corda/client/rpc/CordaRPCClient.kt | 23 +-- .../rpc/internal/CordaRPCClientUtils.kt | 8 - .../corda/client/rpc/internal/RPCClient.kt | 10 +- .../net/corda/core/internal/InternalUtils.kt | 10 -- .../net/corda/nodeapi/ArtemisTcpTransport.kt | 70 ++------ .../internal/ArtemisMessagingClient.kt | 7 +- .../nodeapi/internal/DevIdentityGenerator.kt | 19 +- .../internal/InternalArtemisTcpTransport.kt | 166 ++++++++++++++++++ .../nodeapi/internal/KeyStoreConfigHelpers.kt | 81 ++++----- .../internal/bridging/AMQPBridgeManager.kt | 18 +- .../bridging/BridgeControlListener.kt | 6 +- .../internal/config/CertificateStore.kt | 82 +++++++++ .../config/CertificateStoreSupplier.kt | 24 +++ ...SLConfiguration.kt => SslConfiguration.kt} | 31 +++- .../nodeapi/internal/crypto/X509KeyStore.kt | 9 + .../protonwrapper/netty/AMQPClient.kt | 2 +- .../protonwrapper/netty/AMQPConfiguration.kt | 14 +- .../protonwrapper/netty/AMQPServer.kt | 2 +- .../internal/protonwrapper/netty/SSLHelper.kt | 21 ++- .../internal/crypto/DevCertificatesTest.kt | 7 +- .../internal/crypto/X509UtilitiesTest.kt | 53 +++--- .../net/corda/node/NodeKeystoreCheckTest.kt | 17 +- .../net/corda/node/amqp/AMQPBridgeTest.kt | 32 ++-- .../CertificateRevocationListNodeTests.kt | 75 ++++---- .../net/corda/node/amqp/ProtonWrapperTests.kt | 111 +++++++----- .../messaging/ArtemisMessagingTest.kt | 13 +- .../node/services/network/NetworkMapTest.kt | 13 +- .../node/services/rpc/ArtemisRpcTests.kt | 16 +- .../messaging/MQSecurityAsNodeTest.kt | 76 +++----- .../services/messaging/MQSecurityTest.kt | 6 +- .../services/messaging/SimpleMQClient.kt | 8 +- .../net/corda/node/internal/AbstractNode.kt | 42 +++-- .../kotlin/net/corda/node/internal/Node.kt | 8 +- .../node/services/config/ConfigUtilities.kt | 30 ++-- .../node/services/config/NodeConfiguration.kt | 32 +++- .../node/services/config/shell/ShellConfig.kt | 5 +- .../messaging/ArtemisMessagingServer.kt | 9 +- .../messaging/InternalRPCMessagingClient.kt | 8 +- .../services/messaging/P2PMessagingClient.kt | 4 +- .../node/services/rpc/ArtemisRpcBroker.kt | 14 +- .../services/rpc/RpcBrokerConfiguration.kt | 8 +- .../transactions/RaftUniquenessProvider.kt | 18 +- .../registration/NetworkRegistrationHelper.kt | 59 ++++--- .../NetworkRegistrationHelperTest.kt | 42 +++-- .../testing/node/internal/DriverDSLImpl.kt | 15 +- .../node/internal/InternalMockNetwork.kt | 19 +- .../corda/testing/node/internal/RPCDriver.kt | 8 +- .../internal/UnsafeCertificatesFactory.kt | 20 ++- .../testing/internal/InternalTestUtils.kt | 54 ++---- .../internal/stubs/CertificateStoreStubs.kt | 101 +++++++++++ .../shell/InteractiveShellIntegrationTest.kt | 2 +- .../net/corda/tools/shell/InteractiveShell.kt | 19 -- .../corda/tools/shell/ShellConfiguration.kt | 2 - .../net/corda/webserver/WebServerConfig.kt | 13 +- .../corda/webserver/internal/NodeWebServer.kt | 4 +- 55 files changed, 961 insertions(+), 605 deletions(-) create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/InternalArtemisTcpTransport.kt create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/config/CertificateStore.kt create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/config/CertificateStoreSupplier.kt rename node-api/src/main/kotlin/net/corda/nodeapi/internal/config/{SSLConfiguration.kt => SslConfiguration.kt} (54%) create mode 100644 testing/test-utils/src/main/kotlin/net/corda/testing/internal/stubs/CertificateStoreStubs.kt diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt index 7dfc797e0e..c6b9d0556e 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt @@ -7,12 +7,11 @@ import net.corda.core.context.Trace import net.corda.core.messaging.CordaRPCOps import net.corda.core.serialization.internal.effectiveSerializationEnv import net.corda.core.utilities.NetworkHostAndPort -import net.corda.nodeapi.ArtemisTcpTransport.Companion.rpcConnectorTcpTransport import net.corda.core.messaging.ClientRpcSslOptions import net.corda.core.utilities.days import net.corda.core.utilities.minutes import net.corda.core.utilities.seconds -import net.corda.nodeapi.internal.config.SSLConfiguration +import net.corda.nodeapi.internal.InternalArtemisTcpTransport.Companion.rpcConnectorTcpTransport import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT import java.time.Duration @@ -243,10 +242,8 @@ class CordaRPCClient private constructor( private val hostAndPort: NetworkHostAndPort, private val configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT, private val sslConfiguration: ClientRpcSslOptions? = null, - private val nodeSslConfiguration: SSLConfiguration? = null, private val classLoader: ClassLoader? = null, - private val haAddressPool: List = emptyList(), - private val internalConnection: Boolean = false + private val haAddressPool: List = emptyList() ) { @JvmOverloads constructor(hostAndPort: NetworkHostAndPort, @@ -260,7 +257,7 @@ class CordaRPCClient private constructor( * @param configuration An optional configuration used to tweak client behaviour. */ @JvmOverloads - constructor(haAddressPool: List, configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT) : this(haAddressPool.first(), configuration, null, null, null, haAddressPool) + constructor(haAddressPool: List, configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT) : this(haAddressPool.first(), configuration, null, null, haAddressPool) companion object { fun createWithSsl( @@ -285,16 +282,7 @@ class CordaRPCClient private constructor( sslConfiguration: ClientRpcSslOptions? = null, classLoader: ClassLoader? = null ): CordaRPCClient { - return CordaRPCClient(hostAndPort, configuration, sslConfiguration, null, classLoader) - } - - internal fun createWithInternalSslAndClassLoader( - hostAndPort: NetworkHostAndPort, - configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT, - sslConfiguration: SSLConfiguration?, - classLoader: ClassLoader? = null - ): CordaRPCClient { - return CordaRPCClient(hostAndPort, configuration, null, sslConfiguration, classLoader, internalConnection = true) + return CordaRPCClient(hostAndPort, configuration, sslConfiguration, classLoader) } } @@ -312,9 +300,6 @@ class CordaRPCClient private constructor( private fun getRpcClient(): RPCClient { return when { - // Node->RPC broker, mutually authenticated SSL. This is used when connecting the integrated shell - internalConnection == true -> RPCClient(hostAndPort, nodeSslConfiguration!!) - // Client->RPC broker haAddressPool.isEmpty() -> RPCClient( rpcConnectorTcpTransport(hostAndPort, config = sslConfiguration), diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/CordaRPCClientUtils.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/CordaRPCClientUtils.kt index 7781d0f135..9b07c405a9 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/CordaRPCClientUtils.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/CordaRPCClientUtils.kt @@ -6,7 +6,6 @@ import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.pendingFlowsCount import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.messaging.ClientRpcSslOptions -import net.corda.nodeapi.internal.config.SSLConfiguration import rx.Observable /** Utility which exposes the internal Corda RPC constructor to other internal Corda components */ @@ -17,13 +16,6 @@ fun createCordaRPCClientWithSslAndClassLoader( classLoader: ClassLoader? = null ) = CordaRPCClient.createWithSslAndClassLoader(hostAndPort, configuration, sslConfiguration, classLoader) -fun createCordaRPCClientWithInternalSslAndClassLoader( - hostAndPort: NetworkHostAndPort, - configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT, - sslConfiguration: SSLConfiguration? = null, - classLoader: ClassLoader? = null -) = CordaRPCClient.createWithInternalSslAndClassLoader(hostAndPort, configuration, sslConfiguration, classLoader) - fun CordaRPCOps.drainAndShutdown(): Observable { setFlowsDrainingModeEnabled(true) diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt index 1d7969caaa..1665aa0159 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt @@ -15,11 +15,11 @@ import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger -import net.corda.nodeapi.ArtemisTcpTransport.Companion.rpcConnectorTcpTransport -import net.corda.nodeapi.ArtemisTcpTransport.Companion.rpcConnectorTcpTransportsFromList -import net.corda.nodeapi.ArtemisTcpTransport.Companion.rpcInternalClientTcpTransport import net.corda.nodeapi.RPCApi -import net.corda.nodeapi.internal.config.SSLConfiguration +import net.corda.nodeapi.internal.InternalArtemisTcpTransport.Companion.rpcConnectorTcpTransport +import net.corda.nodeapi.internal.InternalArtemisTcpTransport.Companion.rpcConnectorTcpTransportsFromList +import net.corda.nodeapi.internal.InternalArtemisTcpTransport.Companion.rpcInternalClientTcpTransport +import net.corda.nodeapi.internal.config.SslConfiguration import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.TransportConfiguration import org.apache.activemq.artemis.api.core.client.ActiveMQClient @@ -43,7 +43,7 @@ class RPCClient( constructor( hostAndPort: NetworkHostAndPort, - sslConfiguration: SSLConfiguration, + sslConfiguration: SslConfiguration, configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT, serializationContext: SerializationContext = SerializationDefaults.RPC_CLIENT_CONTEXT ) : this(rpcInternalClientTcpTransport(hostAndPort, sslConfiguration), configuration, serializationContext) diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index 9626cf88a0..078cfcaa11 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -4,21 +4,11 @@ package net.corda.core.internal import net.corda.core.DeleteForDJVM import net.corda.core.KeepForDJVM -import net.corda.core.cordapp.Cordapp -import net.corda.core.cordapp.CordappConfig -import net.corda.core.cordapp.CordappContext import net.corda.core.crypto.* -import net.corda.core.flows.FlowLogic -import net.corda.core.node.ServicesForResolution import net.corda.core.serialization.* -import net.corda.core.transactions.LedgerTransaction -import net.corda.core.transactions.SignedTransaction -import net.corda.core.transactions.TransactionBuilder -import net.corda.core.transactions.WireTransaction import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.UntrustworthyData import org.slf4j.Logger -import org.slf4j.MDC import rx.Observable import rx.Observer import rx.subjects.PublishSubject diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisTcpTransport.kt b/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisTcpTransport.kt index 18fbad6ff2..210f04586a 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisTcpTransport.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisTcpTransport.kt @@ -1,12 +1,16 @@ package net.corda.nodeapi import net.corda.core.messaging.ClientRpcSslOptions -import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.core.utilities.NetworkHostAndPort +import net.corda.nodeapi.internal.InternalArtemisTcpTransport +import net.corda.nodeapi.internal.InternalArtemisTcpTransport.Companion.acceptorFactoryClassName +import net.corda.nodeapi.internal.InternalArtemisTcpTransport.Companion.connectorFactoryClassName +import net.corda.nodeapi.internal.InternalArtemisTcpTransport.Companion.defaultArtemisOptions +import net.corda.nodeapi.internal.InternalArtemisTcpTransport.Companion.defaultSSLOptions import net.corda.nodeapi.internal.config.SSLConfiguration +import net.corda.nodeapi.internal.config.SslConfiguration import net.corda.nodeapi.internal.requireOnDefaultFileSystem import org.apache.activemq.artemis.api.core.TransportConfiguration -import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants import java.nio.file.Path @@ -38,26 +42,6 @@ class ArtemisTcpTransport { /** Supported TLS versions, currently TLSv1.2 only. */ val TLS_VERSIONS = listOf("TLSv1.2") - private fun defaultArtemisOptions(hostAndPort: NetworkHostAndPort) = mapOf( - // Basic TCP target details. - TransportConstants.HOST_PROP_NAME to hostAndPort.host, - TransportConstants.PORT_PROP_NAME to hostAndPort.port, - - // Turn on AMQP support, which needs the protocol jar on the classpath. - // Unfortunately we cannot disable core protocol as artemis only uses AMQP for interop. - // It does not use AMQP messages for its own messages e.g. topology and heartbeats. - // TODO further investigate how to ensure we use a well defined wire level protocol for Node to Node communications. - TransportConstants.PROTOCOLS_PROP_NAME to "CORE,AMQP", - TransportConstants.USE_GLOBAL_WORKER_POOL_PROP_NAME to (nodeSerializationEnv != null), - TransportConstants.REMOTING_THREADS_PROPNAME to (if (nodeSerializationEnv != null) -1 else 1), - // turn off direct delivery in Artemis - this is latency optimisation that can lead to - //hick-ups under high load (CORDA-1336) - TransportConstants.DIRECT_DELIVER to false) - - private val defaultSSLOptions = mapOf( - TransportConstants.ENABLED_CIPHER_SUITES_PROP_NAME to CIPHER_SUITES.joinToString(","), - TransportConstants.ENABLED_PROTOCOLS_PROP_NAME to TLS_VERSIONS.joinToString(",")) - private fun SSLConfiguration.toTransportOptions() = mapOf( TransportConstants.SSL_ENABLED_PROP_NAME to true, TransportConstants.KEYSTORE_PROVIDER_PROP_NAME to "JKS", @@ -68,22 +52,6 @@ class ArtemisTcpTransport { TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME to trustStorePassword, TransportConstants.NEED_CLIENT_AUTH_PROP_NAME to true) - private fun ClientRpcSslOptions.toTransportOptions() = mapOf( - TransportConstants.SSL_ENABLED_PROP_NAME to true, - TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME to trustStoreProvider, - TransportConstants.TRUSTSTORE_PATH_PROP_NAME to trustStorePath, - TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME to trustStorePassword) - - private fun BrokerRpcSslOptions.toTransportOptions() = mapOf( - TransportConstants.SSL_ENABLED_PROP_NAME to true, - TransportConstants.KEYSTORE_PROVIDER_PROP_NAME to "JKS", - TransportConstants.KEYSTORE_PATH_PROP_NAME to keyStorePath, - TransportConstants.KEYSTORE_PASSWORD_PROP_NAME to keyStorePassword, - TransportConstants.NEED_CLIENT_AUTH_PROP_NAME to false) - - private val acceptorFactoryClassName = "org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptorFactory" - private val connectorFactoryClassName = NettyConnectorFactory::class.java.name - fun p2pAcceptorTcpTransport(hostAndPort: NetworkHostAndPort, config: SSLConfiguration?, enableSSL: Boolean = true): TransportConfiguration { val options = defaultArtemisOptions(hostAndPort).toMutableMap() @@ -110,27 +78,13 @@ class ArtemisTcpTransport { /** [TransportConfiguration] for RPC TCP communication - server side. */ fun rpcAcceptorTcpTransport(hostAndPort: NetworkHostAndPort, config: BrokerRpcSslOptions?, enableSSL: Boolean = true): TransportConfiguration { - val options = defaultArtemisOptions(hostAndPort).toMutableMap() - - if (config != null && enableSSL) { - config.keyStorePath.requireOnDefaultFileSystem() - options.putAll(config.toTransportOptions()) - options.putAll(defaultSSLOptions) - } - return TransportConfiguration(acceptorFactoryClassName, options) + return InternalArtemisTcpTransport.rpcAcceptorTcpTransport(hostAndPort, config, enableSSL) } /** [TransportConfiguration] for RPC TCP communication * This is the Transport that connects the client JVM to the broker. */ fun rpcConnectorTcpTransport(hostAndPort: NetworkHostAndPort, config: ClientRpcSslOptions?, enableSSL: Boolean = true): TransportConfiguration { - val options = defaultArtemisOptions(hostAndPort).toMutableMap() - - if (config != null && enableSSL) { - config.trustStorePath.requireOnDefaultFileSystem() - options.putAll(config.toTransportOptions()) - options.putAll(defaultSSLOptions) - } - return TransportConfiguration(connectorFactoryClassName, options) + return InternalArtemisTcpTransport.rpcConnectorTcpTransport(hostAndPort, config, enableSSL) } /** Create as list of [TransportConfiguration]. **/ @@ -138,12 +92,12 @@ class ArtemisTcpTransport { rpcConnectorTcpTransport(it, config, enableSSL) } - fun rpcInternalClientTcpTransport(hostAndPort: NetworkHostAndPort, config: SSLConfiguration): TransportConfiguration { - return TransportConfiguration(connectorFactoryClassName, defaultArtemisOptions(hostAndPort) + defaultSSLOptions + config.toTransportOptions()) + fun rpcInternalClientTcpTransport(hostAndPort: NetworkHostAndPort, config: SslConfiguration): TransportConfiguration { + return InternalArtemisTcpTransport.rpcInternalClientTcpTransport(hostAndPort, config) } - fun rpcInternalAcceptorTcpTransport(hostAndPort: NetworkHostAndPort, config: SSLConfiguration): TransportConfiguration { - return TransportConfiguration(acceptorFactoryClassName, defaultArtemisOptions(hostAndPort) + defaultSSLOptions + config.toTransportOptions()) + fun rpcInternalAcceptorTcpTransport(hostAndPort: NetworkHostAndPort, config: SslConfiguration): TransportConfiguration { + return InternalArtemisTcpTransport.rpcInternalAcceptorTcpTransport(hostAndPort, config) } } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/ArtemisMessagingClient.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/ArtemisMessagingClient.kt index 4e0366b9d7..055c96ffe6 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/ArtemisMessagingClient.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/ArtemisMessagingClient.kt @@ -3,9 +3,8 @@ package net.corda.nodeapi.internal import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.loggerFor -import net.corda.nodeapi.ArtemisTcpTransport import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_P2P_USER -import net.corda.nodeapi.internal.config.SSLConfiguration +import net.corda.nodeapi.internal.config.MutualSslConfiguration import org.apache.activemq.artemis.api.core.client.ActiveMQClient import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE import org.apache.activemq.artemis.api.core.client.ClientProducer @@ -18,7 +17,7 @@ interface ArtemisSessionProvider { val started: ArtemisMessagingClient.Started? } -class ArtemisMessagingClient(private val config: SSLConfiguration, +class ArtemisMessagingClient(private val config: MutualSslConfiguration, private val serverAddress: NetworkHostAndPort, private val maxMessageSize: Int) : ArtemisSessionProvider { companion object { @@ -34,7 +33,7 @@ class ArtemisMessagingClient(private val config: SSLConfiguration, check(started == null) { "start can't be called twice" } log.info("Connecting to message broker: $serverAddress") // TODO Add broker CN to config for host verification in case the embedded broker isn't used - val tcpTransport = ArtemisTcpTransport.p2pConnectorTcpTransport(serverAddress, config) + val tcpTransport = InternalArtemisTcpTransport.p2pConnectorTcpTransport(serverAddress, config) val locator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport).apply { // Never time out on our loopback Artemis connections. If we switch back to using the InVM transport this // would be the default and the two lines below can be deleted. diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt index 080bf680c4..ab840ed387 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt @@ -7,7 +7,8 @@ import net.corda.core.identity.Party import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.utilities.trace -import net.corda.nodeapi.internal.config.NodeSSLConfiguration +import net.corda.nodeapi.internal.config.FileBasedCertificateStoreSupplier +import net.corda.nodeapi.internal.config.SslConfiguration import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.crypto.X509Utilities @@ -31,15 +32,15 @@ object DevIdentityGenerator { /** Install a node key store for the given node directory using the given legal name. */ fun installKeyStoreWithNodeIdentity(nodeDir: Path, legalName: CordaX500Name): Party { - val nodeSslConfig = object : NodeSSLConfiguration { - override val baseDirectory = nodeDir - override val keyStorePassword: String = "cordacadevpass" - override val trustStorePassword get() = throw NotImplementedError("Not expected to be called") - override val crlCheckSoftFail: Boolean = true - } + val certificatesDirectory = nodeDir / "certificates" + val signingCertStore = FileBasedCertificateStoreSupplier(certificatesDirectory / "nodekeystore.jks", "cordacadevpass") + val p2pKeyStore = FileBasedCertificateStoreSupplier(certificatesDirectory / "sslkeystore.jks", "cordacadevpass") + val p2pTrustStore = FileBasedCertificateStoreSupplier(certificatesDirectory / "truststore.jks", "trustpass") + val p2pSslConfig = SslConfiguration.mutual(p2pKeyStore, p2pTrustStore) - nodeSslConfig.certificatesDirectory.createDirectories() - val (nodeKeyStore) = nodeSslConfig.createDevKeyStores(legalName) + certificatesDirectory.createDirectories() + val nodeKeyStore = signingCertStore.get(true).also { it.registerDevSigningCertificates(legalName) } + p2pSslConfig.keyStore.get(true).also { it.registerDevP2pCertificates(legalName) } val identity = nodeKeyStore.storeLegalIdentity("$NODE_IDENTITY_ALIAS_PREFIX-private-key") return identity.party diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/InternalArtemisTcpTransport.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/InternalArtemisTcpTransport.kt new file mode 100644 index 0000000000..85513c9ad1 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/InternalArtemisTcpTransport.kt @@ -0,0 +1,166 @@ +package net.corda.nodeapi.internal + +import net.corda.core.messaging.ClientRpcSslOptions +import net.corda.core.serialization.internal.nodeSerializationEnv +import net.corda.core.utilities.NetworkHostAndPort +import net.corda.nodeapi.BrokerRpcSslOptions +import net.corda.nodeapi.internal.config.CertificateStore +import net.corda.nodeapi.internal.config.FileBasedCertificateStoreSupplier +import net.corda.nodeapi.internal.config.SslConfiguration +import net.corda.nodeapi.internal.config.MutualSslConfiguration +import org.apache.activemq.artemis.api.core.TransportConfiguration +import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory +import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants +import java.nio.file.Path + +// This avoids internal types from leaking in the public API. The "external" ArtemisTcpTransport delegates to this internal one. +class InternalArtemisTcpTransport { + companion object { + val CIPHER_SUITES = listOf( + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256" + ) + + val TLS_VERSIONS = listOf("TLSv1.2") + + internal fun defaultArtemisOptions(hostAndPort: NetworkHostAndPort) = mapOf( + // Basic TCP target details. + TransportConstants.HOST_PROP_NAME to hostAndPort.host, + TransportConstants.PORT_PROP_NAME to hostAndPort.port, + + // Turn on AMQP support, which needs the protocol jar on the classpath. + // Unfortunately we cannot disable core protocol as artemis only uses AMQP for interop. + // It does not use AMQP messages for its own messages e.g. topology and heartbeats. + // TODO further investigate how to ensure we use a well defined wire level protocol for Node to Node communications. + TransportConstants.PROTOCOLS_PROP_NAME to "CORE,AMQP", + TransportConstants.USE_GLOBAL_WORKER_POOL_PROP_NAME to (nodeSerializationEnv != null), + TransportConstants.REMOTING_THREADS_PROPNAME to (if (nodeSerializationEnv != null) -1 else 1), + // turn off direct delivery in Artemis - this is latency optimisation that can lead to + //hick-ups under high load (CORDA-1336) + TransportConstants.DIRECT_DELIVER to false) + + internal val defaultSSLOptions = mapOf( + TransportConstants.ENABLED_CIPHER_SUITES_PROP_NAME to CIPHER_SUITES.joinToString(","), + TransportConstants.ENABLED_PROTOCOLS_PROP_NAME to TLS_VERSIONS.joinToString(",")) + + private fun SslConfiguration.toTransportOptions(): Map { + + val options = mutableMapOf() + (keyStore to trustStore).addToTransportOptions(options) + return options + } + + private fun Pair.addToTransportOptions(options: MutableMap) { + + val keyStore = first + val trustStore = second + keyStore?.let { + with (it) { + path.requireOnDefaultFileSystem() + options.putAll(get().toKeyStoreTransportOptions(path)) + } + } + trustStore?.let { + with (it) { + path.requireOnDefaultFileSystem() + options.putAll(get().toTrustStoreTransportOptions(path)) + } + } + } + + private fun CertificateStore.toKeyStoreTransportOptions(path: Path) = mapOf( + TransportConstants.SSL_ENABLED_PROP_NAME to true, + TransportConstants.KEYSTORE_PROVIDER_PROP_NAME to "JKS", + TransportConstants.KEYSTORE_PATH_PROP_NAME to path, + TransportConstants.KEYSTORE_PASSWORD_PROP_NAME to password, + TransportConstants.NEED_CLIENT_AUTH_PROP_NAME to true) + + private fun CertificateStore.toTrustStoreTransportOptions(path: Path) = mapOf( + TransportConstants.SSL_ENABLED_PROP_NAME to true, + TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME to "JKS", + TransportConstants.TRUSTSTORE_PATH_PROP_NAME to path, + TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME to password, + TransportConstants.NEED_CLIENT_AUTH_PROP_NAME to true) + + private fun ClientRpcSslOptions.toTransportOptions() = mapOf( + TransportConstants.SSL_ENABLED_PROP_NAME to true, + TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME to trustStoreProvider, + TransportConstants.TRUSTSTORE_PATH_PROP_NAME to trustStorePath, + TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME to trustStorePassword) + + private fun BrokerRpcSslOptions.toTransportOptions() = mapOf( + TransportConstants.SSL_ENABLED_PROP_NAME to true, + TransportConstants.KEYSTORE_PROVIDER_PROP_NAME to "JKS", + TransportConstants.KEYSTORE_PATH_PROP_NAME to keyStorePath, + TransportConstants.KEYSTORE_PASSWORD_PROP_NAME to keyStorePassword, + TransportConstants.NEED_CLIENT_AUTH_PROP_NAME to false) + + internal val acceptorFactoryClassName = "org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptorFactory" + internal val connectorFactoryClassName = NettyConnectorFactory::class.java.name + + fun p2pAcceptorTcpTransport(hostAndPort: NetworkHostAndPort, config: MutualSslConfiguration?, enableSSL: Boolean = true): TransportConfiguration { + + return p2pAcceptorTcpTransport(hostAndPort, config?.keyStore, config?.trustStore, enableSSL = enableSSL) + } + + fun p2pConnectorTcpTransport(hostAndPort: NetworkHostAndPort, config: MutualSslConfiguration?, enableSSL: Boolean = true): TransportConfiguration { + + return p2pConnectorTcpTransport(hostAndPort, config?.keyStore, config?.trustStore, enableSSL = enableSSL) + } + + fun p2pAcceptorTcpTransport(hostAndPort: NetworkHostAndPort, keyStore: FileBasedCertificateStoreSupplier?, trustStore: FileBasedCertificateStoreSupplier?, enableSSL: Boolean = true): TransportConfiguration { + + val options = defaultArtemisOptions(hostAndPort).toMutableMap() + if (enableSSL) { + options.putAll(defaultSSLOptions) + (keyStore to trustStore).addToTransportOptions(options) + } + return TransportConfiguration(acceptorFactoryClassName, options) + } + + fun p2pConnectorTcpTransport(hostAndPort: NetworkHostAndPort, keyStore: FileBasedCertificateStoreSupplier?, trustStore: FileBasedCertificateStoreSupplier?, enableSSL: Boolean = true): TransportConfiguration { + + val options = defaultArtemisOptions(hostAndPort).toMutableMap() + if (enableSSL) { + options.putAll(defaultSSLOptions) + (keyStore to trustStore).addToTransportOptions(options) + } + return TransportConfiguration(connectorFactoryClassName, options) + } + + fun rpcAcceptorTcpTransport(hostAndPort: NetworkHostAndPort, config: BrokerRpcSslOptions?, enableSSL: Boolean = true): TransportConfiguration { + val options = defaultArtemisOptions(hostAndPort).toMutableMap() + + if (config != null && enableSSL) { + config.keyStorePath.requireOnDefaultFileSystem() + options.putAll(config.toTransportOptions()) + options.putAll(defaultSSLOptions) + } + return TransportConfiguration(acceptorFactoryClassName, options) + } + + fun rpcConnectorTcpTransport(hostAndPort: NetworkHostAndPort, config: ClientRpcSslOptions?, enableSSL: Boolean = true): TransportConfiguration { + val options = defaultArtemisOptions(hostAndPort).toMutableMap() + + if (config != null && enableSSL) { + config.trustStorePath.requireOnDefaultFileSystem() + options.putAll(config.toTransportOptions()) + options.putAll(defaultSSLOptions) + } + return TransportConfiguration(connectorFactoryClassName, options) + } + + fun rpcConnectorTcpTransportsFromList(hostAndPortList: List, config: ClientRpcSslOptions?, enableSSL: Boolean = true): List = hostAndPortList.map { + rpcConnectorTcpTransport(it, config, enableSSL) + } + + fun rpcInternalClientTcpTransport(hostAndPort: NetworkHostAndPort, config: SslConfiguration): TransportConfiguration { + return TransportConfiguration(connectorFactoryClassName, defaultArtemisOptions(hostAndPort) + defaultSSLOptions + config.toTransportOptions()) + } + + fun rpcInternalAcceptorTcpTransport(hostAndPort: NetworkHostAndPort, config: SslConfiguration): TransportConfiguration { + return TransportConfiguration(acceptorFactoryClassName, defaultArtemisOptions(hostAndPort) + defaultSSLOptions + config.toTransportOptions()) + } + } +} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt index 6d2ca5b271..d8a856afa5 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt @@ -5,7 +5,7 @@ import net.corda.core.crypto.Crypto.generateKeyPair import net.corda.core.identity.CordaX500Name import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.toX500Name -import net.corda.nodeapi.internal.config.SSLConfiguration +import net.corda.nodeapi.internal.config.CertificateStore import net.corda.nodeapi.internal.crypto.* import org.bouncycastle.asn1.x509.GeneralName import org.bouncycastle.asn1.x509.GeneralSubtree @@ -20,48 +20,43 @@ import javax.security.auth.x500.X500Principal * 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(legalName: CordaX500Name, - rootCert: X509Certificate = DEV_ROOT_CA.certificate, - intermediateCa: CertificateAndKeyPair = DEV_INTERMEDIATE_CA): Pair { - val (nodeCaCert, nodeCaKeyPair) = createDevNodeCa(intermediateCa, legalName) - val nodeKeyStore = loadNodeKeyStore(createNew = true) - nodeKeyStore.update { - setPrivateKey( - X509Utilities.CORDA_CLIENT_CA, - nodeCaKeyPair.private, - listOf(nodeCaCert, intermediateCa.certificate, rootCert)) +fun CertificateStore.registerDevSigningCertificates(legalName: CordaX500Name, + rootCert: X509Certificate = DEV_ROOT_CA.certificate, + intermediateCa: CertificateAndKeyPair = DEV_INTERMEDIATE_CA, + devNodeCa: CertificateAndKeyPair = createDevNodeCa(intermediateCa, legalName)) { + + update { + setPrivateKey(X509Utilities.CORDA_CLIENT_CA, devNodeCa.keyPair.private, listOf(devNodeCa.certificate, intermediateCa.certificate, rootCert)) } - - val sslKeyStore = loadSslKeyStore(createNew = true) - sslKeyStore.update { - val tlsKeyPair = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, nodeCaCert, nodeCaKeyPair, legalName.x500Principal, tlsKeyPair.public) - setPrivateKey( - X509Utilities.CORDA_CLIENT_TLS, - tlsKeyPair.private, - listOf(tlsCert, nodeCaCert, intermediateCa.certificate, rootCert)) - } - - return Pair(nodeKeyStore, sslKeyStore) } -fun X509KeyStore.storeLegalIdentity(alias: String, keyPair: KeyPair = Crypto.generateKeyPair()): PartyAndCertificate { - val nodeCaCertPath = getCertificateChain(X509Utilities.CORDA_CLIENT_CA) - // Assume key password = store password. - val nodeCaCertAndKeyPair = getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA) - // Create new keys and store in keystore. - val identityCert = X509Utilities.createCertificate( - CertificateType.LEGAL_IDENTITY, - nodeCaCertAndKeyPair.certificate, - nodeCaCertAndKeyPair.keyPair, - nodeCaCertAndKeyPair.certificate.subjectX500Principal, - keyPair.public) - // TODO: X509Utilities.validateCertificateChain() - // Assume key password = store password. - val identityCertPath = listOf(identityCert) + nodeCaCertPath - setPrivateKey(alias, keyPair.private, identityCertPath) - save() +fun CertificateStore.registerDevP2pCertificates(legalName: CordaX500Name, + rootCert: X509Certificate = DEV_ROOT_CA.certificate, + intermediateCa: CertificateAndKeyPair = DEV_INTERMEDIATE_CA, + devNodeCa: CertificateAndKeyPair = createDevNodeCa(intermediateCa, legalName)) { + + update { + val tlsKeyPair = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, devNodeCa.certificate, devNodeCa.keyPair, legalName.x500Principal, tlsKeyPair.public) + setPrivateKey(X509Utilities.CORDA_CLIENT_TLS, tlsKeyPair.private, listOf(tlsCert, devNodeCa.certificate, intermediateCa.certificate, rootCert)) + } +} + +fun CertificateStore.storeLegalIdentity(alias: String, keyPair: KeyPair = Crypto.generateKeyPair()): PartyAndCertificate { + val identityCertPath = query { + val nodeCaCertPath = getCertificateChain(X509Utilities.CORDA_CLIENT_CA) + // Assume key password = store password. + val nodeCaCertAndKeyPair = getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA) + // Create new keys and store in keystore. + val identityCert = X509Utilities.createCertificate(CertificateType.LEGAL_IDENTITY, nodeCaCertAndKeyPair.certificate, nodeCaCertAndKeyPair.keyPair, nodeCaCertAndKeyPair.certificate.subjectX500Principal, keyPair.public) + // TODO: X509Utilities.validateCertificateChain() + // Assume key password = store password. + listOf(identityCert) + nodeCaCertPath + } + update { + setPrivateKey(alias, keyPair.private, identityCertPath) + } return PartyAndCertificate(X509Utilities.buildCertPath(identityCertPath)) } @@ -105,8 +100,10 @@ const val DEV_CA_TRUST_STORE_PASS: String = "trustpass" // We need a class so that we can get hold of the class loader internal object DevCaHelper { fun loadDevCa(alias: String): CertificateAndKeyPair { - // TODO: Should be identity scheme - val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/$DEV_CA_KEY_STORE_FILE"), DEV_CA_KEY_STORE_PASS) - return caKeyStore.getCertificateAndKeyPair(alias, DEV_CA_PRIVATE_KEY_PASS) + return loadDevCaKeyStore().query { getCertificateAndKeyPair(alias, DEV_CA_PRIVATE_KEY_PASS) } } } + +fun loadDevCaKeyStore(classLoader: ClassLoader = DevCaHelper::class.java.classLoader): CertificateStore = CertificateStore.fromResource("certificates/$DEV_CA_KEY_STORE_FILE", DEV_CA_KEY_STORE_PASS, classLoader) + +fun loadDevCaTrustStore(classLoader: ClassLoader = DevCaHelper::class.java.classLoader): CertificateStore = CertificateStore.fromResource("certificates/$DEV_CA_TRUST_STORE_FILE", DEV_CA_TRUST_STORE_PASS, classLoader) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/AMQPBridgeManager.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/AMQPBridgeManager.kt index 5b6a2d8cd5..7be3844aec 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/AMQPBridgeManager.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/AMQPBridgeManager.kt @@ -14,7 +14,8 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2PMessagi import net.corda.nodeapi.internal.ArtemisMessagingComponent.RemoteInboxAddress.Companion.translateLocalQueueToInboxAddress import net.corda.nodeapi.internal.ArtemisSessionProvider import net.corda.nodeapi.internal.bridging.AMQPBridgeManager.AMQPBridge.Companion.getBridgeName -import net.corda.nodeapi.internal.config.NodeSSLConfiguration +import net.corda.nodeapi.internal.config.CertificateStore +import net.corda.nodeapi.internal.config.MutualSslConfiguration import net.corda.nodeapi.internal.protonwrapper.messages.MessageStatus import net.corda.nodeapi.internal.protonwrapper.netty.AMQPClient import net.corda.nodeapi.internal.protonwrapper.netty.AMQPConfiguration @@ -25,7 +26,6 @@ import org.apache.activemq.artemis.api.core.client.ClientMessage import org.apache.activemq.artemis.api.core.client.ClientSession import org.slf4j.MDC import rx.Subscription -import java.security.KeyStore import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock @@ -37,26 +37,22 @@ import kotlin.concurrent.withLock * The Netty thread pool used by the AMQPBridges is also shared and managed by the AMQPBridgeManager. */ @VisibleForTesting -class AMQPBridgeManager(config: NodeSSLConfiguration, maxMessageSize: Int, private val artemisMessageClientFactory: () -> ArtemisSessionProvider) : BridgeManager { +class AMQPBridgeManager(config: MutualSslConfiguration, maxMessageSize: Int, private val artemisMessageClientFactory: () -> ArtemisSessionProvider) : BridgeManager { private val lock = ReentrantLock() private val bridgeNameToBridgeMap = mutableMapOf() - private class AMQPConfigurationImpl private constructor(override val keyStore: KeyStore, - override val keyStorePrivateKeyPassword: CharArray, - override val trustStore: KeyStore, + private class AMQPConfigurationImpl private constructor(override val keyStore: CertificateStore, + override val trustStore: CertificateStore, override val maxMessageSize: Int) : AMQPConfiguration { - constructor(config: NodeSSLConfiguration, maxMessageSize: Int) : this(config.loadSslKeyStore().internal, - config.keyStorePassword.toCharArray(), - config.loadTrustStore().internal, - maxMessageSize) + constructor(config: MutualSslConfiguration, maxMessageSize: Int) : this(config.keyStore.get(), config.trustStore.get(), maxMessageSize) } private val amqpConfig: AMQPConfiguration = AMQPConfigurationImpl(config, maxMessageSize) private var sharedEventLoopGroup: EventLoopGroup? = null private var artemis: ArtemisSessionProvider? = null - constructor(config: NodeSSLConfiguration, p2pAddress: NetworkHostAndPort, maxMessageSize: Int) : this(config, maxMessageSize, { ArtemisMessagingClient(config, p2pAddress, maxMessageSize) }) + constructor(config: MutualSslConfiguration, p2pAddress: NetworkHostAndPort, maxMessageSize: Int) : this(config, maxMessageSize, { ArtemisMessagingClient(config, p2pAddress, maxMessageSize) }) companion object { private const val NUM_BRIDGE_THREADS = 0 // Default sized pool diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/BridgeControlListener.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/BridgeControlListener.kt index 3dc987e6ec..c9d6e23af7 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/BridgeControlListener.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/BridgeControlListener.kt @@ -11,14 +11,14 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.BRIDGE_NOT import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREFIX import net.corda.nodeapi.internal.ArtemisSessionProvider -import net.corda.nodeapi.internal.config.NodeSSLConfiguration +import net.corda.nodeapi.internal.config.MutualSslConfiguration import org.apache.activemq.artemis.api.core.RoutingType import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.client.ClientConsumer import org.apache.activemq.artemis.api.core.client.ClientMessage import java.util.* -class BridgeControlListener(val config: NodeSSLConfiguration, +class BridgeControlListener(val config: MutualSslConfiguration, maxMessageSize: Int, val artemisMessageClientFactory: () -> ArtemisSessionProvider) : AutoCloseable { private val bridgeId: String = UUID.randomUUID().toString() @@ -27,7 +27,7 @@ class BridgeControlListener(val config: NodeSSLConfiguration, private var artemis: ArtemisSessionProvider? = null private var controlConsumer: ClientConsumer? = null - constructor(config: NodeSSLConfiguration, + constructor(config: MutualSslConfiguration, p2pAddress: NetworkHostAndPort, maxMessageSize: Int) : this(config, maxMessageSize, { ArtemisMessagingClient(config, p2pAddress, maxMessageSize) }) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/CertificateStore.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/CertificateStore.kt new file mode 100644 index 0000000000..3ca6be6d6b --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/CertificateStore.kt @@ -0,0 +1,82 @@ +package net.corda.nodeapi.internal.config + +import net.corda.core.internal.outputStream +import net.corda.nodeapi.internal.crypto.X509KeyStore +import net.corda.nodeapi.internal.crypto.X509Utilities +import net.corda.nodeapi.internal.crypto.addOrReplaceCertificate +import java.io.InputStream +import java.io.OutputStream +import java.nio.file.OpenOption +import java.nio.file.Path +import java.security.cert.X509Certificate + +interface CertificateStore : Iterable> { + + companion object { + + fun of(store: X509KeyStore, password: String): CertificateStore = DelegatingCertificateStore(store, password) + + fun fromFile(storePath: Path, password: String, createNew: Boolean): CertificateStore = DelegatingCertificateStore(X509KeyStore.fromFile(storePath, password, createNew), password) + + fun fromInputStream(stream: InputStream, password: String): CertificateStore = DelegatingCertificateStore(X509KeyStore.fromInputStream(stream, password), password) + + fun fromResource(storeResourceName: String, password: String, classLoader: ClassLoader = Thread.currentThread().contextClassLoader): CertificateStore = fromInputStream(classLoader.getResourceAsStream(storeResourceName), password) + } + + val value: X509KeyStore + val password: String + + fun writeTo(stream: OutputStream) = value.internal.store(stream, password.toCharArray()) + + fun writeTo(path: Path, vararg options: OpenOption) = path.outputStream(*options) + + fun update(action: X509KeyStore.() -> Unit) { + val result = action.invoke(value) + value.save() + return result + } + + fun query(action: X509KeyStore.() -> RESULT): RESULT { + return action.invoke(value) + } + + operator fun set(alias: String, certificate: X509Certificate) { + + update { + internal.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, certificate) + } + } + + override fun iterator(): Iterator> { + + return query { + aliases() + }.asSequence().map { alias -> alias to get(alias) }.iterator() + } + + fun forEach(action: (alias: String, certificate: X509Certificate) -> Unit) { + + forEach { (alias, certificate) -> action.invoke(alias, certificate) } + } + + /** + * @throws IllegalArgumentException if no certificate for the alias is found, or if the certificate is not an [X509Certificate]. + */ + operator fun get(alias: String): X509Certificate { + + return query { + getCertificate(alias) + } + } + + operator fun contains(alias: String): Boolean = value.contains(alias) + + fun copyTo(certificateStore: CertificateStore) { + + certificateStore.update { + this@CertificateStore.forEach(::setCertificate) + } + } +} + +private class DelegatingCertificateStore(override val value: X509KeyStore, override val password: String) : CertificateStore \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/CertificateStoreSupplier.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/CertificateStoreSupplier.kt new file mode 100644 index 0000000000..3703742813 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/CertificateStoreSupplier.kt @@ -0,0 +1,24 @@ +package net.corda.nodeapi.internal.config + +import java.io.IOException +import java.nio.file.Path + +interface CertificateStoreSupplier { + + fun get(createNew: Boolean = false): CertificateStore + + fun getOptional(): CertificateStore? { + + return try { + get() + } catch (e: IOException) { + null + } + } +} + +// TODO replace reference to FileBasedCertificateStoreSupplier with CertificateStoreSupplier, after coming up with a way of passing certificate stores to Artemis. +class FileBasedCertificateStoreSupplier(val path: Path, val password: String) : CertificateStoreSupplier { + + override fun get(createNew: Boolean) = CertificateStore.fromFile(path, password, createNew) +} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/SSLConfiguration.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/SslConfiguration.kt similarity index 54% rename from node-api/src/main/kotlin/net/corda/nodeapi/internal/config/SSLConfiguration.kt rename to node-api/src/main/kotlin/net/corda/nodeapi/internal/config/SslConfiguration.kt index e8c63fc1f3..1011d49f9d 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/SSLConfiguration.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/SslConfiguration.kt @@ -4,12 +4,34 @@ import net.corda.core.internal.div import net.corda.nodeapi.internal.crypto.X509KeyStore import java.nio.file.Path +interface SslConfiguration { + + val keyStore: FileBasedCertificateStoreSupplier? + val trustStore: FileBasedCertificateStoreSupplier? + + companion object { + + fun mutual(keyStore: FileBasedCertificateStoreSupplier, trustStore: FileBasedCertificateStoreSupplier): MutualSslConfiguration { + + return MutualSslOptions(keyStore, trustStore) + } + } +} + +interface MutualSslConfiguration : SslConfiguration { + + override val keyStore: FileBasedCertificateStoreSupplier + override val trustStore: FileBasedCertificateStoreSupplier +} + +private class MutualSslOptions(override val keyStore: FileBasedCertificateStoreSupplier, override val trustStore: FileBasedCertificateStoreSupplier) : MutualSslConfiguration + +// Don't use this internally. It's still here because it's used by ArtemisTcpTransport, which is in public node-api by mistake. interface SSLConfiguration { val keyStorePassword: String val trustStorePassword: String val certificatesDirectory: Path val sslKeystore: Path get() = certificatesDirectory / "sslkeystore.jks" - // TODO This looks like it should be in NodeSSLConfiguration val nodeKeystore: Path get() = certificatesDirectory / "nodekeystore.jks" val trustStoreFile: Path get() = certificatesDirectory / "truststore.jks" val crlCheckSoftFail: Boolean @@ -25,9 +47,4 @@ interface SSLConfiguration { fun loadSslKeyStore(createNew: Boolean = false): X509KeyStore { return X509KeyStore.fromFile(sslKeystore, keyStorePassword, createNew) } -} - -interface NodeSSLConfiguration : SSLConfiguration { - val baseDirectory: Path - override val certificatesDirectory: Path get() = baseDirectory / "certificates" -} +} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509KeyStore.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509KeyStore.kt index 1fa56bebea..5c0c4b501b 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509KeyStore.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509KeyStore.kt @@ -2,6 +2,7 @@ package net.corda.nodeapi.internal.crypto import net.corda.core.crypto.Crypto import net.corda.core.internal.uncheckedCast +import java.io.InputStream import java.nio.file.Path import java.security.KeyPair import java.security.KeyStore @@ -30,6 +31,14 @@ class X509KeyStore private constructor(val internal: KeyStore, private val store val internal: KeyStore = if (createNew) loadOrCreateKeyStore(keyStoreFile, storePassword) else loadKeyStore(keyStoreFile, storePassword) return X509KeyStore(internal, storePassword, keyStoreFile) } + + /** + * Reads a [KeyStore] from an [InputStream]. + */ + fun fromInputStream(stream: InputStream, storePassword: String): X509KeyStore { + val internal = loadKeyStore(stream, storePassword) + return X509KeyStore(internal, storePassword) + } } operator fun contains(alias: String): Boolean = internal.containsAlias(alias) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPClient.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPClient.kt index 5c010014be..3be66c2824 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPClient.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPClient.kt @@ -116,7 +116,7 @@ class AMQPClient(val targets: List, private val conf = parent.configuration init { - keyManagerFactory.init(conf.keyStore, conf.keyStorePrivateKeyPassword) + keyManagerFactory.init(conf.keyStore) trustManagerFactory.init(initialiseTrustStoreAndEnableCrlChecking(conf.trustStore, conf.crlCheckSoftFail)) } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPConfiguration.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPConfiguration.kt index c2ba54286a..3b7289a8c5 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPConfiguration.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPConfiguration.kt @@ -1,6 +1,7 @@ package net.corda.nodeapi.internal.protonwrapper.netty import net.corda.nodeapi.internal.ArtemisMessagingComponent +import net.corda.nodeapi.internal.config.CertificateStore import java.security.KeyStore interface AMQPConfiguration { @@ -21,19 +22,14 @@ interface AMQPConfiguration { get() = ArtemisMessagingComponent.PEER_USER /** - * The keystore used for TLS connections + * The key store used for TLS connections */ - val keyStore: KeyStore + val keyStore: CertificateStore /** - * Password used to unlock TLS private keys in the KeyStore. + * The trust root key store to validate the peer certificates against */ - val keyStorePrivateKeyPassword: CharArray - - /** - * The trust root KeyStore to validate the peer certificates against - */ - val trustStore: KeyStore + val trustStore: CertificateStore /** * Setting crlCheckSoftFail to true allows certificate paths where some leaf certificates do not contain cRLDistributionPoints diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPServer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPServer.kt index 7c16a0547e..56c8b8bfda 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPServer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPServer.kt @@ -60,7 +60,7 @@ class AMQPServer(val hostName: String, private val conf = parent.configuration init { - keyManagerFactory.init(conf.keyStore, conf.keyStorePrivateKeyPassword) + keyManagerFactory.init(conf.keyStore) trustManagerFactory.init(initialiseTrustStoreAndEnableCrlChecking(conf.trustStore, conf.crlCheckSoftFail)) } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/SSLHelper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/SSLHelper.kt index 1f4328a8ee..6ffd49cc2d 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/SSLHelper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/SSLHelper.kt @@ -5,14 +5,13 @@ import net.corda.core.crypto.newSecureRandom import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger import net.corda.core.utilities.toHex -import net.corda.nodeapi.ArtemisTcpTransport +import net.corda.nodeapi.internal.InternalArtemisTcpTransport +import net.corda.nodeapi.internal.config.CertificateStore import net.corda.nodeapi.internal.crypto.toBc import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier import org.bouncycastle.asn1.x509.Extension import org.bouncycastle.asn1.x509.SubjectKeyIdentifier import java.net.Socket -import java.security.KeyStore -import java.security.SecureRandom import java.security.cert.* import java.util.* import javax.net.ssl.* @@ -111,8 +110,8 @@ internal fun createClientSslHelper(target: NetworkHostAndPort, sslContext.init(keyManagers, trustManagers, newSecureRandom()) val sslEngine = sslContext.createSSLEngine(target.host, target.port) sslEngine.useClientMode = true - sslEngine.enabledProtocols = ArtemisTcpTransport.TLS_VERSIONS.toTypedArray() - sslEngine.enabledCipherSuites = ArtemisTcpTransport.CIPHER_SUITES.toTypedArray() + sslEngine.enabledProtocols = InternalArtemisTcpTransport.TLS_VERSIONS.toTypedArray() + sslEngine.enabledCipherSuites = InternalArtemisTcpTransport.CIPHER_SUITES.toTypedArray() sslEngine.enableSessionCreation = true return SslHandler(sslEngine) } @@ -126,13 +125,13 @@ internal fun createServerSslHelper(keyManagerFactory: KeyManagerFactory, val sslEngine = sslContext.createSSLEngine() sslEngine.useClientMode = false sslEngine.needClientAuth = true - sslEngine.enabledProtocols = ArtemisTcpTransport.TLS_VERSIONS.toTypedArray() - sslEngine.enabledCipherSuites = ArtemisTcpTransport.CIPHER_SUITES.toTypedArray() + sslEngine.enabledProtocols = InternalArtemisTcpTransport.TLS_VERSIONS.toTypedArray() + sslEngine.enabledCipherSuites = InternalArtemisTcpTransport.CIPHER_SUITES.toTypedArray() sslEngine.enableSessionCreation = true return SslHandler(sslEngine) } -internal fun initialiseTrustStoreAndEnableCrlChecking(trustStore: KeyStore, crlCheckSoftFail: Boolean): ManagerFactoryParameters { +internal fun initialiseTrustStoreAndEnableCrlChecking(trustStore: CertificateStore, crlCheckSoftFail: Boolean): ManagerFactoryParameters { val certPathBuilder = CertPathBuilder.getInstance("PKIX") val revocationChecker = certPathBuilder.revocationChecker as PKIXRevocationChecker revocationChecker.options = EnumSet.of( @@ -145,7 +144,11 @@ internal fun initialiseTrustStoreAndEnableCrlChecking(trustStore: KeyStore, crlC // the following reasons: The CRL or OCSP response cannot be obtained because of a network error. revocationChecker.options = revocationChecker.options + PKIXRevocationChecker.Option.SOFT_FAIL } - val pkixParams = PKIXBuilderParameters(trustStore, X509CertSelector()) + val pkixParams = PKIXBuilderParameters(trustStore.value.internal, X509CertSelector()) pkixParams.addCertPathChecker(revocationChecker) return CertPathTrustManagerParameters(pkixParams) } + +fun KeyManagerFactory.init(keyStore: CertificateStore) = init(keyStore.value.internal, keyStore.password.toCharArray()) + +fun TrustManagerFactory.init(trustStore: CertificateStore) = init(trustStore.value.internal) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/DevCertificatesTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/DevCertificatesTest.kt index 4fdc43dd2d..e586a6c642 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/DevCertificatesTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/DevCertificatesTest.kt @@ -1,8 +1,7 @@ package net.corda.nodeapi.internal.crypto import net.corda.core.internal.validate -import net.corda.nodeapi.internal.DEV_CA_TRUST_STORE_FILE -import net.corda.nodeapi.internal.DEV_CA_TRUST_STORE_PASS +import net.corda.nodeapi.internal.loadDevCaTrustStore import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder @@ -22,8 +21,8 @@ class DevCertificatesTest { @Test fun `create server certificate in keystore for SSL`() { // given - val newTrustStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/$DEV_CA_TRUST_STORE_FILE"), DEV_CA_TRUST_STORE_PASS) - val newTrustRoot = newTrustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA) + val newTrustStore = loadDevCaTrustStore() + val newTrustRoot = newTrustStore[X509Utilities.CORDA_ROOT_CA] val newTrustAnchor = TrustAnchor(newTrustRoot, null) val oldNodeCaKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("regression-test/$OLD_NODE_DEV_KEYSTORE_FILE_NAME"), OLD_DEV_KEYSTORE_PASS) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt index 1df89e1070..d389e5b27d 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt @@ -10,8 +10,11 @@ import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.node.serialization.amqp.AMQPServerSerializationScheme -import net.corda.nodeapi.internal.config.SSLConfiguration -import net.corda.nodeapi.internal.createDevKeyStores +import net.corda.nodeapi.internal.config.MutualSslConfiguration +import net.corda.nodeapi.internal.createDevNodeCa +import net.corda.nodeapi.internal.protonwrapper.netty.init +import net.corda.nodeapi.internal.registerDevP2pCertificates +import net.corda.nodeapi.internal.registerDevSigningCertificates import net.corda.serialization.internal.AllWhitelist import net.corda.serialization.internal.SerializationContextImpl import net.corda.serialization.internal.SerializationFactoryImpl @@ -19,6 +22,7 @@ import net.corda.serialization.internal.amqp.amqpMagic import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.TestIdentity +import net.corda.testing.internal.stubs.CertificateStoreStubs import net.corda.testing.internal.createDevIntermediateCaCertPath import org.assertj.core.api.Assertions.assertThat import org.bouncycastle.asn1.x509.* @@ -31,7 +35,6 @@ import java.io.IOException import java.net.InetAddress import java.net.InetSocketAddress import java.nio.file.Path -import java.security.SecureRandom import java.security.cert.CertPath import java.security.cert.X509Certificate import java.util.* @@ -180,29 +183,27 @@ class X509UtilitiesTest { @Test fun `create server certificate in keystore for SSL`() { - val sslConfig = object : SSLConfiguration { - override val certificatesDirectory = tempFolder.root.toPath() - override val keyStorePassword = "serverstorepass" - override val trustStorePassword = "trustpass" - override val crlCheckSoftFail: Boolean = true - } + val certificatesDirectory = tempFolder.root.toPath() + val signingCertStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory, "serverstorepass") + val p2pSslConfig = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory, keyStorePassword = "serverstorepass") val (rootCa, intermediateCa) = createDevIntermediateCaCertPath() // Generate server cert and private key and populate another keystore suitable for SSL - sslConfig.createDevKeyStores(MEGA_CORP.name, rootCa.certificate, intermediateCa) - + val nodeCa = createDevNodeCa(intermediateCa, MEGA_CORP.name) + signingCertStore.get(createNew = true).also { it.registerDevSigningCertificates(MEGA_CORP.name, rootCa.certificate, intermediateCa, nodeCa) } + p2pSslConfig.keyStore.get(createNew = true).also { it.registerDevP2pCertificates(MEGA_CORP.name, rootCa.certificate, intermediateCa, nodeCa) } // Load back server certificate - val serverKeyStore = loadKeyStore(sslConfig.nodeKeystore, sslConfig.keyStorePassword) - val (serverCert, serverKeyPair) = serverKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, sslConfig.keyStorePassword) + val serverKeyStore = signingCertStore.get().value + val (serverCert, serverKeyPair) = serverKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA) serverCert.checkValidity() serverCert.verify(intermediateCa.certificate.publicKey) assertThat(CordaX500Name.build(serverCert.subjectX500Principal)).isEqualTo(MEGA_CORP.name) // Load back SSL certificate - val sslKeyStore = loadKeyStore(sslConfig.sslKeystore, sslConfig.keyStorePassword) - val (sslCert) = sslKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_TLS, sslConfig.keyStorePassword) + val sslKeyStoreReloaded = p2pSslConfig.keyStore.get() + val (sslCert) = sslKeyStoreReloaded.query { getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_TLS, p2pSslConfig.keyStore.password) } sslCert.checkValidity() sslCert.verify(serverCert.publicKey) @@ -216,25 +217,20 @@ class X509UtilitiesTest { @Test fun `create server cert and use in SSL socket`() { - val sslConfig = object : SSLConfiguration { - override val certificatesDirectory = tempFolder.root.toPath() - override val keyStorePassword = "serverstorepass" - override val trustStorePassword = "trustpass" - override val crlCheckSoftFail: Boolean = true - } + val sslConfig = CertificateStoreStubs.P2P.withCertificatesDirectory(tempFolder.root.toPath(), keyStorePassword = "serverstorepass") val (rootCa, intermediateCa) = createDevIntermediateCaCertPath() // Generate server cert and private key and populate another keystore suitable for SSL - sslConfig.createDevKeyStores(MEGA_CORP.name, rootCa.certificate, intermediateCa) + sslConfig.keyStore.get(true).registerDevP2pCertificates(MEGA_CORP.name, rootCa.certificate, intermediateCa) sslConfig.createTrustStore(rootCa.certificate) - val keyStore = loadKeyStore(sslConfig.sslKeystore, sslConfig.keyStorePassword) - val trustStore = loadKeyStore(sslConfig.trustStoreFile, sslConfig.trustStorePassword) + val keyStore = sslConfig.keyStore.get() + val trustStore = sslConfig.trustStore.get() val context = SSLContext.getInstance("TLS") val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) - keyManagerFactory.init(keyStore, sslConfig.keyStorePassword.toCharArray()) + keyManagerFactory.init(keyStore) val keyManagers = keyManagerFactory.keyManagers val trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) trustMgrFactory.init(trustStore) @@ -313,10 +309,9 @@ class X509UtilitiesTest { private fun tempFile(name: String): Path = tempFolder.root.toPath() / name - private fun SSLConfiguration.createTrustStore(rootCert: X509Certificate) { - val trustStore = loadOrCreateKeyStore(trustStoreFile, trustStorePassword) - trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCert) - trustStore.save(trustStoreFile, trustStorePassword) + private fun MutualSslConfiguration.createTrustStore(rootCert: X509Certificate) { + val trustStore = this.trustStore.get(true) + trustStore[X509Utilities.CORDA_ROOT_CA] = rootCert } @Test diff --git a/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt b/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt index 7800bf67b2..9bf90c5a48 100644 --- a/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt @@ -4,15 +4,14 @@ import net.corda.core.crypto.Crypto import net.corda.core.internal.div import net.corda.core.utilities.getOrThrow import net.corda.node.services.config.configureDevKeyAndTrustStores -import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.testing.core.ALICE_NAME import net.corda.testing.driver.DriverParameters import net.corda.testing.driver.driver +import net.corda.testing.internal.stubs.CertificateStoreStubs import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Test -import java.nio.file.Path import javax.security.auth.x500.X500Principal class NodeKeystoreCheckTest { @@ -30,13 +29,11 @@ class NodeKeystoreCheckTest { driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) { // Create keystores val keystorePassword = "password" - val config = object : SSLConfiguration { - override val keyStorePassword: String = keystorePassword - override val trustStorePassword: String = keystorePassword - override val certificatesDirectory: Path = baseDirectory(ALICE_NAME) / "certificates" - override val crlCheckSoftFail: Boolean = true - } - config.configureDevKeyAndTrustStores(ALICE_NAME) + val certificatesDirectory = baseDirectory(ALICE_NAME) / "certificates" + val signingCertStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory, keystorePassword) + val p2pSslConfig = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory, keyStorePassword = keystorePassword, trustStorePassword = keystorePassword) + + p2pSslConfig.configureDevKeyAndTrustStores(ALICE_NAME, signingCertStore, certificatesDirectory) // This should pass with correct keystore. val node = startNode( @@ -48,7 +45,7 @@ class NodeKeystoreCheckTest { node.stop() // Fiddle with node keystore. - config.loadNodeKeyStore().update { + signingCertStore.get().update { // Self signed root val badRootKeyPair = Crypto.generateKeyPair() val badRoot = X509Utilities.createSelfSignedCACertificate(X500Principal("O=Bad Root,L=Lodnon,C=GB"), badRootKeyPair) diff --git a/node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt b/node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt index 1d7c8a4112..1563abb0c8 100644 --- a/node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt @@ -19,6 +19,7 @@ import net.corda.testing.core.BOB_NAME import net.corda.testing.core.MAX_MESSAGE_SIZE import net.corda.testing.core.TestIdentity import net.corda.testing.driver.PortAllocation +import net.corda.testing.internal.stubs.CertificateStoreStubs import net.corda.testing.internal.rigorousMock import org.apache.activemq.artemis.api.core.Message.HDR_DUPLICATE_DETECTION_ID import org.apache.activemq.artemis.api.core.RoutingType @@ -27,7 +28,6 @@ import org.junit.Assert.assertArrayEquals import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder -import java.security.KeyStore import java.util.* import kotlin.test.assertEquals @@ -168,21 +168,26 @@ class AMQPBridgeTest { } private fun createArtemis(sourceQueueName: String?): Triple { + val baseDir = temporaryFolder.root.toPath() / "artemis" + val certificatesDirectory = baseDir / "certificates" + val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory) + val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory) val artemisConfig = rigorousMock().also { - doReturn(temporaryFolder.root.toPath() / "artemis").whenever(it).baseDirectory + doReturn(baseDir).whenever(it).baseDirectory doReturn(ALICE_NAME).whenever(it).myLegalName - doReturn("trustpass").whenever(it).trustStorePassword + doReturn(certificatesDirectory).whenever(it).certificatesDirectory + doReturn(signingCertificateStore).whenever(it).signingCertificateStore + doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions doReturn(true).whenever(it).crlCheckSoftFail - doReturn("cordacadevpass").whenever(it).keyStorePassword doReturn(artemisAddress).whenever(it).p2pAddress doReturn(null).whenever(it).jmxMonitoringHttpPort } artemisConfig.configureWithDevSSLCertificate() val artemisServer = ArtemisMessagingServer(artemisConfig, artemisAddress.copy(host = "0.0.0.0"), MAX_MESSAGE_SIZE) - val artemisClient = ArtemisMessagingClient(artemisConfig, artemisAddress, MAX_MESSAGE_SIZE) + val artemisClient = ArtemisMessagingClient(artemisConfig.p2pSslOptions, artemisAddress, MAX_MESSAGE_SIZE) artemisServer.start() artemisClient.start() - val bridgeManager = AMQPBridgeManager(artemisConfig, artemisAddress, MAX_MESSAGE_SIZE) + val bridgeManager = AMQPBridgeManager(artemisConfig.p2pSslOptions, artemisAddress, MAX_MESSAGE_SIZE) bridgeManager.start() val artemis = artemisClient.started!! if (sourceQueueName != null) { @@ -194,18 +199,23 @@ class AMQPBridgeTest { } private fun createAMQPServer(maxMessageSize: Int = MAX_MESSAGE_SIZE): AMQPServer { + val baseDir = temporaryFolder.root.toPath() / "server" + val certificatesDirectory = baseDir / "certificates" + val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory) + val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory) val serverConfig = rigorousMock().also { doReturn(temporaryFolder.root.toPath() / "server").whenever(it).baseDirectory doReturn(BOB_NAME).whenever(it).myLegalName - doReturn("trustpass").whenever(it).trustStorePassword - doReturn("cordacadevpass").whenever(it).keyStorePassword + doReturn(certificatesDirectory).whenever(it).certificatesDirectory + doReturn(signingCertificateStore).whenever(it).signingCertificateStore + doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions } serverConfig.configureWithDevSSLCertificate() + val keyStore = serverConfig.p2pSslOptions.keyStore.get() val amqpConfig = object : AMQPConfiguration { - override val keyStore: KeyStore = serverConfig.loadSslKeyStore().internal - override val keyStorePrivateKeyPassword: CharArray = serverConfig.keyStorePassword.toCharArray() - override val trustStore: KeyStore = serverConfig.loadTrustStore().internal + override val keyStore = keyStore + override val trustStore = serverConfig.p2pSslOptions.trustStore.get() override val trace: Boolean = true override val maxMessageSize: Int = maxMessageSize } diff --git a/node/src/integration-test/kotlin/net/corda/node/amqp/CertificateRevocationListNodeTests.kt b/node/src/integration-test/kotlin/net/corda/node/amqp/CertificateRevocationListNodeTests.kt index cb766bbcf8..d2ef1dbe5c 100644 --- a/node/src/integration-test/kotlin/net/corda/node/amqp/CertificateRevocationListNodeTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/amqp/CertificateRevocationListNodeTests.kt @@ -13,7 +13,8 @@ import net.corda.core.utilities.seconds import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.configureWithDevSSLCertificate import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX -import net.corda.nodeapi.internal.config.SSLConfiguration +import net.corda.nodeapi.internal.config.CertificateStoreSupplier +import net.corda.nodeapi.internal.config.MutualSslConfiguration import net.corda.nodeapi.internal.crypto.* import net.corda.nodeapi.internal.protonwrapper.messages.MessageStatus import net.corda.nodeapi.internal.protonwrapper.netty.AMQPClient @@ -24,6 +25,7 @@ import net.corda.testing.core.BOB_NAME import net.corda.testing.core.CHARLIE_NAME import net.corda.testing.core.MAX_MESSAGE_SIZE import net.corda.testing.driver.PortAllocation +import net.corda.testing.internal.stubs.CertificateStoreStubs import net.corda.testing.internal.DEV_INTERMEDIATE_CA import net.corda.testing.internal.DEV_ROOT_CA import net.corda.testing.internal.rigorousMock @@ -50,7 +52,6 @@ import java.io.Closeable import java.math.BigInteger import java.net.InetSocketAddress import java.security.KeyPair -import java.security.KeyStore import java.security.PrivateKey import java.security.Security import java.security.cert.X509CRL @@ -330,22 +331,25 @@ class CertificateRevocationListNodeTests { nodeCrlDistPoint: String = "http://${server.hostAndPort}/crl/node.crl", tlsCrlDistPoint: String? = "http://${server.hostAndPort}/crl/empty.crl", maxMessageSize: Int = MAX_MESSAGE_SIZE): Pair { + val baseDirectory = temporaryFolder.root.toPath() / "client" + val certificatesDirectory = baseDirectory / "certificates" + val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory) + val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory) val clientConfig = rigorousMock().also { - doReturn(temporaryFolder.root.toPath() / "client").whenever(it).baseDirectory + doReturn(baseDirectory).whenever(it).baseDirectory + doReturn(certificatesDirectory).whenever(it).certificatesDirectory doReturn(BOB_NAME).whenever(it).myLegalName - doReturn("trustpass").whenever(it).trustStorePassword - doReturn("cordacadevpass").whenever(it).keyStorePassword + doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions + doReturn(signingCertificateStore).whenever(it).signingCertificateStore doReturn(crlCheckSoftFail).whenever(it).crlCheckSoftFail } clientConfig.configureWithDevSSLCertificate() - val nodeCert = clientConfig.recreateNodeCaAndTlsCertificates(nodeCrlDistPoint, tlsCrlDistPoint) - val clientTruststore = clientConfig.loadTrustStore().internal - val clientKeystore = clientConfig.loadSslKeyStore().internal + val nodeCert = (signingCertificateStore to p2pSslConfiguration).recreateNodeCaAndTlsCertificates(nodeCrlDistPoint, tlsCrlDistPoint) + val keyStore = clientConfig.p2pSslOptions.keyStore.get() val amqpConfig = object : AMQPConfiguration { - override val keyStore: KeyStore = clientKeystore - override val keyStorePrivateKeyPassword: CharArray = clientConfig.keyStorePassword.toCharArray() - override val trustStore: KeyStore = clientTruststore + override val keyStore = keyStore + override val trustStore = clientConfig.p2pSslOptions.trustStore.get() override val crlCheckSoftFail: Boolean = crlCheckSoftFail override val maxMessageSize: Int = maxMessageSize } @@ -360,21 +364,24 @@ class CertificateRevocationListNodeTests { nodeCrlDistPoint: String = "http://${server.hostAndPort}/crl/node.crl", tlsCrlDistPoint: String? = "http://${server.hostAndPort}/crl/empty.crl", maxMessageSize: Int = MAX_MESSAGE_SIZE): Pair { + val baseDirectory = temporaryFolder.root.toPath() / "server" + val certificatesDirectory = baseDirectory / "certificates" + val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory) + val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory) val serverConfig = rigorousMock().also { - doReturn(temporaryFolder.root.toPath() / "server").whenever(it).baseDirectory + doReturn(baseDirectory).whenever(it).baseDirectory + doReturn(certificatesDirectory).whenever(it).certificatesDirectory doReturn(name).whenever(it).myLegalName - doReturn("trustpass").whenever(it).trustStorePassword - doReturn("cordacadevpass").whenever(it).keyStorePassword + doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions + doReturn(signingCertificateStore).whenever(it).signingCertificateStore doReturn(crlCheckSoftFail).whenever(it).crlCheckSoftFail } serverConfig.configureWithDevSSLCertificate() - val nodeCert = serverConfig.recreateNodeCaAndTlsCertificates(nodeCrlDistPoint, tlsCrlDistPoint) - val serverTruststore = serverConfig.loadTrustStore().internal - val serverKeystore = serverConfig.loadSslKeyStore().internal + val nodeCert = (signingCertificateStore to p2pSslConfiguration).recreateNodeCaAndTlsCertificates(nodeCrlDistPoint, tlsCrlDistPoint) + val keyStore = serverConfig.p2pSslOptions.keyStore.get() val amqpConfig = object : AMQPConfiguration { - override val keyStore: KeyStore = serverKeystore - override val keyStorePrivateKeyPassword: CharArray = serverConfig.keyStorePassword.toCharArray() - override val trustStore: KeyStore = serverTruststore + override val keyStore = keyStore + override val trustStore = serverConfig.p2pSslOptions.trustStore.get() override val crlCheckSoftFail: Boolean = crlCheckSoftFail override val maxMessageSize: Int = maxMessageSize } @@ -384,22 +391,28 @@ class CertificateRevocationListNodeTests { amqpConfig), nodeCert) } - private fun SSLConfiguration.recreateNodeCaAndTlsCertificates(nodeCaCrlDistPoint: String, tlsCrlDistPoint: String?): X509Certificate { - val nodeKeyStore = loadNodeKeyStore() - val (nodeCert, nodeKeys) = nodeKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA) + private fun Pair.recreateNodeCaAndTlsCertificates(nodeCaCrlDistPoint: String, tlsCrlDistPoint: String?): X509Certificate { + + val signingCertificateStore = first + val p2pSslConfiguration = second + val nodeKeyStore = signingCertificateStore.get() + val (nodeCert, nodeKeys) = nodeKeyStore.query { getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA) } val newNodeCert = replaceCrlDistPointCaCertificate(nodeCert, CertificateType.NODE_CA, INTERMEDIATE_CA.keyPair, nodeCaCrlDistPoint) - val nodeCertChain = listOf(newNodeCert, INTERMEDIATE_CA.certificate, *nodeKeyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA).drop(2).toTypedArray()) - nodeKeyStore.internal.deleteEntry(X509Utilities.CORDA_CLIENT_CA) - nodeKeyStore.save() + val nodeCertChain = listOf(newNodeCert, INTERMEDIATE_CA.certificate, *nodeKeyStore.query { getCertificateChain(X509Utilities.CORDA_CLIENT_CA) }.drop(2).toTypedArray()) + nodeKeyStore.update { + internal.deleteEntry(X509Utilities.CORDA_CLIENT_CA) + } nodeKeyStore.update { setPrivateKey(X509Utilities.CORDA_CLIENT_CA, nodeKeys.private, nodeCertChain) } - val sslKeyStore = loadSslKeyStore() - val (tlsCert, tlsKeys) = sslKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_TLS) + val sslKeyStore = p2pSslConfiguration.keyStore.get() + val (tlsCert, tlsKeys) = sslKeyStore.query { getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_TLS) } val newTlsCert = replaceCrlDistPointCaCertificate(tlsCert, CertificateType.TLS, nodeKeys, tlsCrlDistPoint, X500Name.getInstance(ROOT_CA.certificate.subjectX500Principal.encoded)) - val sslCertChain = listOf(newTlsCert, newNodeCert, INTERMEDIATE_CA.certificate, *sslKeyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).drop(3).toTypedArray()) - sslKeyStore.internal.deleteEntry(X509Utilities.CORDA_CLIENT_TLS) - sslKeyStore.save() + val sslCertChain = listOf(newTlsCert, newNodeCert, INTERMEDIATE_CA.certificate, *sslKeyStore.query { getCertificateChain(X509Utilities.CORDA_CLIENT_TLS) }.drop(3).toTypedArray()) + + sslKeyStore.update { + internal.deleteEntry(X509Utilities.CORDA_CLIENT_TLS) + } sslKeyStore.update { setPrivateKey(X509Utilities.CORDA_CLIENT_TLS, tlsKeys.private, sslCertChain) } diff --git a/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt b/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt index 93016870ce..5ef36c2372 100644 --- a/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt @@ -12,16 +12,18 @@ import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.configureWithDevSSLCertificate import net.corda.node.services.messaging.ArtemisMessagingServer -import net.corda.nodeapi.ArtemisTcpTransport.Companion.CIPHER_SUITES import net.corda.nodeapi.internal.ArtemisMessagingClient import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX -import net.corda.nodeapi.internal.config.SSLConfiguration -import net.corda.nodeapi.internal.createDevKeyStores +import net.corda.nodeapi.internal.InternalArtemisTcpTransport +import net.corda.nodeapi.internal.config.MutualSslConfiguration +import net.corda.nodeapi.internal.registerDevP2pCertificates import net.corda.nodeapi.internal.crypto.* import net.corda.nodeapi.internal.protonwrapper.messages.MessageStatus import net.corda.nodeapi.internal.protonwrapper.netty.AMQPClient import net.corda.nodeapi.internal.protonwrapper.netty.AMQPConfiguration import net.corda.nodeapi.internal.protonwrapper.netty.AMQPServer +import net.corda.nodeapi.internal.protonwrapper.netty.init +import net.corda.nodeapi.internal.registerDevSigningCertificates import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.CHARLIE_NAME @@ -29,13 +31,13 @@ import net.corda.testing.core.MAX_MESSAGE_SIZE import net.corda.testing.driver.PortAllocation import net.corda.testing.internal.createDevIntermediateCaCertPath import net.corda.testing.internal.rigorousMock +import net.corda.testing.internal.stubs.CertificateStoreStubs import org.apache.activemq.artemis.api.core.RoutingType import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Assert.assertArrayEquals import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder -import java.security.KeyStore import java.security.cert.X509Certificate import javax.net.ssl.* import kotlin.concurrent.thread @@ -102,34 +104,30 @@ class ProtonWrapperTests { } } - private fun SSLConfiguration.createTrustStore(rootCert: X509Certificate) { - val trustStore = loadOrCreateKeyStore(trustStoreFile, trustStorePassword) - trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCert) - trustStore.save(trustStoreFile, trustStorePassword) - } + private fun MutualSslConfiguration.createTrustStore(rootCert: X509Certificate) { + trustStore.get(true)[X509Utilities.CORDA_ROOT_CA] = rootCert + } @Test fun `Test AMQP Client with invalid root certificate`() { - val sslConfig = object : SSLConfiguration { - override val certificatesDirectory = temporaryFolder.root.toPath() - override val keyStorePassword = "serverstorepass" - override val trustStorePassword = "trustpass" - override val crlCheckSoftFail: Boolean = true - } + val certificatesDirectory = temporaryFolder.root.toPath() + val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory, "serverstorepass") + val sslConfig = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory, keyStorePassword = "serverstorepass") val (rootCa, intermediateCa) = createDevIntermediateCaCertPath() // Generate server cert and private key and populate another keystore suitable for SSL - sslConfig.createDevKeyStores(ALICE_NAME, rootCa.certificate, intermediateCa) + signingCertificateStore.get(true).also { it.registerDevSigningCertificates(ALICE_NAME, rootCa.certificate, intermediateCa) } + sslConfig.keyStore.get(true).also { it.registerDevP2pCertificates(ALICE_NAME, rootCa.certificate, intermediateCa) } sslConfig.createTrustStore(rootCa.certificate) - val keyStore = loadKeyStore(sslConfig.sslKeystore, sslConfig.keyStorePassword) - val trustStore = loadKeyStore(sslConfig.trustStoreFile, sslConfig.trustStorePassword) + val keyStore = sslConfig.keyStore.get() + val trustStore = sslConfig.trustStore.get() val context = SSLContext.getInstance("TLS") val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) - keyManagerFactory.init(keyStore, sslConfig.keyStorePassword.toCharArray()) + keyManagerFactory.init(keyStore) val keyManagers = keyManagerFactory.keyManagers val trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) trustMgrFactory.init(trustStore) @@ -139,7 +137,7 @@ class ProtonWrapperTests { val serverSocketFactory = context.serverSocketFactory val serverSocket = serverSocketFactory.createServerSocket(serverPort) as SSLServerSocket - val serverParams = SSLParameters(CIPHER_SUITES.toTypedArray(), + val serverParams = SSLParameters(InternalArtemisTcpTransport.CIPHER_SUITES.toTypedArray(), arrayOf("TLSv1.2")) serverParams.wantClientAuth = true serverParams.needClientAuth = true @@ -388,11 +386,16 @@ class ProtonWrapperTests { } private fun createArtemisServerAndClient(maxMessageSize: Int = MAX_MESSAGE_SIZE): Pair { + val baseDirectory = temporaryFolder.root.toPath() / "artemis" + val certificatesDirectory = baseDirectory / "certificates" + val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory) + val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory) val artemisConfig = rigorousMock().also { - doReturn(temporaryFolder.root.toPath() / "artemis").whenever(it).baseDirectory + doReturn(baseDirectory).whenever(it).baseDirectory + doReturn(certificatesDirectory).whenever(it).certificatesDirectory doReturn(CHARLIE_NAME).whenever(it).myLegalName - doReturn("trustpass").whenever(it).trustStorePassword - doReturn("cordacadevpass").whenever(it).keyStorePassword + doReturn(signingCertificateStore).whenever(it).signingCertificateStore + doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions doReturn(NetworkHostAndPort("0.0.0.0", artemisPort)).whenever(it).p2pAddress doReturn(null).whenever(it).jmxMonitoringHttpPort doReturn(true).whenever(it).crlCheckSoftFail @@ -400,28 +403,32 @@ class ProtonWrapperTests { artemisConfig.configureWithDevSSLCertificate() val server = ArtemisMessagingServer(artemisConfig, NetworkHostAndPort("0.0.0.0", artemisPort), maxMessageSize) - val client = ArtemisMessagingClient(artemisConfig, NetworkHostAndPort("localhost", artemisPort), maxMessageSize) + val client = ArtemisMessagingClient(artemisConfig.p2pSslOptions, NetworkHostAndPort("localhost", artemisPort), maxMessageSize) server.start() client.start() return Pair(server, client) } private fun createClient(maxMessageSize: Int = MAX_MESSAGE_SIZE): AMQPClient { + val baseDirectory = temporaryFolder.root.toPath() / "client" + val certificatesDirectory = baseDirectory / "certificates" + val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory) + val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory) val clientConfig = rigorousMock().also { - doReturn(temporaryFolder.root.toPath() / "client").whenever(it).baseDirectory + doReturn(baseDirectory).whenever(it).baseDirectory + doReturn(certificatesDirectory).whenever(it).certificatesDirectory doReturn(BOB_NAME).whenever(it).myLegalName - doReturn("trustpass").whenever(it).trustStorePassword - doReturn("cordacadevpass").whenever(it).keyStorePassword + doReturn(signingCertificateStore).whenever(it).signingCertificateStore + doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions doReturn(true).whenever(it).crlCheckSoftFail } clientConfig.configureWithDevSSLCertificate() - val clientTruststore = clientConfig.loadTrustStore().internal - val clientKeystore = clientConfig.loadSslKeyStore().internal + val clientTruststore = clientConfig.p2pSslOptions.trustStore.get() + val clientKeystore = clientConfig.p2pSslOptions.keyStore.get() val amqpConfig = object : AMQPConfiguration { - override val keyStore: KeyStore = clientKeystore - override val keyStorePrivateKeyPassword: CharArray = clientConfig.keyStorePassword.toCharArray() - override val trustStore: KeyStore = clientTruststore + override val keyStore = clientKeystore + override val trustStore = clientTruststore override val trace: Boolean = true override val maxMessageSize: Int = maxMessageSize } @@ -434,21 +441,25 @@ class ProtonWrapperTests { } private fun createSharedThreadsClient(sharedEventGroup: EventLoopGroup, id: Int, maxMessageSize: Int = MAX_MESSAGE_SIZE): AMQPClient { + val baseDirectory = temporaryFolder.root.toPath() / "client_%$id" + val certificatesDirectory = baseDirectory / "certificates" + val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory) + val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory) val clientConfig = rigorousMock().also { - doReturn(temporaryFolder.root.toPath() / "client_%$id").whenever(it).baseDirectory + doReturn(baseDirectory).whenever(it).baseDirectory + doReturn(certificatesDirectory).whenever(it).certificatesDirectory doReturn(CordaX500Name(null, "client $id", "Corda", "London", null, "GB")).whenever(it).myLegalName - doReturn("trustpass").whenever(it).trustStorePassword - doReturn("cordacadevpass").whenever(it).keyStorePassword + doReturn(signingCertificateStore).whenever(it).signingCertificateStore + doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions doReturn(true).whenever(it).crlCheckSoftFail } clientConfig.configureWithDevSSLCertificate() - val clientTruststore = clientConfig.loadTrustStore().internal - val clientKeystore = clientConfig.loadSslKeyStore().internal + val clientTruststore = clientConfig.p2pSslOptions.trustStore.get() + val clientKeystore = clientConfig.p2pSslOptions.keyStore.get() val amqpConfig = object : AMQPConfiguration { - override val keyStore: KeyStore = clientKeystore - override val keyStorePrivateKeyPassword: CharArray = clientConfig.keyStorePassword.toCharArray() - override val trustStore: KeyStore = clientTruststore + override val keyStore = clientKeystore + override val trustStore = clientTruststore override val trace: Boolean = true override val maxMessageSize: Int = maxMessageSize } @@ -460,21 +471,25 @@ class ProtonWrapperTests { } private fun createServer(port: Int, name: CordaX500Name = ALICE_NAME, maxMessageSize: Int = MAX_MESSAGE_SIZE): AMQPServer { + val baseDirectory = temporaryFolder.root.toPath() / "server" + val certificatesDirectory = baseDirectory / "certificates" + val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory) + val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory) val serverConfig = rigorousMock().also { - doReturn(temporaryFolder.root.toPath() / "server").whenever(it).baseDirectory + doReturn(baseDirectory).whenever(it).baseDirectory + doReturn(certificatesDirectory).whenever(it).certificatesDirectory doReturn(name).whenever(it).myLegalName - doReturn("trustpass").whenever(it).trustStorePassword - doReturn("cordacadevpass").whenever(it).keyStorePassword + doReturn(signingCertificateStore).whenever(it).signingCertificateStore + doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions doReturn(true).whenever(it).crlCheckSoftFail } serverConfig.configureWithDevSSLCertificate() - val serverTruststore = serverConfig.loadTrustStore().internal - val serverKeystore = serverConfig.loadSslKeyStore().internal + val serverTruststore = serverConfig.p2pSslOptions.trustStore.get() + val serverKeystore = serverConfig.p2pSslOptions.keyStore.get() val amqpConfig = object : AMQPConfiguration { - override val keyStore: KeyStore = serverKeystore - override val keyStorePrivateKeyPassword: CharArray = serverConfig.keyStorePassword.toCharArray() - override val trustStore: KeyStore = serverTruststore + override val keyStore = serverKeystore + override val trustStore = serverTruststore override val trace: Boolean = true override val maxMessageSize: Int = maxMessageSize } diff --git a/node/src/integration-test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt index 0121d8be47..4f3e80594b 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt @@ -3,6 +3,7 @@ package net.corda.node.services.messaging import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.crypto.generateKeyPair +import net.corda.core.internal.div import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.seconds import net.corda.node.internal.configureDatabase @@ -19,6 +20,7 @@ import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.MAX_MESSAGE_SIZE import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.driver.PortAllocation +import net.corda.testing.internal.stubs.CertificateStoreStubs import net.corda.testing.internal.LogHelper import net.corda.testing.internal.rigorousMock import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties @@ -69,11 +71,18 @@ class ArtemisMessagingTest { @Before fun setUp() { abstract class AbstractNodeConfiguration : NodeConfiguration + + val baseDirectory = temporaryFolder.root.toPath() + val certificatesDirectory = baseDirectory / "certificates" + val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory) + val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory) + config = rigorousMock().also { doReturn(temporaryFolder.root.toPath()).whenever(it).baseDirectory doReturn(ALICE_NAME).whenever(it).myLegalName - doReturn("trustpass").whenever(it).trustStorePassword - doReturn("cordacadevpass").whenever(it).keyStorePassword + doReturn(certificatesDirectory).whenever(it).certificatesDirectory + doReturn(signingCertificateStore).whenever(it).signingCertificateStore + doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions doReturn(NetworkHostAndPort("0.0.0.0", serverPort)).whenever(it).p2pAddress doReturn(null).whenever(it).jmxMonitoringHttpPort doReturn(FlowTimeoutConfiguration(5.seconds, 3, backoffBase = 1.0)).whenever(it).flowTimeout diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt index 91d4299702..3573ba6c9f 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt @@ -12,7 +12,6 @@ import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.seconds import net.corda.node.services.config.configureDevKeyAndTrustStores import net.corda.nodeapi.internal.NODE_INFO_DIRECTORY -import net.corda.nodeapi.internal.config.NodeSSLConfiguration import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME import net.corda.nodeapi.internal.network.SignedNetworkParameters @@ -21,6 +20,7 @@ import net.corda.testing.core.* import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.PortAllocation import net.corda.testing.driver.internal.NodeHandleInternal +import net.corda.testing.internal.stubs.CertificateStoreStubs import net.corda.testing.node.internal.* import net.corda.testing.node.internal.network.NetworkMapServer import org.assertj.core.api.Assertions.assertThat @@ -247,13 +247,10 @@ private fun DriverDSLImpl.startNode(providedName: CordaX500Name, devMode: Boolea var customOverrides = emptyMap() if (!devMode) { val nodeDir = baseDirectory(providedName) - val nodeSslConfig = object : NodeSSLConfiguration { - override val baseDirectory = nodeDir - override val keyStorePassword = "cordacadevpass" - override val trustStorePassword = "trustpass" - override val crlCheckSoftFail = true - } - nodeSslConfig.configureDevKeyAndTrustStores(providedName) + val certificatesDirectory = nodeDir / "certificates" + val signingCertStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory) + val p2pSslConfig = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory) + p2pSslConfig.configureDevKeyAndTrustStores(providedName, signingCertStore, certificatesDirectory) customOverrides = mapOf("devMode" to "false") } return startNode(providedName = providedName, customOverrides = customOverrides) diff --git a/node/src/integration-test/kotlin/net/corda/node/services/rpc/ArtemisRpcTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/rpc/ArtemisRpcTests.kt index 9a6192b4b1..c64a3773f5 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/rpc/ArtemisRpcTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/rpc/ArtemisRpcTests.kt @@ -17,13 +17,13 @@ import net.corda.node.services.messaging.RPCServerConfiguration import net.corda.node.utilities.createKeyPairAndSelfSignedTLSCertificate import net.corda.node.utilities.saveToKeyStore import net.corda.node.utilities.saveToTrustStore -import net.corda.nodeapi.ArtemisTcpTransport.Companion.rpcConnectorTcpTransport import net.corda.nodeapi.BrokerRpcSslOptions -import net.corda.nodeapi.internal.config.SSLConfiguration +import net.corda.nodeapi.internal.InternalArtemisTcpTransport.Companion.rpcConnectorTcpTransport +import net.corda.nodeapi.internal.config.MutualSslConfiguration import net.corda.nodeapi.internal.config.User import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.driver.PortAllocation -import net.corda.testing.internal.createNodeSslConfig +import net.corda.testing.internal.p2pSslOptions import org.apache.activemq.artemis.api.core.ActiveMQConnectionTimedOutException import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl import org.assertj.core.api.Assertions.assertThat @@ -58,12 +58,12 @@ class ArtemisRpcTests { val brokerSslOptions = BrokerRpcSslOptions(keyStorePath, "password") val trustStorePath = saveToTrustStore(tempFile("rpcTruststore.jks"), selfSignCert) val clientSslOptions = ClientRpcSslOptions(trustStorePath, "password") - testSslCommunication(createNodeSslConfig(tempFolder.root.toPath()), brokerSslOptions, true, clientSslOptions) + testSslCommunication(p2pSslOptions(tempFolder.root.toPath()), brokerSslOptions, true, clientSslOptions) } @Test fun rpc_with_ssl_disabled() { - testSslCommunication(createNodeSslConfig(tempFolder.root.toPath()), null, false, null) + testSslCommunication(p2pSslOptions(tempFolder.root.toPath()), null, false, null) } @Test @@ -73,7 +73,7 @@ class ArtemisRpcTests { val brokerSslOptions = BrokerRpcSslOptions(keyStorePath, "password") // here client sslOptions are passed null (as in, do not use SSL) assertThatThrownBy { - testSslCommunication(createNodeSslConfig(tempFolder.root.toPath()), brokerSslOptions, true, null) + testSslCommunication(p2pSslOptions(tempFolder.root.toPath()), brokerSslOptions, true, null) }.isInstanceOf(ActiveMQConnectionTimedOutException::class.java) } @@ -91,11 +91,11 @@ class ArtemisRpcTests { val clientSslOptions = ClientRpcSslOptions(trustStorePath, "password") assertThatThrownBy { - testSslCommunication(createNodeSslConfig(tempFolder.root.toPath()), brokerSslOptions, true, clientSslOptions) + testSslCommunication(p2pSslOptions(tempFolder.root.toPath()), brokerSslOptions, true, clientSslOptions) }.isInstanceOf(RPCException::class.java) } - private fun testSslCommunication(nodeSSlconfig: SSLConfiguration, + private fun testSslCommunication(nodeSSlconfig: MutualSslConfiguration, brokerSslOptions: BrokerRpcSslOptions?, useSslForBroker: Boolean, clientSslOptions: ClientRpcSslOptions?, diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt index feda5b405f..7fb2986bc4 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt @@ -2,7 +2,6 @@ package net.corda.services.messaging import net.corda.core.crypto.Crypto import net.corda.core.identity.CordaX500Name -import net.corda.core.internal.copyTo import net.corda.core.internal.createDirectories import net.corda.core.internal.exists import net.corda.core.internal.toX500Name @@ -11,9 +10,10 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_P2P_U import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER import net.corda.nodeapi.internal.DEV_INTERMEDIATE_CA import net.corda.nodeapi.internal.DEV_ROOT_CA -import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509Utilities +import net.corda.nodeapi.internal.loadDevCaTrustStore +import net.corda.testing.internal.stubs.CertificateStoreStubs import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration import org.apache.activemq.artemis.api.core.ActiveMQClusterSecurityException import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException @@ -84,57 +84,35 @@ class MQSecurityAsNodeTest : P2PMQSecurityTest() { @Test fun `login with invalid certificate chain`() { - val sslConfig = object : SSLConfiguration { - override val certificatesDirectory = Files.createTempDirectory("certs") - override val keyStorePassword: String get() = "cordacadevpass" - override val trustStorePassword: String get() = "trustpass" - override val crlCheckSoftFail: Boolean = true + val certsDir = Files.createTempDirectory("certs") + certsDir.createDirectories() + val signingCertStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certsDir) + val p2pSslConfig = CertificateStoreStubs.P2P.withCertificatesDirectory(certsDir) - init { - val legalName = CordaX500Name("MegaCorp", "London", "GB") - certificatesDirectory.createDirectories() - if (!trustStoreFile.exists()) { - javaClass.classLoader.getResourceAsStream("certificates/cordatruststore.jks").use { it.copyTo(trustStoreFile) } - } - - val clientKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - // Set name constrain to the legal name. - val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName.toX500Name()))), arrayOf()) - val clientCACert = X509Utilities.createCertificate( - CertificateType.INTERMEDIATE_CA, - DEV_INTERMEDIATE_CA.certificate, - DEV_INTERMEDIATE_CA.keyPair, - legalName.x500Principal, - clientKeyPair.public, - nameConstraints = nameConstraints) - - val tlsKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - // Using different x500 name in the TLS cert which is not allowed in the name constraints. - val clientTLSCert = X509Utilities.createCertificate( - CertificateType.TLS, - clientCACert, - clientKeyPair, - CordaX500Name("MiniCorp", "London", "GB").x500Principal, - tlsKeyPair.public) - - loadNodeKeyStore(createNew = true).update { - setPrivateKey( - X509Utilities.CORDA_CLIENT_CA, - clientKeyPair.private, - listOf(clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate)) - } - - loadSslKeyStore(createNew = true).update { - setPrivateKey( - X509Utilities.CORDA_CLIENT_TLS, - tlsKeyPair.private, - listOf(clientTLSCert, clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate)) - } - } + val legalName = CordaX500Name("MegaCorp", "London", "GB") + if (!p2pSslConfig.trustStore.path.exists()) { + val trustStore = p2pSslConfig.trustStore.get(true) + loadDevCaTrustStore().copyTo(trustStore) } - val attacker = clientTo(alice.node.configuration.p2pAddress, sslConfig) + val clientKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + // Set name constrain to the legal name. + val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName.toX500Name()))), arrayOf()) + val clientCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, DEV_INTERMEDIATE_CA.certificate, DEV_INTERMEDIATE_CA.keyPair, legalName.x500Principal, clientKeyPair.public, nameConstraints = nameConstraints) + val tlsKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + // Using different x500 name in the TLS cert which is not allowed in the name constraints. + val clientTLSCert = X509Utilities.createCertificate(CertificateType.TLS, clientCACert, clientKeyPair, CordaX500Name("MiniCorp", "London", "GB").x500Principal, tlsKeyPair.public) + + signingCertStore.get(createNew = true).update { + setPrivateKey(X509Utilities.CORDA_CLIENT_CA, clientKeyPair.private, listOf(clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate)) + } + + p2pSslConfig.keyStore.get(createNew = true).update { + setPrivateKey(X509Utilities.CORDA_CLIENT_TLS, tlsKeyPair.private, listOf(clientTLSCert, clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate)) + } + + val attacker = clientTo(alice.node.configuration.p2pAddress, p2pSslConfig) assertThatExceptionOfType(ActiveMQNotConnectedException::class.java).isThrownBy { attacker.start(PEER_USER, PEER_USER) } diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt index 0214cd0f48..2f52ad269d 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt @@ -18,12 +18,12 @@ import net.corda.node.internal.NodeWithInfo import net.corda.nodeapi.RPCApi import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_PREFIX import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NOTIFICATIONS_ADDRESS -import net.corda.nodeapi.internal.config.SSLConfiguration +import net.corda.nodeapi.internal.config.MutualSslConfiguration import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME -import net.corda.testing.node.User import net.corda.testing.core.singleIdentity import net.corda.testing.internal.configureTestSSL +import net.corda.testing.node.User import net.corda.testing.node.internal.NodeBasedTest import net.corda.testing.node.internal.startFlow import org.apache.activemq.artemis.api.core.ActiveMQNonExistentQueueException @@ -94,7 +94,7 @@ abstract class MQSecurityTest : NodeBasedTest() { assertAllQueueCreationAttacksFail(randomQueue) } - fun clientTo(target: NetworkHostAndPort, sslConfiguration: SSLConfiguration? = configureTestSSL(CordaX500Name("MegaCorp", "London", "GB"))): SimpleMQClient { + fun clientTo(target: NetworkHostAndPort, sslConfiguration: MutualSslConfiguration? = configureTestSSL(CordaX500Name("MegaCorp", "London", "GB"))): SimpleMQClient { val client = SimpleMQClient(target, sslConfiguration) clients += client return client diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/SimpleMQClient.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/SimpleMQClient.kt index 58e04948c4..e70e77d6b5 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/SimpleMQClient.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/SimpleMQClient.kt @@ -3,8 +3,8 @@ package net.corda.services.messaging import net.corda.core.identity.CordaX500Name import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.core.utilities.NetworkHostAndPort -import net.corda.nodeapi.ArtemisTcpTransport -import net.corda.nodeapi.internal.config.SSLConfiguration +import net.corda.nodeapi.internal.InternalArtemisTcpTransport.Companion.p2pConnectorTcpTransport +import net.corda.nodeapi.internal.config.MutualSslConfiguration import net.corda.testing.internal.configureTestSSL import org.apache.activemq.artemis.api.core.client.* @@ -12,7 +12,7 @@ import org.apache.activemq.artemis.api.core.client.* * As the name suggests this is a simple client for connecting to MQ brokers. */ class SimpleMQClient(val target: NetworkHostAndPort, - private val config: SSLConfiguration? = configureTestSSL(DEFAULT_MQ_LEGAL_NAME)) { + private val config: MutualSslConfiguration? = configureTestSSL(DEFAULT_MQ_LEGAL_NAME)) { companion object { val DEFAULT_MQ_LEGAL_NAME = CordaX500Name(organisation = "SimpleMQClient", locality = "London", country = "GB") } @@ -22,7 +22,7 @@ class SimpleMQClient(val target: NetworkHostAndPort, lateinit var producer: ClientProducer fun start(username: String? = null, password: String? = null, enableSSL: Boolean = true) { - val tcpTransport = ArtemisTcpTransport.p2pConnectorTcpTransport(target, config, enableSSL = enableSSL) + val tcpTransport = p2pConnectorTcpTransport(target, config, enableSSL = enableSSL) val locator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport).apply { isBlockOnNonDurableSend = true threadPoolMaxSize = 1 diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 40f0faf110..1f99dc9bf1 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -44,6 +44,7 @@ import net.corda.node.services.FinalityHandler import net.corda.node.services.NotaryChangeHandler import net.corda.node.services.api.* import net.corda.node.services.config.* +import net.corda.node.services.config.rpc.NodeRpcOptions import net.corda.node.services.config.shell.toShellConfig import net.corda.node.services.events.NodeSchedulerService import net.corda.node.services.events.ScheduledActivityObserver @@ -248,7 +249,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, val trustRoot = initKeyStore() val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null) startDatabase() - val nodeCa = configuration.loadNodeKeyStore().getCertificate(X509Utilities.CORDA_CLIENT_CA) + val nodeCa = configuration.signingCertificateStore.get()[X509Utilities.CORDA_CLIENT_CA] identityService.start(trustRoot, listOf(identity.certificate, nodeCa)) return database.use { it.transaction { @@ -278,7 +279,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, log.info("Node starting up ...") val trustRoot = initKeyStore() - val nodeCa = configuration.loadNodeKeyStore().getCertificate(X509Utilities.CORDA_CLIENT_CA) + val nodeCa = configuration.signingCertificateStore.get()[X509Utilities.CORDA_CLIENT_CA] initialiseJVMAgents() schemaService.mappedSchemasWarnings().forEach { @@ -368,10 +369,10 @@ abstract class AbstractNode(val configuration: NodeConfiguration, open fun startShell() { if (configuration.shouldInitCrashShell()) { val shellConfiguration = configuration.toShellConfig() - shellConfiguration.sshHostKeyDirectory?.let { + shellConfiguration.sshdPort?.let { log.info("Binding Shell SSHD server on port $it.") } - InteractiveShell.startShellInternal(shellConfiguration, cordappLoader.appClassLoader) + InteractiveShell.startShell(shellConfiguration, cordappLoader.appClassLoader) } } @@ -696,8 +697,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration, private fun validateKeyStore(): X509Certificate { val containCorrectKeys = try { // This will throw IOException if key file not found or KeyStoreException if keystore password is incorrect. - val sslKeystore = configuration.loadSslKeyStore() - val identitiesKeystore = configuration.loadNodeKeyStore() + val sslKeystore = configuration.p2pSslOptions.keyStore.get() + val identitiesKeystore = configuration.signingCertificateStore.get() X509Utilities.CORDA_CLIENT_TLS in sslKeystore && X509Utilities.CORDA_CLIENT_CA in identitiesKeystore } catch (e: KeyStoreException) { log.warn("Certificate key store found but key store password does not match configuration.") @@ -714,9 +715,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } // Check all cert path chain to the trusted root - val sslCertChainRoot = configuration.loadSslKeyStore().getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).last() - val nodeCaCertChainRoot = configuration.loadNodeKeyStore().getCertificateChain(X509Utilities.CORDA_CLIENT_CA).last() - val trustRoot = configuration.loadTrustStore().getCertificate(X509Utilities.CORDA_ROOT_CA) + val sslCertChainRoot = configuration.p2pSslOptions.keyStore.get().query { getCertificateChain(X509Utilities.CORDA_CLIENT_TLS) }.last() + val nodeCaCertChainRoot = configuration.signingCertificateStore.get().query { getCertificateChain(X509Utilities.CORDA_CLIENT_CA) }.last() + val trustRoot = configuration.p2pSslOptions.trustStore.get()[X509Utilities.CORDA_ROOT_CA] require(sslCertChainRoot == trustRoot) { "TLS certificate must chain to the trusted root." } require(nodeCaCertChainRoot == trustRoot) { "Client CA certificate must chain to the trusted root." } @@ -760,7 +761,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, return notaryConfig.run { when { raft != null -> { - val uniquenessProvider = RaftUniquenessProvider(configuration, database, platformClock, monitoringService.metrics, raft) + val uniquenessProvider = RaftUniquenessProvider(configuration.baseDirectory, configuration.p2pSslOptions, database, platformClock, monitoringService.metrics, raft) (if (validating) ::RaftValidatingNotaryService else ::RaftNonValidatingNotaryService)(services, notaryKey, uniquenessProvider) } bftSMaRt != null -> { @@ -804,7 +805,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, networkParameters: NetworkParameters) private fun obtainIdentity(notaryConfig: NotaryConfig?): Pair { - val keyStore = configuration.loadNodeKeyStore() + val keyStore = configuration.signingCertificateStore.get() val (id, singleName) = if (notaryConfig == null || !notaryConfig.isClusterConfig) { // Node's main identity or if it's a single node notary @@ -819,25 +820,25 @@ abstract class AbstractNode(val configuration: NodeConfiguration, if (privateKeyAlias !in keyStore) { singleName ?: throw IllegalArgumentException( "Unable to find in the key store the identity of the distributed notary the node is part of") - log.info("$privateKeyAlias not found in key store ${configuration.nodeKeystore}, generating fresh key!") + log.info("$privateKeyAlias not found in key store, generating fresh key!") // TODO This check shouldn't be needed check(singleName == configuration.myLegalName) keyStore.storeLegalIdentity(privateKeyAlias, generateKeyPair()) } - val (x509Cert, keyPair) = keyStore.getCertificateAndKeyPair(privateKeyAlias) + val (x509Cert, keyPair) = keyStore.query { getCertificateAndKeyPair(privateKeyAlias) } // TODO: Use configuration to indicate composite key should be used instead of public key for the identity. val compositeKeyAlias = "$id-composite-key" val certificates = if (compositeKeyAlias in keyStore) { // Use composite key instead if it exists - val certificate = keyStore.getCertificate(compositeKeyAlias) + val certificate = keyStore[compositeKeyAlias] // We have to create the certificate chain for the composite key manually, this is because we don't have a keystore // provider that understand compositeKey-privateKey combo. The cert chain is created using the composite key certificate + // the tail of the private key certificates, as they are both signed by the same certificate chain. - listOf(certificate) + keyStore.getCertificateChain(privateKeyAlias).drop(1) + listOf(certificate) + keyStore.query { getCertificateChain(privateKeyAlias) }.drop(1) } else { - keyStore.getCertificateChain(privateKeyAlias).let { + keyStore.query { getCertificateChain(privateKeyAlias) }.let { check(it[0] == x509Cert) { "Certificates from key store do not line up!" } it } @@ -1027,4 +1028,13 @@ fun CordaPersistence.startHikariPool(hikariProperties: Properties, databaseConfi else -> throw CouldNotCreateDataSourceException("Could not create the DataSource: ${ex.message}", ex) } } +} + +fun clientSslOptionsCompatibleWith(nodeRpcOptions: NodeRpcOptions): ClientRpcSslOptions? { + + if (!nodeRpcOptions.useSsl || nodeRpcOptions.sslConfig == null) { + return null + } + // Here we're using the node's RPC key store as the RPC client's trust store. + return ClientRpcSslOptions(trustStorePath = nodeRpcOptions.sslConfig!!.keyStorePath, trustStorePassword = nodeRpcOptions.sslConfig!!.keyStorePassword) } \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index 383bc11ba2..c135093517 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -227,12 +227,12 @@ open class Node(configuration: NodeConfiguration, startLocalRpcBroker(securityManager) } - val bridgeControlListener = BridgeControlListener(configuration, network.serverAddress, networkParameters.maxMessageSize) + val bridgeControlListener = BridgeControlListener(configuration.p2pSslOptions, network.serverAddress, networkParameters.maxMessageSize) printBasicNodeInfo("Advertised P2P messaging addresses", nodeInfo.addresses.joinToString()) val rpcServerConfiguration = RPCServerConfiguration.DEFAULT rpcServerAddresses?.let { - internalRpcMessagingClient = InternalRPCMessagingClient(configuration, it.admin, MAX_RPC_MESSAGE_SIZE, CordaX500Name.build(configuration.loadSslKeyStore().getCertificate(X509Utilities.CORDA_CLIENT_TLS).subjectX500Principal), rpcServerConfiguration) + internalRpcMessagingClient = InternalRPCMessagingClient(configuration.p2pSslOptions, it.admin, MAX_RPC_MESSAGE_SIZE, CordaX500Name.build(configuration.p2pSslOptions.keyStore.get()[X509Utilities.CORDA_CLIENT_TLS].subjectX500Principal), rpcServerConfiguration) printBasicNodeInfo("RPC connection address", it.primary.toString()) printBasicNodeInfo("RPC admin connection address", it.admin.toString()) } @@ -271,9 +271,9 @@ open class Node(configuration: NodeConfiguration, val rpcBrokerDirectory: Path = baseDirectory / "brokers" / "rpc" with(rpcOptions) { rpcBroker = if (useSsl) { - ArtemisRpcBroker.withSsl(configuration, this.address, adminAddress, sslConfig!!, securityManager, MAX_RPC_MESSAGE_SIZE, jmxMonitoringHttpPort != null, rpcBrokerDirectory, shouldStartLocalShell()) + ArtemisRpcBroker.withSsl(configuration.p2pSslOptions, this.address, adminAddress, sslConfig!!, securityManager, MAX_RPC_MESSAGE_SIZE, jmxMonitoringHttpPort != null, rpcBrokerDirectory, shouldStartLocalShell()) } else { - ArtemisRpcBroker.withoutSsl(configuration, this.address, adminAddress, securityManager, MAX_RPC_MESSAGE_SIZE, jmxMonitoringHttpPort != null, rpcBrokerDirectory, shouldStartLocalShell()) + ArtemisRpcBroker.withoutSsl(configuration.p2pSslOptions, this.address, adminAddress, securityManager, MAX_RPC_MESSAGE_SIZE, jmxMonitoringHttpPort != null, rpcBrokerDirectory, shouldStartLocalShell()) } } rpcBroker!!.closeOnStop() diff --git a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt index 051097ff3f..d5ba9fd453 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt @@ -8,11 +8,10 @@ import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.internal.exists import net.corda.nodeapi.internal.* -import net.corda.nodeapi.internal.config.SSLConfiguration +import net.corda.nodeapi.internal.config.FileBasedCertificateStoreSupplier +import net.corda.nodeapi.internal.config.MutualSslConfiguration import net.corda.nodeapi.internal.config.toProperties import net.corda.nodeapi.internal.crypto.X509KeyStore -import net.corda.nodeapi.internal.crypto.loadKeyStore -import net.corda.nodeapi.internal.crypto.save import org.slf4j.LoggerFactory import java.nio.file.Path @@ -68,22 +67,33 @@ object ConfigHelper { * the CA certs in Node resources. Then provision KeyStores into certificates folder under node path. */ // TODO Move this to KeyStoreConfigHelpers -fun NodeConfiguration.configureWithDevSSLCertificate() = configureDevKeyAndTrustStores(myLegalName) +fun NodeConfiguration.configureWithDevSSLCertificate() = p2pSslOptions.configureDevKeyAndTrustStores(myLegalName, signingCertificateStore, certificatesDirectory) // TODO Move this to KeyStoreConfigHelpers -fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name) { +fun MutualSslConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name, signingCertificateStore: FileBasedCertificateStoreSupplier, certificatesDirectory: Path) { + + val specifiedTrustStore = trustStore.getOptional() + + val specifiedKeyStore = keyStore.getOptional() + val specifiedSigningStore = signingCertificateStore.getOptional() + + if (specifiedTrustStore != null && specifiedKeyStore != null && specifiedSigningStore != null) return certificatesDirectory.createDirectories() - if (!trustStoreFile.exists()) { - loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/$DEV_CA_TRUST_STORE_FILE"), DEV_CA_TRUST_STORE_PASS).save(trustStoreFile, trustStorePassword) + + if (specifiedTrustStore == null) { + loadDevCaTrustStore().copyTo(trustStore.get(true)) } - if (!sslKeystore.exists() || !nodeKeystore.exists()) { - val (nodeKeyStore) = createDevKeyStores(myLegalName) + + if (keyStore.getOptional() == null || signingCertificateStore.getOptional() == null) { + val signingKeyStore = FileBasedCertificateStoreSupplier(signingCertificateStore.path, signingCertificateStore.password).get(true).also { it.registerDevSigningCertificates(myLegalName) } + + FileBasedCertificateStoreSupplier(keyStore.path, keyStore.password).get(true).also { it.registerDevP2pCertificates(myLegalName) } // Move distributed service composite key (generated by IdentityGenerator.generateToDisk) to keystore if exists. val distributedServiceKeystore = certificatesDirectory / "distributedService.jks" if (distributedServiceKeystore.exists()) { val serviceKeystore = X509KeyStore.fromFile(distributedServiceKeystore, DEV_CA_KEY_STORE_PASS) - nodeKeyStore.update { + signingKeyStore.update { serviceKeystore.aliases().forEach { if (serviceKeystore.internal.isKeyEntry(it)) { setPrivateKey(it, serviceKeystore.getPrivateKey(it, DEV_CA_PRIVATE_KEY_PASS), serviceKeystore.getCertificateChain(it)) diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index c095fbadf3..2754629f75 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -11,7 +11,9 @@ import net.corda.core.utilities.loggerFor import net.corda.core.utilities.seconds import net.corda.node.services.config.rpc.NodeRpcOptions import net.corda.nodeapi.BrokerRpcSslOptions -import net.corda.nodeapi.internal.config.NodeSSLConfiguration +import net.corda.nodeapi.internal.config.FileBasedCertificateStoreSupplier +import net.corda.nodeapi.internal.config.SslConfiguration +import net.corda.nodeapi.internal.config.MutualSslConfiguration import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.config.parseAs @@ -30,7 +32,7 @@ private val DEFAULT_FLOW_MONITOR_PERIOD_MILLIS: Duration = Duration.ofMinutes(1) private val DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS: Duration = Duration.ofMinutes(1) private const val CORDAPPS_DIR_NAME_DEFAULT = "cordapps" -interface NodeConfiguration : NodeSSLConfiguration { +interface NodeConfiguration { val myLegalName: CordaX500Name val emailAddress: String val jmxMonitoringHttpPort: Int? @@ -69,9 +71,16 @@ interface NodeConfiguration : NodeSSLConfiguration { val effectiveH2Settings: NodeH2Settings? val flowMonitorPeriodMillis: Duration get() = DEFAULT_FLOW_MONITOR_PERIOD_MILLIS val flowMonitorSuspensionLoggingThresholdMillis: Duration get() = DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS - val cordappDirectories: List get() = listOf(baseDirectory / CORDAPPS_DIR_NAME_DEFAULT) + val crlCheckSoftFail: Boolean val jmxReporterType : JmxReporterType? get() = defaultJmxReporterType + val baseDirectory: Path + val certificatesDirectory: Path + val signingCertificateStore: FileBasedCertificateStoreSupplier + val p2pSslOptions: MutualSslConfiguration + + val cordappDirectories: List + fun validate(): List companion object { @@ -176,8 +185,8 @@ data class NodeConfigurationImpl( override val myLegalName: CordaX500Name, override val jmxMonitoringHttpPort: Int? = null, override val emailAddress: String, - override val keyStorePassword: String, - override val trustStorePassword: String, + private val keyStorePassword: String, + private val trustStorePassword: String, override val crlCheckSoftFail: Boolean, override val dataSourceProperties: Properties, override val compatibilityZoneURL: URL? = null, @@ -243,6 +252,17 @@ data class NodeConfigurationImpl( } } + override val certificatesDirectory = baseDirectory / "certificates" + + private val signingCertificateStorePath = certificatesDirectory / "nodekeystore.jks" + override val signingCertificateStore = FileBasedCertificateStoreSupplier(signingCertificateStorePath, keyStorePassword) + + private val p2pKeystorePath: Path get() = certificatesDirectory / "sslkeystore.jks" + private val p2pKeyStore = FileBasedCertificateStoreSupplier(p2pKeystorePath, keyStorePassword) + private val p2pTrustStoreFilePath: Path get() = certificatesDirectory / "truststore.jks" + private val p2pTrustStore = FileBasedCertificateStoreSupplier(p2pTrustStoreFilePath, trustStorePassword) + override val p2pSslOptions: MutualSslConfiguration = SslConfiguration.mutual(p2pKeyStore, p2pTrustStore) + override val rpcOptions: NodeRpcOptions get() { return actualRpcSettings.asOptions() @@ -356,8 +376,6 @@ data class NodeConfigurationImpl( } } - - data class NodeRpcSettings( val address: NetworkHostAndPort?, val adminAddress: NetworkHostAndPort?, diff --git a/node/src/main/kotlin/net/corda/node/services/config/shell/ShellConfig.kt b/node/src/main/kotlin/net/corda/node/services/config/shell/ShellConfig.kt index 5a0eaa4ac9..3138a29934 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/shell/ShellConfig.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/shell/ShellConfig.kt @@ -1,6 +1,7 @@ package net.corda.node.services.config.shell import net.corda.core.internal.div +import net.corda.node.internal.clientSslOptionsCompatibleWith import net.corda.node.services.config.NodeConfiguration import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_SHELL_USER import net.corda.tools.shell.ShellConfiguration @@ -14,8 +15,8 @@ fun NodeConfiguration.toShellConfig() = ShellConfiguration( cordappsDirectory = this.baseDirectory.toString() / CORDAPPS_DIR, user = INTERNAL_SHELL_USER, password = INTERNAL_SHELL_USER, - hostAndPort = this.rpcOptions.adminAddress, - nodeSslConfig = this, + hostAndPort = this.rpcOptions.address, + ssl = clientSslOptionsCompatibleWith(this.rpcOptions), sshdPort = this.sshd?.port, sshHostKeyDirectory = this.baseDirectory / SSHD_HOSTKEY_DIR, noLocalShell = this.noLocalShell) diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt index 170dfd1b37..bb46278787 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt @@ -1,6 +1,5 @@ package net.corda.node.services.messaging -import io.netty.channel.unix.Errors import net.corda.core.internal.ThreadBox import net.corda.core.internal.div import net.corda.core.serialization.SingletonSerializeAsToken @@ -12,13 +11,13 @@ import net.corda.node.internal.artemis.BrokerJaasLoginModule.Companion.NODE_P2P_ import net.corda.node.internal.artemis.BrokerJaasLoginModule.Companion.PEER_ROLE import net.corda.core.internal.errors.AddressBindingException import net.corda.node.services.config.NodeConfiguration -import net.corda.nodeapi.ArtemisTcpTransport.Companion.p2pAcceptorTcpTransport import net.corda.nodeapi.internal.AmqpMessageSizeChecksInterceptor import net.corda.nodeapi.internal.ArtemisMessageSizeChecksInterceptor import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_PREFIX import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.JOURNAL_HEADER_SIZE import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NOTIFICATIONS_ADDRESS import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX +import net.corda.nodeapi.internal.InternalArtemisTcpTransport.Companion.p2pAcceptorTcpTransport import net.corda.nodeapi.internal.requireOnDefaultFileSystem import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl @@ -120,7 +119,7 @@ class ArtemisMessagingServer(private val config: NodeConfiguration, bindingsDirectory = (artemisDir / "bindings").toString() journalDirectory = (artemisDir / "journal").toString() largeMessagesDirectory = (artemisDir / "large-messages").toString() - acceptorConfigurations = mutableSetOf(p2pAcceptorTcpTransport(NetworkHostAndPort(messagingServerAddress.host, messagingServerAddress.port), config)) + acceptorConfigurations = mutableSetOf(p2pAcceptorTcpTransport(NetworkHostAndPort(messagingServerAddress.host, messagingServerAddress.port), config.p2pSslOptions)) // Enable built in message deduplication. Note we still have to do our own as the delayed commits // and our own definition of commit mean that the built in deduplication cannot remove all duplicates. idCacheSize = 2000 // Artemis Default duplicate cache size i.e. a guess @@ -162,8 +161,8 @@ class ArtemisMessagingServer(private val config: NodeConfiguration, @Throws(IOException::class, KeyStoreException::class) private fun createArtemisSecurityManager(): ActiveMQJAASSecurityManager { - val keyStore = config.loadSslKeyStore().internal - val trustStore = config.loadTrustStore().internal + val keyStore = config.p2pSslOptions.keyStore.get().value.internal + val trustStore = config.p2pSslOptions.trustStore.get().value.internal val securityConfig = object : SecurityConfiguration() { // Override to make it work with our login module diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/InternalRPCMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/InternalRPCMessagingClient.kt index 9b5191b312..abb07763d1 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/InternalRPCMessagingClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/InternalRPCMessagingClient.kt @@ -6,9 +6,9 @@ import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.internal.security.RPCSecurityManager -import net.corda.nodeapi.ArtemisTcpTransport import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_RPC_USER -import net.corda.nodeapi.internal.config.SSLConfiguration +import net.corda.nodeapi.internal.InternalArtemisTcpTransport +import net.corda.nodeapi.internal.config.MutualSslConfiguration import org.apache.activemq.artemis.api.core.client.ActiveMQClient import org.apache.activemq.artemis.api.core.client.ServerLocator import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl @@ -16,13 +16,13 @@ import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl /** * Used by the Node to communicate with the RPC broker. */ -class InternalRPCMessagingClient(val sslConfig: SSLConfiguration, val serverAddress: NetworkHostAndPort, val maxMessageSize: Int, val nodeName: CordaX500Name, val rpcServerConfiguration: RPCServerConfiguration) : SingletonSerializeAsToken(), AutoCloseable { +class InternalRPCMessagingClient(val sslConfig: MutualSslConfiguration, val serverAddress: NetworkHostAndPort, val maxMessageSize: Int, val nodeName: CordaX500Name, val rpcServerConfiguration: RPCServerConfiguration) : SingletonSerializeAsToken(), AutoCloseable { private var locator: ServerLocator? = null private var rpcServer: RPCServer? = null fun init(rpcOps: RPCOps, securityManager: RPCSecurityManager) = synchronized(this) { - val tcpTransport = ArtemisTcpTransport.rpcInternalClientTcpTransport(serverAddress, sslConfig) + val tcpTransport = InternalArtemisTcpTransport.rpcInternalClientTcpTransport(serverAddress, sslConfig) locator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport).apply { // Never time out on our loopback Artemis connections. If we switch back to using the InVM transport this // would be the default and the two lines below can be deleted. diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt index 44667c43b8..95c340ead1 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt @@ -26,7 +26,6 @@ import net.corda.node.services.statemachine.DeduplicationId import net.corda.node.services.statemachine.ExternalEvent import net.corda.node.services.statemachine.SenderDeduplicationId import net.corda.node.utilities.AffinityExecutor -import net.corda.nodeapi.ArtemisTcpTransport.Companion.p2pConnectorTcpTransport import net.corda.nodeapi.internal.ArtemisMessagingComponent import net.corda.nodeapi.internal.ArtemisMessagingComponent.* import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.BRIDGE_CONTROL @@ -34,6 +33,7 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.BRIDGE_NOT import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.JOURNAL_HEADER_SIZE import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2PMessagingHeaders import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREFIX +import net.corda.nodeapi.internal.InternalArtemisTcpTransport.Companion.p2pConnectorTcpTransport import net.corda.nodeapi.internal.bridging.BridgeControl import net.corda.nodeapi.internal.bridging.BridgeEntry import net.corda.nodeapi.internal.persistence.CordaPersistence @@ -149,7 +149,7 @@ class P2PMessagingClient(val config: NodeConfiguration, started = true log.info("Connecting to message broker: $serverAddress") // TODO Add broker CN to config for host verification in case the embedded broker isn't used - val tcpTransport = p2pConnectorTcpTransport(serverAddress, config) + val tcpTransport = p2pConnectorTcpTransport(serverAddress, config.p2pSslOptions) locator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport).apply { // Never time out on our loopback Artemis connections. If we switch back to using the InVM transport this // would be the default and the two lines below can be deleted. diff --git a/node/src/main/kotlin/net/corda/node/services/rpc/ArtemisRpcBroker.kt b/node/src/main/kotlin/net/corda/node/services/rpc/ArtemisRpcBroker.kt index 5fc3c98bc8..af6158e33a 100644 --- a/node/src/main/kotlin/net/corda/node/services/rpc/ArtemisRpcBroker.kt +++ b/node/src/main/kotlin/net/corda/node/services/rpc/ArtemisRpcBroker.kt @@ -8,7 +8,7 @@ import net.corda.node.internal.artemis.BrokerJaasLoginModule.Companion.NODE_SECU import net.corda.node.internal.artemis.BrokerJaasLoginModule.Companion.RPC_SECURITY_CONFIG import net.corda.node.internal.security.RPCSecurityManager import net.corda.nodeapi.BrokerRpcSslOptions -import net.corda.nodeapi.internal.config.SSLConfiguration +import net.corda.nodeapi.internal.config.MutualSslConfiguration import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl import org.apache.activemq.artemis.core.config.impl.SecurityConfiguration import org.apache.activemq.artemis.core.server.ActiveMQServer @@ -28,17 +28,17 @@ class ArtemisRpcBroker internal constructor( private val maxMessageSize: Int, private val jmxEnabled: Boolean = false, private val baseDirectory: Path, - private val nodeConfiguration: SSLConfiguration, + private val nodeConfiguration: MutualSslConfiguration, private val shouldStartLocalShell: Boolean) : ArtemisBroker { companion object { private val logger = loggerFor() - fun withSsl(configuration: SSLConfiguration, address: NetworkHostAndPort, adminAddress: NetworkHostAndPort, sslOptions: BrokerRpcSslOptions, securityManager: RPCSecurityManager, maxMessageSize: Int, jmxEnabled: Boolean, baseDirectory: Path, shouldStartLocalShell: Boolean): ArtemisBroker { + fun withSsl(configuration: MutualSslConfiguration, address: NetworkHostAndPort, adminAddress: NetworkHostAndPort, sslOptions: BrokerRpcSslOptions, securityManager: RPCSecurityManager, maxMessageSize: Int, jmxEnabled: Boolean, baseDirectory: Path, shouldStartLocalShell: Boolean): ArtemisBroker { return ArtemisRpcBroker(address, adminAddress, sslOptions, true, securityManager, maxMessageSize, jmxEnabled, baseDirectory, configuration, shouldStartLocalShell) } - fun withoutSsl(configuration: SSLConfiguration, address: NetworkHostAndPort, adminAddress: NetworkHostAndPort, securityManager: RPCSecurityManager, maxMessageSize: Int, jmxEnabled: Boolean, baseDirectory: Path, shouldStartLocalShell: Boolean): ArtemisBroker { + fun withoutSsl(configuration: MutualSslConfiguration, address: NetworkHostAndPort, adminAddress: NetworkHostAndPort, securityManager: RPCSecurityManager, maxMessageSize: Int, jmxEnabled: Boolean, baseDirectory: Path, shouldStartLocalShell: Boolean): ArtemisBroker { return ArtemisRpcBroker(address, adminAddress, null, false, securityManager, maxMessageSize, jmxEnabled, baseDirectory, configuration, shouldStartLocalShell) } } @@ -83,14 +83,14 @@ class ArtemisRpcBroker internal constructor( @Throws(IOException::class, KeyStoreException::class) private fun createArtemisSecurityManager(loginListener: LoginListener): ActiveMQJAASSecurityManager { - val keyStore = nodeConfiguration.loadSslKeyStore().internal - val trustStore = nodeConfiguration.loadTrustStore().internal + val keyStore = nodeConfiguration.keyStore.get() + val trustStore = nodeConfiguration.trustStore.get() val securityConfig = object : SecurityConfiguration() { override fun getAppConfigurationEntry(name: String): Array { val options = mapOf( RPC_SECURITY_CONFIG to RPCJaasConfig(securityManager, loginListener, useSsl), - NODE_SECURITY_CONFIG to NodeJaasConfig(keyStore, trustStore) + NODE_SECURITY_CONFIG to NodeJaasConfig(keyStore.value.internal, trustStore.value.internal) ) return arrayOf(AppConfigurationEntry(name, AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options)) } diff --git a/node/src/main/kotlin/net/corda/node/services/rpc/RpcBrokerConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/rpc/RpcBrokerConfiguration.kt index 3859e7cd8c..f17dcee8aa 100644 --- a/node/src/main/kotlin/net/corda/node/services/rpc/RpcBrokerConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/rpc/RpcBrokerConfiguration.kt @@ -4,12 +4,12 @@ import net.corda.core.internal.div import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.internal.artemis.BrokerJaasLoginModule import net.corda.node.internal.artemis.SecureArtemisConfiguration -import net.corda.nodeapi.ArtemisTcpTransport.Companion.rpcAcceptorTcpTransport -import net.corda.nodeapi.ArtemisTcpTransport.Companion.rpcInternalAcceptorTcpTransport import net.corda.nodeapi.BrokerRpcSslOptions import net.corda.nodeapi.RPCApi import net.corda.nodeapi.internal.ArtemisMessagingComponent -import net.corda.nodeapi.internal.config.SSLConfiguration +import net.corda.nodeapi.internal.InternalArtemisTcpTransport.Companion.rpcAcceptorTcpTransport +import net.corda.nodeapi.internal.InternalArtemisTcpTransport.Companion.rpcInternalAcceptorTcpTransport +import net.corda.nodeapi.internal.config.MutualSslConfiguration import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.core.config.CoreQueueConfiguration import org.apache.activemq.artemis.core.security.Role @@ -17,7 +17,7 @@ import org.apache.activemq.artemis.core.settings.impl.AddressFullMessagePolicy import org.apache.activemq.artemis.core.settings.impl.AddressSettings import java.nio.file.Path -internal class RpcBrokerConfiguration(baseDirectory: Path, maxMessageSize: Int, jmxEnabled: Boolean, address: NetworkHostAndPort, adminAddress: NetworkHostAndPort?, sslOptions: BrokerRpcSslOptions?, useSsl: Boolean, nodeConfiguration: SSLConfiguration, shouldStartLocalShell: Boolean) : SecureArtemisConfiguration() { +internal class RpcBrokerConfiguration(baseDirectory: Path, maxMessageSize: Int, jmxEnabled: Boolean, address: NetworkHostAndPort, adminAddress: NetworkHostAndPort?, sslOptions: BrokerRpcSslOptions?, useSsl: Boolean, nodeConfiguration: MutualSslConfiguration, shouldStartLocalShell: Boolean) : SecureArtemisConfiguration() { val loginListener: (String) -> Unit init { diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt index 1e5c46d28b..1265461d17 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt @@ -28,8 +28,7 @@ import net.corda.core.utilities.debug import net.corda.node.services.config.RaftConfig import net.corda.node.services.transactions.RaftTransactionCommitLog.Commands.CommitTransaction import net.corda.node.utilities.AppendOnlyPersistentMap -import net.corda.nodeapi.internal.config.NodeSSLConfiguration -import net.corda.nodeapi.internal.config.SSLConfiguration +import net.corda.nodeapi.internal.config.MutualSslConfiguration import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import java.nio.file.Path @@ -51,7 +50,8 @@ import javax.persistence.Table */ @ThreadSafe class RaftUniquenessProvider( - private val transportConfiguration: NodeSSLConfiguration, + private val storagePath: Path, + private val transportConfiguration: MutualSslConfiguration, private val db: CordaPersistence, private val clock: Clock, private val metrics: MetricRegistry, @@ -96,8 +96,6 @@ class RaftUniquenessProvider( var index: Long = 0 ) - /** Directory storing the Raft log and state machine snapshots */ - private val storagePath: Path = transportConfiguration.baseDirectory private lateinit var _clientFuture: CompletableFuture private lateinit var server: CopycatServer @@ -155,14 +153,14 @@ class RaftUniquenessProvider( .build() } - private fun buildTransport(config: SSLConfiguration): Transport? { + private fun buildTransport(config: MutualSslConfiguration): Transport? { return NettyTransport.builder() .withSsl() .withSslProtocol(SslProtocol.TLSv1_2) - .withKeyStorePath(config.sslKeystore.toString()) - .withKeyStorePassword(config.keyStorePassword) - .withTrustStorePath(config.trustStoreFile.toString()) - .withTrustStorePassword(config.trustStorePassword) + .withKeyStorePath(config.keyStore.path.toString()) + .withKeyStorePassword(config.keyStore.password) + .withTrustStorePath(config.trustStore.path.toString()) + .withTrustStorePassword(config.trustStore.password) .build() } diff --git a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt index 4ec5319c28..b3186b8d7b 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt @@ -6,7 +6,8 @@ import net.corda.core.internal.* import net.corda.core.utilities.contextLogger import net.corda.node.NodeRegistrationOption import net.corda.node.services.config.NodeConfiguration -import net.corda.nodeapi.internal.config.SSLConfiguration +import net.corda.nodeapi.internal.config.CertificateStore +import net.corda.nodeapi.internal.config.CertificateStoreSupplier import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.crypto.X509Utilities @@ -33,7 +34,8 @@ import javax.security.auth.x500.X500Principal * needed. */ // TODO: Use content signer instead of keypairs. -open class NetworkRegistrationHelper(private val config: SSLConfiguration, +open class NetworkRegistrationHelper(private val certificatesDirectory: Path, + private val signingCertificateStore: CertificateStoreSupplier, private val myLegalName: CordaX500Name, private val emailAddress: String, private val certService: NetworkRegistrationService, @@ -48,9 +50,7 @@ open class NetworkRegistrationHelper(private val config: SSLConfiguration, val logger = contextLogger() } - private val requestIdStore = config.certificatesDirectory / "certificate-request-id.txt" - // TODO: Use different password for private key. - private val privateKeyPassword = config.keyStorePassword + private val requestIdStore = certificatesDirectory / "certificate-request-id.txt" private val rootTrustStore: X509KeyStore protected val rootCert: X509Certificate @@ -75,12 +75,14 @@ open class NetworkRegistrationHelper(private val config: SSLConfiguration, * @throws CertificateRequestException if the certificate retrieved by doorman is invalid. */ fun buildKeystore() { - config.certificatesDirectory.createDirectories() - val nodeKeyStore = config.loadNodeKeyStore(createNew = true) + certificatesDirectory.createDirectories() + val nodeKeyStore = signingCertificateStore.get(createNew = true) if (keyAlias in nodeKeyStore) { println("Certificate already exists, Corda node will now terminate...") return } + // TODO: Use different password for private key. + val privateKeyPassword = nodeKeyStore.password val tlsCrlIssuerCert = validateAndGetTlsCrlIssuerCert() if (tlsCrlIssuerCert == null && isTlsCrlIssuerCertRequired()) { System.err.println("""tlsCrlIssuerCert config does not match the root certificate issuer and nor is there any other certificate in the trust store with a matching issuer. @@ -89,7 +91,7 @@ open class NetworkRegistrationHelper(private val config: SSLConfiguration, throw IllegalArgumentException("TLS CRL issuer certificate not found in the trust store.") } - val keyPair = nodeKeyStore.loadOrCreateKeyPair(SELF_SIGNED_PRIVATE_KEY) + val keyPair = nodeKeyStore.loadOrCreateKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword) val requestId = try { submitOrResumeCertificateSigningRequest(keyPair) @@ -110,7 +112,7 @@ open class NetworkRegistrationHelper(private val config: SSLConfiguration, throw certificateRequestException } validateCertificates(keyPair.public, certificates) - storePrivateKeyWithCertificates(nodeKeyStore, keyPair, certificates, keyAlias) + storePrivateKeyWithCertificates(nodeKeyStore, keyPair, certificates, keyAlias, privateKeyPassword) onSuccess(keyPair, certificates, tlsCrlIssuerCert?.subjectX500Principal?.toX500Name()) // All done, clean up temp files. requestIdStore.deleteIfExists() @@ -148,25 +150,29 @@ open class NetworkRegistrationHelper(private val config: SSLConfiguration, println("Certificate signing request approved, storing private key with the certificate chain.") } - private fun storePrivateKeyWithCertificates(nodeKeystore: X509KeyStore, keyPair: KeyPair, certificates: List, keyAlias: String) { + private fun storePrivateKeyWithCertificates(nodeKeystore: CertificateStore, keyPair: KeyPair, certificates: List, keyAlias: String, keyPassword: String) { // Save private key and certificate chain to the key store. - nodeKeystore.setPrivateKey(keyAlias, keyPair.private, certificates, keyPassword = config.keyStorePassword) - nodeKeystore.internal.deleteEntry(SELF_SIGNED_PRIVATE_KEY) - nodeKeystore.save() - println("Private key '$keyAlias' and certificate stored in ${config.nodeKeystore}.") + with(nodeKeystore.value) { + setPrivateKey(keyAlias, keyPair.private, certificates, keyPassword = keyPassword) + internal.deleteEntry(SELF_SIGNED_PRIVATE_KEY) + save() + } + println("Private key '$keyAlias' and certificate stored in node signing keystore.") } - private fun X509KeyStore.loadOrCreateKeyPair(alias: String): KeyPair { + private fun CertificateStore.loadOrCreateKeyPair(alias: String, privateKeyPassword: String = password): KeyPair { // Create or load self signed keypair from the key store. // We use the self sign certificate to store the key temporarily in the keystore while waiting for the request approval. if (alias !in this) { val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val selfSignCert = X509Utilities.createSelfSignedCACertificate(myLegalName.x500Principal, keyPair) // Save to the key store. - setPrivateKey(alias, keyPair.private, listOf(selfSignCert), keyPassword = privateKeyPassword) - save() + with(value) { + setPrivateKey(alias, keyPair.private, listOf(selfSignCert), keyPassword = privateKeyPassword) + save() + } } - return getCertificateAndKeyPair(alias, privateKeyPassword).keyPair + return query { getCertificateAndKeyPair(alias, privateKeyPassword) }.keyPair } /** @@ -243,7 +249,9 @@ open class NetworkRegistrationHelper(private val config: SSLConfiguration, class NodeRegistrationException(cause: Throwable?) : IOException("Unable to contact node registration service", cause) class NodeRegistrationHelper(private val config: NodeConfiguration, certService: NetworkRegistrationService, regConfig: NodeRegistrationOption, computeNextIdleDoormanConnectionPollInterval: (Duration?) -> Duration? = FixedPeriodLimitedRetrialStrategy(10, Duration.ofMinutes(1))) : - NetworkRegistrationHelper(config, + NetworkRegistrationHelper( + config.certificatesDirectory, + config.signingCertificateStore, config.myLegalName, config.emailAddress, certService, @@ -263,7 +271,7 @@ class NodeRegistrationHelper(private val config: NodeConfiguration, certService: } private fun createSSLKeystore(nodeCAKeyPair: KeyPair, certificates: List, tlsCertCrlIssuer: X500Name?) { - config.loadSslKeyStore(createNew = true).update { + config.p2pSslOptions.keyStore.get(createNew = true).update { println("Generating SSL certificate for node messaging service.") val sslKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val sslCert = X509Utilities.createCertificate( @@ -277,17 +285,17 @@ class NodeRegistrationHelper(private val config: NodeConfiguration, certService: logger.info("Generated TLS certificate: $sslCert") setPrivateKey(CORDA_CLIENT_TLS, sslKeyPair.private, listOf(sslCert) + certificates) } - println("SSL private key and certificate stored in ${config.sslKeystore}.") + println("SSL private key and certificate stored in ${config.p2pSslOptions.keyStore.path}.") } private fun createTruststore(rootCertificate: X509Certificate) { // Save root certificates to trust store. - config.loadTrustStore(createNew = true).update { + config.p2pSslOptions.trustStore.get(createNew = true).update { println("Generating trust store for corda node.") // Assumes certificate chain always starts with client certificate and end with root certificate. setCertificate(CORDA_ROOT_CA, rootCertificate) } - println("Node trust store stored in ${config.trustStoreFile}.") + println("Node trust store stored in ${config.p2pSslOptions.trustStore.path}.") } override fun validateAndGetTlsCrlIssuerCert(): X509Certificate? { @@ -296,8 +304,9 @@ class NodeRegistrationHelper(private val config: NodeConfiguration, certService: if (principalMatchesCertificatePrincipal(tlsCertCrlIssuer, rootCert)) { return rootCert } - return if (config.trustStoreFile.exists()) { - findMatchingCertificate(tlsCertCrlIssuer, config.loadTrustStore()) + val trustStore = config.p2pSslOptions.trustStore.getOptional() + return if (trustStore != null) { + findMatchingCertificate(tlsCertCrlIssuer, trustStore.value) } else { null } diff --git a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt index eaae962161..09d61e64a6 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt @@ -22,6 +22,7 @@ import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.testing.core.ALICE_NAME +import net.corda.testing.internal.stubs.CertificateStoreStubs import net.corda.testing.internal.createDevIntermediateCaCertPath import net.corda.testing.internal.rigorousMock import org.assertj.core.api.Assertions.* @@ -52,10 +53,12 @@ class NetworkRegistrationHelperTest { val baseDirectory = fs.getPath("/baseDir").createDirectories() abstract class AbstractNodeConfiguration : NodeConfiguration + val certificatesDirectory = baseDirectory / "certificates" config = rigorousMock().also { doReturn(baseDirectory).whenever(it).baseDirectory - doReturn("trustpass").whenever(it).trustStorePassword - doReturn("cordacadevpass").whenever(it).keyStorePassword + doReturn(certificatesDirectory).whenever(it).certificatesDirectory + doReturn(CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory)).whenever(it).p2pSslOptions + doReturn(CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory)).whenever(it).signingCertificateStore doReturn(nodeLegalName).whenever(it).myLegalName doReturn("").whenever(it).emailAddress doReturn(null).whenever(it).tlsCertCrlDistPoint @@ -71,30 +74,30 @@ class NetworkRegistrationHelperTest { @Test fun `successful registration`() { - assertThat(config.nodeKeystore).doesNotExist() - assertThat(config.sslKeystore).doesNotExist() - assertThat(config.trustStoreFile).doesNotExist() + assertThat(config.signingCertificateStore.getOptional()).isNull() + assertThat(config.p2pSslOptions.keyStore.getOptional()).isNull() + assertThat(config.p2pSslOptions.trustStore.getOptional()).isNull() val rootAndIntermediateCA = createDevIntermediateCaCertPath().also { saveNetworkTrustStore(it.first.certificate) } createRegistrationHelper(rootAndIntermediateCA = rootAndIntermediateCA).buildKeystore() - val nodeKeystore = config.loadNodeKeyStore() - val sslKeystore = config.loadSslKeyStore() - val trustStore = config.loadTrustStore() + val nodeKeystore = config.signingCertificateStore.get() + val sslKeystore = config.p2pSslOptions.keyStore.get() + val trustStore = config.p2pSslOptions.trustStore.get() nodeKeystore.run { assertFalse(contains(X509Utilities.CORDA_INTERMEDIATE_CA)) assertFalse(contains(X509Utilities.CORDA_ROOT_CA)) assertFalse(contains(X509Utilities.CORDA_CLIENT_TLS)) - assertThat(CertRole.extract(getCertificate(X509Utilities.CORDA_CLIENT_CA))).isEqualTo(CertRole.NODE_CA) + assertThat(CertRole.extract(this[X509Utilities.CORDA_CLIENT_CA])).isEqualTo(CertRole.NODE_CA) } sslKeystore.run { assertFalse(contains(X509Utilities.CORDA_CLIENT_CA)) assertFalse(contains(X509Utilities.CORDA_INTERMEDIATE_CA)) assertFalse(contains(X509Utilities.CORDA_ROOT_CA)) - val nodeTlsCertChain = getCertificateChain(X509Utilities.CORDA_CLIENT_TLS) + val nodeTlsCertChain = query { getCertificateChain(X509Utilities.CORDA_CLIENT_TLS) } assertThat(nodeTlsCertChain).hasSize(4) // The TLS cert has the same subject as the node CA cert assertThat(CordaX500Name.build(nodeTlsCertChain[0].subjectX500Principal)).isEqualTo(nodeLegalName) @@ -104,7 +107,7 @@ class NetworkRegistrationHelperTest { trustStore.run { assertFalse(contains(X509Utilities.CORDA_CLIENT_CA)) assertFalse(contains(X509Utilities.CORDA_INTERMEDIATE_CA)) - assertThat(getCertificate(X509Utilities.CORDA_ROOT_CA)).isEqualTo(rootAndIntermediateCA.first.certificate) + assertThat(this[X509Utilities.CORDA_ROOT_CA]).isEqualTo(rootAndIntermediateCA.first.certificate) } } @@ -152,18 +155,18 @@ class NetworkRegistrationHelperTest { @Test fun `create service identity cert`() { - assertThat(config.nodeKeystore).doesNotExist() - assertThat(config.sslKeystore).doesNotExist() - assertThat(config.trustStoreFile).doesNotExist() + assertThat(config.signingCertificateStore.getOptional()).isNull() + assertThat(config.p2pSslOptions.keyStore.getOptional()).isNull() + assertThat(config.p2pSslOptions.trustStore.getOptional()).isNull() val rootAndIntermediateCA = createDevIntermediateCaCertPath().also { saveNetworkTrustStore(it.first.certificate) } createRegistrationHelper(CertRole.SERVICE_IDENTITY, rootAndIntermediateCA).buildKeystore() - val nodeKeystore = config.loadNodeKeyStore() + val nodeKeystore = config.signingCertificateStore.get() - assertThat(config.sslKeystore).doesNotExist() - assertThat(config.trustStoreFile).doesNotExist() + assertThat(config.p2pSslOptions.keyStore.getOptional()).isNull() + assertThat(config.p2pSslOptions.trustStore.getOptional()).isNull() val serviceIdentityAlias = "${DevIdentityGenerator.DISTRIBUTED_NOTARY_ALIAS_PREFIX}-private-key" @@ -172,7 +175,7 @@ class NetworkRegistrationHelperTest { assertFalse(contains(X509Utilities.CORDA_ROOT_CA)) assertFalse(contains(X509Utilities.CORDA_CLIENT_TLS)) assertFalse(contains(X509Utilities.CORDA_CLIENT_CA)) - assertThat(CertRole.extract(getCertificate(serviceIdentityAlias))).isEqualTo(CertRole.SERVICE_IDENTITY) + assertThat(CertRole.extract(this[serviceIdentityAlias])).isEqualTo(CertRole.SERVICE_IDENTITY) } } @@ -223,7 +226,8 @@ class NetworkRegistrationHelperTest { return when (certRole) { CertRole.NODE_CA -> NodeRegistrationHelper(config, certService, NodeRegistrationOption(config.certificatesDirectory / networkRootTrustStoreFileName, networkRootTrustStorePassword)) CertRole.SERVICE_IDENTITY -> NetworkRegistrationHelper( - config, + config.certificatesDirectory, + config.signingCertificateStore, config.myLegalName, config.emailAddress, certService, diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt index 10f6392bd1..25f9f01371 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt @@ -5,7 +5,7 @@ import com.typesafe.config.Config import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigRenderOptions import com.typesafe.config.ConfigValueFactory -import net.corda.client.rpc.internal.createCordaRPCClientWithInternalSslAndClassLoader +import net.corda.client.rpc.internal.createCordaRPCClientWithSslAndClassLoader import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.firstOf import net.corda.core.identity.CordaX500Name @@ -23,6 +23,7 @@ import net.corda.node.NodeRegistrationOption import net.corda.node.VersionInfo import net.corda.node.internal.Node import net.corda.node.internal.NodeWithInfo +import net.corda.node.internal.clientSslOptionsCompatibleWith import net.corda.node.services.Permissions import net.corda.node.services.config.* import net.corda.node.utilities.registration.HTTPNetworkRegistrationService @@ -160,7 +161,8 @@ class DriverDSLImpl( private fun establishRpc(config: NodeConfig, processDeathFuture: CordaFuture): CordaFuture { val rpcAddress = config.corda.rpcOptions.address - val client = createCordaRPCClientWithInternalSslAndClassLoader(config.corda.rpcOptions.adminAddress, sslConfiguration = config.corda) + val clientRpcSslOptions = clientSslOptionsCompatibleWith(config.corda.rpcOptions) + val client = createCordaRPCClientWithSslAndClassLoader(rpcAddress, sslConfiguration = clientRpcSslOptions) val connectionFuture = poll(executorService, "RPC connection") { try { config.corda.rpcUsers[0].run { client.start(username, password) } @@ -798,8 +800,13 @@ class DriverDSLImpl( config += "rpcUsers" to configuration.toConfig().getValue("rpcUsers") config += "useHTTPS" to useHTTPS config += "baseDirectory" to configuration.baseDirectory.toAbsolutePath().toString() - config += "keyStorePassword" to configuration.keyStorePassword - config += "trustStorePassword" to configuration.trustStorePassword + + config += "keyStorePath" to configuration.p2pSslOptions.keyStore.path.toString() + config += "keyStorePassword" to configuration.p2pSslOptions.keyStore.password + + config += "trustStorePath" to configuration.p2pSslOptions.trustStore.path.toString() + config += "trustStorePassword" to configuration.p2pSslOptions.trustStore.password + return config } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt index 4a75faf696..e571ee1cfd 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt @@ -53,6 +53,7 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.driver.TestCorDapp +import net.corda.testing.internal.stubs.CertificateStoreStubs import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.setGlobalSerialization import net.corda.testing.internal.testThreadFactory @@ -456,8 +457,11 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe private fun createNodeImpl(parameters: InternalMockNodeParameters, nodeFactory: (MockNodeArgs, CordappLoader?) -> MockNode, start: Boolean): MockNode { val id = parameters.forcedID ?: nextNodeId++ - val config = mockNodeConfiguration().also { - doReturn(baseDirectory(id).createDirectories()).whenever(it).baseDirectory + val baseDirectory = baseDirectory(id) + val certificatesDirectory = baseDirectory / "certificates" + certificatesDirectory.createDirectories() + val config = mockNodeConfiguration(certificatesDirectory).also { + doReturn(baseDirectory).whenever(it).baseDirectory doReturn(parameters.legalName ?: CordaX500Name("Mock Company $id", "London", "GB")).whenever(it).myLegalName doReturn(makeTestDataSourceProperties("node_${id}_net_$networkId")).whenever(it).dataSourceProperties doReturn(emptyList()).whenever(it).extraNetworkMapKeys @@ -561,12 +565,17 @@ abstract class MessagingServiceSpy { abstract fun send(message: Message, target: MessageRecipients, sequenceKey: Any) } -private fun mockNodeConfiguration(): NodeConfiguration { +private fun mockNodeConfiguration(certificatesDirectory: Path): NodeConfiguration { @DoNotImplement abstract class AbstractNodeConfiguration : NodeConfiguration + + val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory) + val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory) + return rigorousMock().also { - doReturn("cordacadevpass").whenever(it).keyStorePassword - doReturn("trustpass").whenever(it).trustStorePassword + doReturn(certificatesDirectory.createDirectories()).whenever(it).certificatesDirectory + doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions + doReturn(signingCertificateStore).whenever(it).signingCertificateStore doReturn(emptyList()).whenever(it).rpcUsers doReturn(null).whenever(it).notary doReturn(DatabaseConfig()).whenever(it).database diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt index 1ce8494c99..94b38ddd2c 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt @@ -21,8 +21,8 @@ import net.corda.core.utilities.seconds import net.corda.node.internal.security.RPCSecurityManagerImpl import net.corda.node.services.messaging.RPCServer import net.corda.node.services.messaging.RPCServerConfiguration -import net.corda.nodeapi.ArtemisTcpTransport import net.corda.nodeapi.RPCApi +import net.corda.nodeapi.internal.InternalArtemisTcpTransport import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.MAX_MESSAGE_SIZE @@ -220,14 +220,14 @@ data class RPCDriverDSL( bindingsDirectory = "$artemisDir/bindings" journalDirectory = "$artemisDir/journal" largeMessagesDirectory = "$artemisDir/large-messages" - acceptorConfigurations = setOf(ArtemisTcpTransport.rpcAcceptorTcpTransport(hostAndPort, null)) + acceptorConfigurations = setOf(InternalArtemisTcpTransport.rpcAcceptorTcpTransport(hostAndPort, null)) configureCommonSettings(maxFileSize, maxBufferedBytesPerClient) } } val inVmClientTransportConfiguration = TransportConfiguration(InVMConnectorFactory::class.java.name) fun createNettyClientTransportConfiguration(hostAndPort: NetworkHostAndPort): TransportConfiguration { - return ArtemisTcpTransport.rpcConnectorTcpTransport(hostAndPort, null) + return InternalArtemisTcpTransport.rpcConnectorTcpTransport(hostAndPort, null) } } @@ -339,7 +339,7 @@ data class RPCDriverDSL( configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT ): CordaFuture { return driverDSL.executorService.fork { - val client = RPCClient(ArtemisTcpTransport.rpcConnectorTcpTransport(rpcAddress, null), configuration) + val client = RPCClient(InternalArtemisTcpTransport.rpcConnectorTcpTransport(rpcAddress, null), configuration) val connection = client.start(rpcOpsClass, username, password, externalTrace) driverDSL.shutdownManager.registerShutdown { connection.close() diff --git a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/UnsafeCertificatesFactory.kt b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/UnsafeCertificatesFactory.kt index 4ce15b78af..bce2152d57 100644 --- a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/UnsafeCertificatesFactory.kt +++ b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/UnsafeCertificatesFactory.kt @@ -4,7 +4,9 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.internal.createFile import net.corda.core.internal.deleteIfExists import net.corda.core.internal.div -import net.corda.nodeapi.internal.config.SSLConfiguration +import net.corda.nodeapi.internal.config.FileBasedCertificateStoreSupplier +import net.corda.nodeapi.internal.config.SslConfiguration +import net.corda.nodeapi.internal.config.MutualSslConfiguration import net.corda.nodeapi.internal.crypto.* import org.apache.commons.io.FileUtils import sun.security.tools.keytool.CertAndKeyGen @@ -65,7 +67,7 @@ class KeyStores(val keyStore: UnsafeKeyStore, val trustStore: UnsafeKeyStore) { val keyStoreFile = keyStore.toTemporaryFile("sslkeystore", directory = directory) val trustStoreFile = trustStore.toTemporaryFile("truststore", directory = directory) - val sslConfiguration = sslConfiguration(directory) + val sslConfiguration = sslConfiguration(keyStoreFile, trustStoreFile) return object : AutoClosableSSLConfiguration { override val value = sslConfiguration @@ -77,16 +79,16 @@ class KeyStores(val keyStore: UnsafeKeyStore, val trustStore: UnsafeKeyStore) { } } - data class TestSslOptions(override val certificatesDirectory: Path, - override val keyStorePassword: String, - override val trustStorePassword: String, - override val crlCheckSoftFail: Boolean) : SSLConfiguration + private fun sslConfiguration(keyStoreFile: TemporaryFile, trustStoreFile: TemporaryFile): MutualSslConfiguration { - private fun sslConfiguration(directory: Path) = TestSslOptions(directory, keyStore.password, trustStore.password, true) + val keyStore = FileBasedCertificateStoreSupplier(keyStoreFile.file, keyStore.password) + val trustStore = FileBasedCertificateStoreSupplier(trustStoreFile.file, trustStore.password) + return SslConfiguration.mutual(keyStore, trustStore) + } } interface AutoClosableSSLConfiguration : AutoCloseable { - val value: SSLConfiguration + val value: MutualSslConfiguration } typealias KeyStoreEntry = Pair @@ -189,7 +191,7 @@ private fun newKeyStore(type: String, password: String): KeyStore { return keyStore } -fun withKeyStores(server: KeyStores, client: KeyStores, action: (brokerSslOptions: SSLConfiguration, clientSslOptions: SSLConfiguration) -> Unit) { +fun withKeyStores(server: KeyStores, client: KeyStores, action: (brokerSslOptions: MutualSslConfiguration, clientSslOptions: MutualSslConfiguration) -> Unit) { val serverDir = Files.createTempDirectory(null) FileUtils.forceDeleteOnExit(serverDir.toFile()) diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt index 5b4555e547..4ddca172af 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt @@ -9,15 +9,15 @@ import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate import net.corda.core.node.NodeInfo import net.corda.core.transactions.WireTransaction -import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.loggerFor -import net.corda.node.services.config.configureDevKeyAndTrustStores import net.corda.nodeapi.BrokerRpcSslOptions -import net.corda.nodeapi.internal.config.SSLConfiguration -import net.corda.nodeapi.internal.createDevKeyStores +import net.corda.nodeapi.internal.config.MutualSslConfiguration +import net.corda.nodeapi.internal.registerDevP2pCertificates import net.corda.nodeapi.internal.createDevNodeCa import net.corda.nodeapi.internal.crypto.* +import net.corda.nodeapi.internal.loadDevCaTrustStore import net.corda.serialization.internal.amqp.AMQP_ENABLED +import net.corda.testing.internal.stubs.CertificateStoreStubs import java.nio.file.Files import java.nio.file.Path import java.security.KeyPair @@ -37,17 +37,17 @@ inline fun T.amqpSpecific(reason: String, function: () -> Unit loggerFor().info("Ignoring AMQP specific test, reason: $reason") } -fun configureTestSSL(legalName: CordaX500Name): SSLConfiguration { - return object : SSLConfiguration { - override val certificatesDirectory = Files.createTempDirectory("certs") - override val keyStorePassword: String get() = "cordacadevpass" - override val trustStorePassword: String get() = "trustpass" - override val crlCheckSoftFail: Boolean = true +fun configureTestSSL(legalName: CordaX500Name): MutualSslConfiguration { - init { - configureDevKeyAndTrustStores(legalName) - } + val certificatesDirectory = Files.createTempDirectory("certs") + val config = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory) + if (config.trustStore.getOptional() == null) { + loadDevCaTrustStore().copyTo(config.trustStore.get(true)) } + if (config.keyStore.getOptional() == null) { + config.keyStore.get(true).registerDevP2pCertificates(legalName) + } + return config } private val defaultRootCaName = X500Principal("CN=Corda Root CA,O=R3 Ltd,L=London,C=GB") @@ -103,17 +103,6 @@ fun BrokerRpcSslOptions.useSslRpcOverrides(): Map { ) } -fun SSLConfiguration.noSslRpcOverrides(rpcAdminAddress: NetworkHostAndPort): Map { - return mapOf( - "rpcSettings.adminAddress" to rpcAdminAddress.toString(), - "rpcSettings.useSsl" to "false", - "rpcSettings.ssl.certificatesDirectory" to certificatesDirectory.toString(), - "rpcSettings.ssl.keyStorePassword" to keyStorePassword, - "rpcSettings.ssl.trustStorePassword" to trustStorePassword, - "rpcSettings.ssl.crlCheckSoftFail" to true - ) -} - /** * Until we have proper handling of multiple identities per node, for tests we use the first identity as special one. * TODO: Should be removed after multiple identities are introduced. @@ -127,19 +116,12 @@ fun NodeInfo.chooseIdentityAndCert(): PartyAndCertificate = legalIdentitiesAndCe */ fun NodeInfo.chooseIdentity(): Party = chooseIdentityAndCert().party -fun createNodeSslConfig(path: Path, name: CordaX500Name = CordaX500Name("MegaCorp", "London", "GB")): SSLConfiguration { - val sslConfig = object : SSLConfiguration { - override val crlCheckSoftFail = true - override val certificatesDirectory = path - override val keyStorePassword = "serverstorepass" - override val trustStorePassword = "trustpass" - } +fun p2pSslOptions(path: Path, name: CordaX500Name = CordaX500Name("MegaCorp", "London", "GB")): MutualSslConfiguration { + val sslConfig = CertificateStoreStubs.P2P.withCertificatesDirectory(path, keyStorePassword = "serverstorepass") val (rootCa, intermediateCa) = createDevIntermediateCaCertPath() - sslConfig.createDevKeyStores(name, rootCa.certificate, intermediateCa) - val trustStore = loadOrCreateKeyStore(sslConfig.trustStoreFile, sslConfig.trustStorePassword) - trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCa.certificate) - trustStore.save(sslConfig.trustStoreFile, sslConfig.trustStorePassword) - + sslConfig.keyStore.get(true).registerDevP2pCertificates(name, rootCa.certificate, intermediateCa) + val trustStore = sslConfig.trustStore.get(true) + trustStore[X509Utilities.CORDA_ROOT_CA] = rootCa.certificate return sslConfig } diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/stubs/CertificateStoreStubs.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/stubs/CertificateStoreStubs.kt new file mode 100644 index 0000000000..1eba7b9aa5 --- /dev/null +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/stubs/CertificateStoreStubs.kt @@ -0,0 +1,101 @@ +package net.corda.testing.internal.stubs + +import net.corda.core.internal.div +import net.corda.nodeapi.internal.config.FileBasedCertificateStoreSupplier +import net.corda.nodeapi.internal.config.SslConfiguration +import net.corda.nodeapi.internal.config.MutualSslConfiguration +import java.nio.file.Path + +class CertificateStoreStubs { + + companion object { + + const val DEFAULT_CERTIFICATES_DIRECTORY_NAME = "certificates" + + @JvmStatic + fun withStoreAt(certificateStorePath: Path, password: String): FileBasedCertificateStoreSupplier = FileBasedCertificateStoreSupplier(certificateStorePath, password) + } + + class Signing { + + companion object { + + const val DEFAULT_STORE_FILE_NAME = "nodekeystore.jks" + const val DEFAULT_STORE_PASSWORD = "cordacadevpass" + + @JvmStatic + fun withCertificatesDirectory(certificatesDirectory: Path, password: String = DEFAULT_STORE_PASSWORD, certificateStoreFileName: String = DEFAULT_STORE_FILE_NAME): FileBasedCertificateStoreSupplier { + + return FileBasedCertificateStoreSupplier(certificatesDirectory / certificateStoreFileName, password) + } + + @JvmStatic + fun withBaseDirectory(baseDirectory: Path, password: String = DEFAULT_STORE_PASSWORD, certificatesDirectoryName: String = DEFAULT_CERTIFICATES_DIRECTORY_NAME, certificateStoreFileName: String = DEFAULT_STORE_FILE_NAME): FileBasedCertificateStoreSupplier { + + return FileBasedCertificateStoreSupplier(baseDirectory / certificatesDirectoryName / certificateStoreFileName, password) + } + } + } + + class P2P { + + companion object { + + @JvmStatic + fun withCertificatesDirectory(certificatesDirectory: Path, keyStoreFileName: String = KeyStore.DEFAULT_STORE_FILE_NAME, keyStorePassword: String = KeyStore.DEFAULT_STORE_PASSWORD, trustStoreFileName: String = TrustStore.DEFAULT_STORE_FILE_NAME, trustStorePassword: String = TrustStore.DEFAULT_STORE_PASSWORD): MutualSslConfiguration { + + val keyStore = FileBasedCertificateStoreSupplier(certificatesDirectory / keyStoreFileName, keyStorePassword) + val trustStore = FileBasedCertificateStoreSupplier(certificatesDirectory / trustStoreFileName, trustStorePassword) + return SslConfiguration.mutual(keyStore, trustStore) + } + + @JvmStatic + fun withBaseDirectory(baseDirectory: Path, certificatesDirectoryName: String = DEFAULT_CERTIFICATES_DIRECTORY_NAME, keyStoreFileName: String = KeyStore.DEFAULT_STORE_FILE_NAME, keyStorePassword: String = KeyStore.DEFAULT_STORE_PASSWORD, trustStoreFileName: String = TrustStore.DEFAULT_STORE_FILE_NAME, trustStorePassword: String = TrustStore.DEFAULT_STORE_PASSWORD): MutualSslConfiguration { + + return withCertificatesDirectory(baseDirectory / certificatesDirectoryName, keyStorePassword, trustStorePassword, keyStoreFileName, trustStoreFileName) + } + } + + class KeyStore { + + companion object { + + const val DEFAULT_STORE_FILE_NAME = "sslkeystore.jks" + const val DEFAULT_STORE_PASSWORD = "cordacadevpass" + + @JvmStatic + fun withCertificatesDirectory(certificatesDirectory: Path, password: String = DEFAULT_STORE_PASSWORD, certificateStoreFileName: String = DEFAULT_STORE_FILE_NAME): FileBasedCertificateStoreSupplier { + + return FileBasedCertificateStoreSupplier(certificatesDirectory / certificateStoreFileName, password) + } + + @JvmStatic + fun withBaseDirectory(baseDirectory: Path, password: String = DEFAULT_STORE_PASSWORD, certificatesDirectoryName: String = DEFAULT_CERTIFICATES_DIRECTORY_NAME, certificateStoreFileName: String = DEFAULT_STORE_FILE_NAME): FileBasedCertificateStoreSupplier { + + return FileBasedCertificateStoreSupplier(baseDirectory / certificatesDirectoryName / certificateStoreFileName, password) + } + } + } + + class TrustStore { + + companion object { + + const val DEFAULT_STORE_FILE_NAME = "truststore.jks" + const val DEFAULT_STORE_PASSWORD = "trustpass" + + @JvmStatic + fun withCertificatesDirectory(certificatesDirectory: Path, password: String = DEFAULT_STORE_PASSWORD, certificateStoreFileName: String = DEFAULT_STORE_FILE_NAME): FileBasedCertificateStoreSupplier { + + return FileBasedCertificateStoreSupplier(certificatesDirectory / certificateStoreFileName, password) + } + + @JvmStatic + fun withBaseDirectory(baseDirectory: Path, password: String = DEFAULT_STORE_PASSWORD, certificatesDirectoryName: String = DEFAULT_CERTIFICATES_DIRECTORY_NAME, certificateStoreFileName: String = DEFAULT_STORE_FILE_NAME): FileBasedCertificateStoreSupplier { + + return FileBasedCertificateStoreSupplier(baseDirectory / certificatesDirectoryName / certificateStoreFileName, password) + } + } + } + } +} \ No newline at end of file diff --git a/tools/shell/src/integration-test/kotlin/net/corda/tools/shell/InteractiveShellIntegrationTest.kt b/tools/shell/src/integration-test/kotlin/net/corda/tools/shell/InteractiveShellIntegrationTest.kt index 8d382d2604..623fcd5670 100644 --- a/tools/shell/src/integration-test/kotlin/net/corda/tools/shell/InteractiveShellIntegrationTest.kt +++ b/tools/shell/src/integration-test/kotlin/net/corda/tools/shell/InteractiveShellIntegrationTest.kt @@ -131,7 +131,7 @@ class InteractiveShellIntegrationTest { driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) { startNode().getOrThrow().use { node -> val conf = (node as NodeHandleInternal).configuration.toShellConfig() - InteractiveShell.startShellInternal(conf) + InteractiveShell.startShell(conf) assertThatThrownBy { InteractiveShell.nodeInfo() }.isInstanceOf(ActiveMQSecurityException::class.java) } } diff --git a/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt b/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt index ed1b5e94cd..9b3f926c97 100644 --- a/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt +++ b/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt @@ -9,7 +9,6 @@ import net.corda.client.jackson.StringToMethodCallParser import net.corda.client.rpc.CordaRPCClientConfiguration import net.corda.client.rpc.CordaRPCConnection import net.corda.client.rpc.PermissionException -import net.corda.client.rpc.internal.createCordaRPCClientWithInternalSslAndClassLoader import net.corda.client.rpc.internal.createCordaRPCClientWithSslAndClassLoader import net.corda.core.CordaException import net.corda.core.concurrent.CordaFuture @@ -93,24 +92,6 @@ object InteractiveShell { _startShell(configuration, classLoader) } - /** - * Starts an interactive shell connected to the local terminal. This shell gives administrator access to the node - * internals. - */ - fun startShellInternal(configuration: ShellConfiguration, classLoader: ClassLoader? = null) { - rpcOps = { username: String, credentials: String -> - val client = createCordaRPCClientWithInternalSslAndClassLoader(hostAndPort = configuration.hostAndPort, - configuration = CordaRPCClientConfiguration.DEFAULT.copy( - maxReconnectAttempts = 1 - ), - sslConfiguration = configuration.nodeSslConfig, - classLoader = classLoader) - this.connection = client.start(username, credentials) - connection.proxy - } - _startShell(configuration, classLoader) - } - private fun _startShell(configuration: ShellConfiguration, classLoader: ClassLoader? = null) { shellConfiguration = configuration InteractiveShell.classLoader = classLoader diff --git a/tools/shell/src/main/kotlin/net/corda/tools/shell/ShellConfiguration.kt b/tools/shell/src/main/kotlin/net/corda/tools/shell/ShellConfiguration.kt index 90d5f8407c..4877b520cc 100644 --- a/tools/shell/src/main/kotlin/net/corda/tools/shell/ShellConfiguration.kt +++ b/tools/shell/src/main/kotlin/net/corda/tools/shell/ShellConfiguration.kt @@ -2,7 +2,6 @@ package net.corda.tools.shell import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.messaging.ClientRpcSslOptions -import net.corda.nodeapi.internal.config.SSLConfiguration import java.nio.file.Path data class ShellConfiguration( @@ -12,7 +11,6 @@ data class ShellConfiguration( var password: String = "", val hostAndPort: NetworkHostAndPort, val ssl: ClientRpcSslOptions? = null, - val nodeSslConfig: SSLConfiguration? = null, val sshdPort: Int? = null, val sshHostKeyDirectory: Path? = null, val noLocalShell: Boolean = false) { diff --git a/webserver/src/main/kotlin/net/corda/webserver/WebServerConfig.kt b/webserver/src/main/kotlin/net/corda/webserver/WebServerConfig.kt index 5ba08b691d..69569e8dbf 100644 --- a/webserver/src/main/kotlin/net/corda/webserver/WebServerConfig.kt +++ b/webserver/src/main/kotlin/net/corda/webserver/WebServerConfig.kt @@ -1,8 +1,8 @@ package net.corda.webserver import com.typesafe.config.Config +import net.corda.core.internal.div import net.corda.core.utilities.NetworkHostAndPort -import net.corda.nodeapi.internal.config.NodeSSLConfiguration import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.config.getValue import net.corda.nodeapi.internal.config.parseAs @@ -11,10 +11,13 @@ import java.nio.file.Path /** * [baseDirectory] is not retrieved from the config file but rather from a command line argument. */ -class WebServerConfig(override val baseDirectory: Path, val config: Config) : NodeSSLConfiguration { - override val keyStorePassword: String by config - override val trustStorePassword: String by config - override val crlCheckSoftFail: Boolean by config +class WebServerConfig(val baseDirectory: Path, val config: Config) { + + val keyStorePath: String by config + val keyStorePassword: String by config + val trustStorePath: String by config + val trustStorePassword: String by config + val useHTTPS: Boolean by config val myLegalName: String by config val rpcAddress: NetworkHostAndPort by lazy { diff --git a/webserver/src/main/kotlin/net/corda/webserver/internal/NodeWebServer.kt b/webserver/src/main/kotlin/net/corda/webserver/internal/NodeWebServer.kt index 5bf16e4a7a..9ca4e03546 100644 --- a/webserver/src/main/kotlin/net/corda/webserver/internal/NodeWebServer.kt +++ b/webserver/src/main/kotlin/net/corda/webserver/internal/NodeWebServer.kt @@ -66,10 +66,10 @@ class NodeWebServer(val config: WebServerConfig) { httpsConfiguration.outputBufferSize = 32768 httpsConfiguration.addCustomizer(SecureRequestCustomizer()) val sslContextFactory = SslContextFactory() - sslContextFactory.keyStorePath = config.sslKeystore.toString() + sslContextFactory.keyStorePath = config.keyStorePath sslContextFactory.setKeyStorePassword(config.keyStorePassword) sslContextFactory.setKeyManagerPassword(config.keyStorePassword) - sslContextFactory.setTrustStorePath(config.trustStoreFile.toString()) + sslContextFactory.setTrustStorePath(config.trustStorePath) sslContextFactory.setTrustStorePassword(config.trustStorePassword) sslContextFactory.setExcludeProtocols("SSL.*", "TLSv1", "TLSv1.1") sslContextFactory.setIncludeProtocols("TLSv1.2") From 31e58dd2e58921bbe723d2998e2019f0cb620807 Mon Sep 17 00:00:00 2001 From: Michele Sollecito Date: Tue, 4 Sep 2018 11:20:34 +0100 Subject: [PATCH 2/3] [CORDA-1937]: Fixes to enterprise float and bridge. --- .../corda/bridge/smoketest/BridgeSmokeTest.kt | 46 ++++---- .../net/corda/bridge/BridgeIntegrationTest.kt | 44 +++++--- .../corda/bridge/services/AMQPListenerTest.kt | 47 ++++---- .../bridge/services/ArtemisConnectionTest.kt | 14 ++- .../services/api/FirewallConfiguration.kt | 15 +-- .../BridgeArtemisConnectionServiceImpl.kt | 10 +- .../config/FirewallConfigurationImpl.kt | 37 ++++--- .../receiver/BridgeAMQPListenerServiceImpl.kt | 25 +++-- .../receiver/FloatControlListenerService.kt | 17 ++- .../InProcessBridgeReceiverService.kt | 16 +-- .../TunnelingBridgeReceiverService.kt | 31 +++--- .../sender/DirectBridgeSenderService.kt | 2 +- .../net/corda/bridge/BridgeTestHelper.kt | 23 ++-- .../kotlin/net/corda/bridge/ConfigTest.kt | 36 +++--- .../client/rpc/CordaRPCJavaClientTest.java | 1 - .../net/corda/client/rpc/CordaRPCClient.kt | 4 +- .../rpc/internal/CordaRPCClientUtils.kt | 7 ++ .../net/corda/core/internal/InternalUtils.kt | 3 + .../net/corda/core/messaging/CordaRPCOps.kt | 6 + .../net/corda/flowworker/FlowWorkerTest.kt | 16 +-- .../kotlin/net/corda/flowworker/FlowWorker.kt | 2 +- .../corda/rpcWorker/RpcFlowWorkerDriver.kt | 10 +- .../net/corda/rpcWorker/CordaRpcWorkerOps.kt | 3 +- .../kotlin/net/corda/rpcWorker/RpcWorker.kt | 12 +- .../internal/ArtemisMessagingClient.kt | 17 +-- .../internal/bridging/AMQPBridgeManager.kt | 7 +- .../bridging/BridgeControlListener.kt | 82 ++++++++++++-- .../protonwrapper/netty/AMQPServer.kt | 2 +- .../kotlin/net/corda/node/BootTests.kt | 4 - .../net/corda/node/NodeKeystoreCheckTest.kt | 13 ++- .../net/corda/node/amqp/AMQPBridgeTest.kt | 103 ++++++++++++++++++ .../net/corda/node/amqp/ProtonWrapperTests.kt | 23 +++- .../kotlin/net/corda/node/amqp/SocksTests.kt | 76 ++++++++----- .../node/services/network/NetworkMapTest.kt | 33 +++--- .../services/messaging/MQSecurityTest.kt | 28 +++-- .../net/corda/node/internal/AbstractNode.kt | 2 +- .../kotlin/net/corda/node/internal/Node.kt | 43 ++++++-- .../node/services/config/NodeConfiguration.kt | 92 +++++++++++++++- .../testing/node/internal/DriverDSLImpl.kt | 20 ++-- .../testing/node/internal/NodeBasedTest.kt | 10 +- .../service/proxy/CordaRPCProxyClient.kt | 80 +++++++------- 41 files changed, 709 insertions(+), 353 deletions(-) diff --git a/bridge/bridgecapsule/src/smoke-test/kotlin/net/corda/bridge/smoketest/BridgeSmokeTest.kt b/bridge/bridgecapsule/src/smoke-test/kotlin/net/corda/bridge/smoketest/BridgeSmokeTest.kt index 3648f0cd33..0d7a16b993 100644 --- a/bridge/bridgecapsule/src/smoke-test/kotlin/net/corda/bridge/smoketest/BridgeSmokeTest.kt +++ b/bridge/bridgecapsule/src/smoke-test/kotlin/net/corda/bridge/smoketest/BridgeSmokeTest.kt @@ -17,12 +17,12 @@ import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.messaging.ArtemisMessagingServer import net.corda.nodeapi.internal.* import net.corda.nodeapi.internal.bridging.BridgeControl -import net.corda.nodeapi.internal.config.NodeSSLConfiguration -import net.corda.nodeapi.internal.config.SSLConfiguration +import net.corda.nodeapi.internal.config.MutualSslConfiguration import net.corda.nodeapi.internal.crypto.* import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.testing.core.* import net.corda.testing.internal.rigorousMock +import net.corda.testing.internal.stubs.CertificateStoreStubs import org.apache.activemq.artemis.api.core.RoutingType import org.apache.activemq.artemis.api.core.SimpleString import org.apache.curator.test.TestingServer @@ -67,22 +67,20 @@ class BridgeSmokeTest { @Test fun `Run full features bridge from jar to ensure everything works`() { - val artemisConfig = object : NodeSSLConfiguration { - override val baseDirectory: Path = tempFolder.root.toPath() - override val keyStorePassword: String = "cordacadevpass" - override val trustStorePassword: String = "trustpass" - override val crlCheckSoftFail: Boolean = true - } + + val baseDirectory = tempFolder.root.toPath().createDirectories() + val artemisConfig = CertificateStoreStubs.P2P.withBaseDirectory(baseDirectory) + artemisConfig.createBridgeKeyStores(DUMMY_BANK_A_NAME) copyBridgeResource("corda-firewall.jar") copyBridgeResource("firewall.conf") - createNetworkParams(tempFolder.root.toPath()) + createNetworkParams(baseDirectory) val (artemisServer, artemisClient) = createArtemis() val zkServer = TestingServer(11105, false) try { installBridgeControlResponder(artemisClient) zkServer.start() - val bridge = startBridge(tempFolder.root.toPath()) + val bridge = startBridge(baseDirectory) waitForBridge(bridge) } finally { zkServer.close() @@ -115,18 +113,18 @@ class BridgeSmokeTest { copier.install(baseDirectory) } - private fun SSLConfiguration.createBridgeKeyStores(legalName: CordaX500Name, - rootCert: X509Certificate = DEV_ROOT_CA.certificate, - intermediateCa: CertificateAndKeyPair = DEV_INTERMEDIATE_CA) { + private fun MutualSslConfiguration.createBridgeKeyStores(legalName: CordaX500Name, + rootCert: X509Certificate = DEV_ROOT_CA.certificate, + intermediateCa: CertificateAndKeyPair = DEV_INTERMEDIATE_CA) { - certificatesDirectory.createDirectories() - if (!trustStoreFile.exists()) { - loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/$DEV_CA_TRUST_STORE_FILE"), DEV_CA_TRUST_STORE_PASS).save(trustStoreFile, trustStorePassword) + if (!trustStore.path.exists()) { + val trustStore = trustStore.get(true) + loadDevCaTrustStore().copyTo(trustStore) } val (nodeCaCert, nodeCaKeyPair) = createDevNodeCa(intermediateCa, legalName) - val sslKeyStore = loadSslKeyStore(createNew = true) + val sslKeyStore = keyStore.get(createNew = true) sslKeyStore.update { val tlsKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, nodeCaCert, nodeCaKeyPair, legalName.x500Principal, tlsKeyPair.public) @@ -203,17 +201,23 @@ class BridgeSmokeTest { } private fun createArtemis(): Pair { + val baseDirectory = tempFolder.root.toPath() + val certificatesDirectory = baseDirectory / "certificates" + val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory) + val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory) + val artemisConfig = rigorousMock().also { - doReturn(tempFolder.root.toPath()).whenever(it).baseDirectory + doReturn(baseDirectory).whenever(it).baseDirectory + doReturn(certificatesDirectory).whenever(it).certificatesDirectory doReturn(ALICE_NAME).whenever(it).myLegalName - doReturn("trustpass").whenever(it).trustStorePassword - doReturn("cordacadevpass").whenever(it).keyStorePassword + doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions + doReturn(signingCertificateStore).whenever(it).signingCertificateStore doReturn(NetworkHostAndPort("localhost", 11005)).whenever(it).p2pAddress doReturn(null).whenever(it).jmxMonitoringHttpPort doReturn(EnterpriseConfiguration(MutualExclusionConfiguration(false, "", 20000, 40000), externalBridge = true)).whenever(it).enterpriseConfiguration } val artemisServer = ArtemisMessagingServer(artemisConfig, NetworkHostAndPort("0.0.0.0", 11005), MAX_MESSAGE_SIZE) - val artemisClient = ArtemisMessagingClient(artemisConfig, NetworkHostAndPort("localhost", 11005), MAX_MESSAGE_SIZE) + val artemisClient = ArtemisMessagingClient(artemisConfig.p2pSslOptions, NetworkHostAndPort("localhost", 11005), MAX_MESSAGE_SIZE) artemisServer.start() artemisClient.start() return Pair(artemisServer, artemisClient) diff --git a/bridge/src/integration-test/kotlin/net/corda/bridge/BridgeIntegrationTest.kt b/bridge/src/integration-test/kotlin/net/corda/bridge/BridgeIntegrationTest.kt index 050f3f7fa5..926dae3e16 100644 --- a/bridge/src/integration-test/kotlin/net/corda/bridge/BridgeIntegrationTest.kt +++ b/bridge/src/integration-test/kotlin/net/corda/bridge/BridgeIntegrationTest.kt @@ -28,6 +28,7 @@ import net.corda.testing.core.DUMMY_BANK_A_NAME import net.corda.testing.core.MAX_MESSAGE_SIZE import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.internal.rigorousMock +import net.corda.testing.internal.stubs.CertificateStoreStubs import org.apache.activemq.artemis.api.core.RoutingType import org.apache.activemq.artemis.api.core.SimpleString import org.apache.curator.test.TestingServer @@ -387,61 +388,76 @@ class BridgeIntegrationTest { private fun createArtemis(): Pair { + val baseDirectory = tempFolder.root.toPath() + val certificatesDirectory = baseDirectory / "certificates" + val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory) + val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory) val artemisConfig = rigorousMock().also { - doReturn(tempFolder.root.toPath()).whenever(it).baseDirectory + doReturn(baseDirectory).whenever(it).baseDirectory + doReturn(certificatesDirectory).whenever(it).certificatesDirectory doReturn(ALICE_NAME).whenever(it).myLegalName - doReturn("trustpass").whenever(it).trustStorePassword - doReturn("cordacadevpass").whenever(it).keyStorePassword + doReturn(signingCertificateStore).whenever(it).signingCertificateStore + doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions doReturn(NetworkHostAndPort("localhost", 11005)).whenever(it).p2pAddress doReturn(null).whenever(it).jmxMonitoringHttpPort doReturn(EnterpriseConfiguration(MutualExclusionConfiguration(false, "", 20000, 40000), externalBridge = true)).whenever(it).enterpriseConfiguration } val artemisServer = ArtemisMessagingServer(artemisConfig, NetworkHostAndPort("0.0.0.0", 11005), MAX_MESSAGE_SIZE) - val artemisClient = ArtemisMessagingClient(artemisConfig, NetworkHostAndPort("localhost", 11005), MAX_MESSAGE_SIZE) + val artemisClient = ArtemisMessagingClient(artemisConfig.p2pSslOptions, NetworkHostAndPort("localhost", 11005), MAX_MESSAGE_SIZE) artemisServer.start() artemisClient.start() return Pair(artemisServer, artemisClient) } private fun createArtemis2(): Pair { - val originalCertsFolderPath = tempFolder.root.toPath() / "certificates" - val folderPath = tempFolder.root.toPath() / "artemis2" + val baseDirectory = tempFolder.root.toPath() + val originalCertsFolderPath = baseDirectory / "certificates" + val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(originalCertsFolderPath) + val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(originalCertsFolderPath) + + val folderPath = baseDirectory / "artemis2" val newCertsFolderPath = folderPath / "certificates" newCertsFolderPath.createDirectories() (originalCertsFolderPath / "truststore.jks").copyToDirectory(newCertsFolderPath) (originalCertsFolderPath / "sslkeystore.jks").copyToDirectory(newCertsFolderPath) val artemisConfig = rigorousMock().also { doReturn(folderPath).whenever(it).baseDirectory + doReturn(originalCertsFolderPath).whenever(it).certificatesDirectory doReturn(ALICE_NAME).whenever(it).myLegalName - doReturn("trustpass").whenever(it).trustStorePassword - doReturn("cordacadevpass").whenever(it).keyStorePassword + doReturn(signingCertificateStore).whenever(it).signingCertificateStore + doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions doReturn(NetworkHostAndPort("localhost", 12005)).whenever(it).p2pAddress doReturn(null).whenever(it).jmxMonitoringHttpPort doReturn(EnterpriseConfiguration(MutualExclusionConfiguration(false, "", 20000, 40000), externalBridge = true)).whenever(it).enterpriseConfiguration } val artemisServer = ArtemisMessagingServer(artemisConfig, NetworkHostAndPort("0.0.0.0", 12005), MAX_MESSAGE_SIZE) - val artemisClient = ArtemisMessagingClient(artemisConfig, NetworkHostAndPort("localhost", 12005), MAX_MESSAGE_SIZE) + val artemisClient = ArtemisMessagingClient(artemisConfig.p2pSslOptions, NetworkHostAndPort("localhost", 12005), MAX_MESSAGE_SIZE) return Pair(artemisServer, artemisClient) } private fun createDummyPeerArtemis(): Pair { - val originalCertsFolderPath = tempFolder.root.toPath() / "certificates" - val folderPath = tempFolder.root.toPath() / "artemis3" + val baseDirectory = tempFolder.root.toPath() + val originalCertsFolderPath = baseDirectory / "certificates" + val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(originalCertsFolderPath) + val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(originalCertsFolderPath) + + val folderPath = baseDirectory / "artemis3" val newCertsFolderPath = folderPath / "certificates" newCertsFolderPath.createDirectories() (originalCertsFolderPath / "truststore.jks").copyToDirectory(newCertsFolderPath) (originalCertsFolderPath / "sslkeystore.jks").copyToDirectory(newCertsFolderPath) val artemisConfig = rigorousMock().also { doReturn(folderPath).whenever(it).baseDirectory + doReturn(newCertsFolderPath).whenever(it).certificatesDirectory doReturn(DUMMY_BANK_A_NAME).whenever(it).myLegalName - doReturn("trustpass").whenever(it).trustStorePassword - doReturn("cordacadevpass").whenever(it).keyStorePassword + doReturn(signingCertificateStore).whenever(it).signingCertificateStore + doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions doReturn(NetworkHostAndPort("localhost", 7890)).whenever(it).p2pAddress doReturn(null).whenever(it).jmxMonitoringHttpPort doReturn(EnterpriseConfiguration(MutualExclusionConfiguration(false, "", 20000, 40000), externalBridge = true)).whenever(it).enterpriseConfiguration } val artemisServer = ArtemisMessagingServer(artemisConfig, NetworkHostAndPort("0.0.0.0", 7890), MAX_MESSAGE_SIZE) - val artemisClient = ArtemisMessagingClient(artemisConfig, NetworkHostAndPort("localhost", 7890), MAX_MESSAGE_SIZE) + val artemisClient = ArtemisMessagingClient(artemisConfig.p2pSslOptions, NetworkHostAndPort("localhost", 7890), MAX_MESSAGE_SIZE) artemisServer.start() artemisClient.start() artemisClient.started!!.session.createQueue(SimpleString("${P2P_PREFIX}12345"), RoutingType.ANYCAST, SimpleString("${P2P_PREFIX}12345"), true) diff --git a/bridge/src/integration-test/kotlin/net/corda/bridge/services/AMQPListenerTest.kt b/bridge/src/integration-test/kotlin/net/corda/bridge/services/AMQPListenerTest.kt index 75133a6b4c..63bfbeae50 100644 --- a/bridge/src/integration-test/kotlin/net/corda/bridge/services/AMQPListenerTest.kt +++ b/bridge/src/integration-test/kotlin/net/corda/bridge/services/AMQPListenerTest.kt @@ -12,6 +12,7 @@ import net.corda.core.internal.div import net.corda.core.internal.readAll import net.corda.core.utilities.NetworkHostAndPort import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREFIX +import net.corda.nodeapi.internal.config.CertificateStore import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.protonwrapper.messages.MessageStatus @@ -25,7 +26,6 @@ import org.junit.Assert.assertArrayEquals import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder -import java.security.KeyStore import kotlin.test.assertEquals class AMQPListenerTest { @@ -58,14 +58,14 @@ class AMQPListenerTest { assertEquals(true, stateFollower.next()) assertEquals(true, amqpListenerService.active) assertEquals(false, serverListening("localhost", 10005)) - val keyStoreBytes = bridgeConfig.sslKeystore.readAll() - val trustStoreBytes = bridgeConfig.trustStoreFile.readAll() + val keyStoreBytes = bridgeConfig.p2pSslOptions.keyStore.path.readAll() + val trustStoreBytes = bridgeConfig.p2pSslOptions.trustStore.path.readAll() // start listening amqpListenerService.provisionKeysAndActivate(keyStoreBytes, - bridgeConfig.keyStorePassword.toCharArray(), - bridgeConfig.keyStorePassword.toCharArray(), + bridgeConfig.p2pSslOptions.keyStore.password.toCharArray(), + bridgeConfig.p2pSslOptions.keyStore.password.toCharArray(), trustStoreBytes, - bridgeConfig.trustStorePassword.toCharArray()) + bridgeConfig.p2pSslOptions.trustStore.password.toCharArray()) // Fire lots of activity to prove we are good assertEquals(TestAuditService.AuditEvent.STATUS_CHANGE, auditFollower.next()) assertEquals(true, amqpListenerService.active) @@ -76,12 +76,11 @@ class AMQPListenerTest { assertEquals(TestAuditService.AuditEvent.FAILED_CONNECTION, auditFollower.next()) val clientConfig = createAndLoadConfigFromResource(tempFolder.root.toPath() / "client", configResource) clientConfig.createBridgeKeyStores(DUMMY_BANK_B_NAME) - val clientKeyStore = clientConfig.loadSslKeyStore().internal - val clientTrustStore = clientConfig.loadTrustStore().internal + val clientKeyStore = clientConfig.p2pSslOptions.keyStore.get() + val clientTrustStore = clientConfig.p2pSslOptions.trustStore.get() val amqpConfig = object : AMQPConfiguration { - override val keyStore: KeyStore = clientKeyStore - override val keyStorePrivateKeyPassword: CharArray = clientConfig.keyStorePassword.toCharArray() - override val trustStore: KeyStore = clientTrustStore + override val keyStore = clientKeyStore + override val trustStore = clientTrustStore override val maxMessageSize: Int = maxMessageSize override val trace: Boolean = true } @@ -134,26 +133,28 @@ class AMQPListenerTest { val amqpListenerService = BridgeAMQPListenerServiceImpl(bridgeConfig, maxMessageSize, auditService) amqpListenerService.start() auditService.start() - val keyStoreBytes = bridgeConfig.sslKeystore.readAll() - val trustStoreBytes = bridgeConfig.trustStoreFile.readAll() + val keyStoreBytes = bridgeConfig.p2pSslOptions.keyStore.path.readAll() + val trustStoreBytes = bridgeConfig.p2pSslOptions.trustStore.path.readAll() // start listening amqpListenerService.provisionKeysAndActivate(keyStoreBytes, - bridgeConfig.keyStorePassword.toCharArray(), - bridgeConfig.keyStorePassword.toCharArray(), + bridgeConfig.p2pSslOptions.keyStore.password.toCharArray(), + bridgeConfig.p2pSslOptions.keyStore.password.toCharArray(), trustStoreBytes, - bridgeConfig.trustStorePassword.toCharArray()) + bridgeConfig.p2pSslOptions.trustStore.password.toCharArray()) val connectionFollower = amqpListenerService.onConnection.toBlocking().iterator val auditFollower = auditService.onAuditEvent.toBlocking().iterator val clientKeys = Crypto.generateKeyPair(ECDSA_SECP256R1_SHA256) val clientCert = X509Utilities.createSelfSignedCACertificate(ALICE_NAME.x500Principal, clientKeys) - val clientKeyStore = X509KeyStore("password") - clientKeyStore.setPrivateKey("TLS_CERT", clientKeys.private, listOf(clientCert)) - val clientTrustStore = X509KeyStore("password") - clientTrustStore.setCertificate("TLS_ROOT", clientCert) + val clientKeyStoreRaw = X509KeyStore("password") + clientKeyStoreRaw.setPrivateKey("TLS_CERT", clientKeys.private, listOf(clientCert)) + val clientKeyStore = CertificateStore.of(clientKeyStoreRaw, "password") + + val clientTrustStoreRaw = X509KeyStore("password") + clientTrustStoreRaw.setCertificate("TLS_ROOT", clientCert) + val clientTrustStore = CertificateStore.of(clientTrustStoreRaw, "password") val amqpConfig = object : AMQPConfiguration { - override val keyStore: KeyStore = clientKeyStore.internal - override val keyStorePrivateKeyPassword: CharArray = "password".toCharArray() - override val trustStore: KeyStore = clientTrustStore.internal + override val keyStore = clientKeyStore + override val trustStore = clientTrustStore override val maxMessageSize: Int = maxMessageSize override val trace: Boolean = true } diff --git a/bridge/src/integration-test/kotlin/net/corda/bridge/services/ArtemisConnectionTest.kt b/bridge/src/integration-test/kotlin/net/corda/bridge/services/ArtemisConnectionTest.kt index 94e7d0d675..f9f46a2b60 100644 --- a/bridge/src/integration-test/kotlin/net/corda/bridge/services/ArtemisConnectionTest.kt +++ b/bridge/src/integration-test/kotlin/net/corda/bridge/services/ArtemisConnectionTest.kt @@ -6,6 +6,7 @@ import net.corda.bridge.createAndLoadConfigFromResource import net.corda.bridge.createBridgeKeyStores import net.corda.bridge.createNetworkParams import net.corda.bridge.services.artemis.BridgeArtemisConnectionServiceImpl +import net.corda.core.internal.div import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.services.config.EnterpriseConfiguration import net.corda.node.services.config.MutualExclusionConfiguration @@ -15,6 +16,7 @@ import net.corda.testing.core.DUMMY_BANK_A_NAME import net.corda.testing.core.MAX_MESSAGE_SIZE import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.internal.rigorousMock +import net.corda.testing.internal.stubs.CertificateStoreStubs import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder @@ -84,11 +86,17 @@ class ArtemisConnectionTest { private fun createArtemis(): ArtemisMessagingServer { + + val baseDirectory = tempFolder.root.toPath() + val certificatesDirectory = baseDirectory / "certificates" + val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory) + val p2pSslOptions = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory) val artemisConfig = rigorousMock().also { - doReturn(tempFolder.root.toPath()).whenever(it).baseDirectory + doReturn(baseDirectory).whenever(it).baseDirectory + doReturn(certificatesDirectory).whenever(it).certificatesDirectory doReturn(DUMMY_BANK_A_NAME).whenever(it).myLegalName - doReturn("trustpass").whenever(it).trustStorePassword - doReturn("cordacadevpass").whenever(it).keyStorePassword + doReturn(signingCertificateStore).whenever(it).signingCertificateStore + doReturn(p2pSslOptions).whenever(it).p2pSslOptions doReturn(NetworkHostAndPort("localhost", 11005)).whenever(it).p2pAddress doReturn(null).whenever(it).jmxMonitoringHttpPort doReturn(EnterpriseConfiguration(MutualExclusionConfiguration(false, "", 20000, 40000), externalBridge = true)).whenever(it).enterpriseConfiguration diff --git a/bridge/src/main/kotlin/net/corda/bridge/services/api/FirewallConfiguration.kt b/bridge/src/main/kotlin/net/corda/bridge/services/api/FirewallConfiguration.kt index e67e5e8973..720f93362e 100644 --- a/bridge/src/main/kotlin/net/corda/bridge/services/api/FirewallConfiguration.kt +++ b/bridge/src/main/kotlin/net/corda/bridge/services/api/FirewallConfiguration.kt @@ -2,8 +2,7 @@ package net.corda.bridge.services.api import net.corda.core.identity.CordaX500Name import net.corda.core.utilities.NetworkHostAndPort -import net.corda.nodeapi.internal.config.NodeSSLConfiguration -import net.corda.nodeapi.internal.config.SSLConfiguration +import net.corda.nodeapi.internal.config.MutualSslConfiguration import net.corda.nodeapi.internal.protonwrapper.netty.SocksProxyConfig import java.nio.file.Path @@ -28,12 +27,7 @@ enum class FirewallMode { FloatOuter } -interface BridgeSSLConfiguration : SSLConfiguration { - override val keyStorePassword: String - override val trustStorePassword: String - override val sslKeystore: Path - override val trustStoreFile: Path -} +interface BridgeSSLConfiguration : MutualSslConfiguration /** @@ -91,7 +85,8 @@ interface FloatOuterConfiguration { val customSSLConfiguration: BridgeSSLConfiguration? } -interface FirewallConfiguration : NodeSSLConfiguration { +interface FirewallConfiguration { + val baseDirectory: Path val firewallMode: FirewallMode val outboundConfig: BridgeOutboundConfiguration? val inboundConfig: BridgeInboundConfiguration? @@ -115,4 +110,6 @@ interface FirewallConfiguration : NodeSSLConfiguration { // This is relevant to bridges, because we push messages into the inbox and use the async acknowledgement responses to reply to sender. val p2pConfirmationWindowSize: Int val whitelistedHeaders: List + val crlCheckSoftFail: Boolean + val p2pSslOptions: MutualSslConfiguration } \ No newline at end of file diff --git a/bridge/src/main/kotlin/net/corda/bridge/services/artemis/BridgeArtemisConnectionServiceImpl.kt b/bridge/src/main/kotlin/net/corda/bridge/services/artemis/BridgeArtemisConnectionServiceImpl.kt index b2e987aede..fcabe28b38 100644 --- a/bridge/src/main/kotlin/net/corda/bridge/services/artemis/BridgeArtemisConnectionServiceImpl.kt +++ b/bridge/src/main/kotlin/net/corda/bridge/services/artemis/BridgeArtemisConnectionServiceImpl.kt @@ -1,15 +1,15 @@ package net.corda.bridge.services.artemis import net.corda.bridge.services.api.* -import net.corda.bridge.services.config.BridgeSSLConfigurationImpl import net.corda.bridge.services.util.ServiceStateCombiner import net.corda.bridge.services.util.ServiceStateHelper import net.corda.core.internal.ThreadBox import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.core.utilities.contextLogger -import net.corda.nodeapi.ArtemisTcpTransport import net.corda.nodeapi.internal.ArtemisMessagingClient import net.corda.nodeapi.internal.ArtemisMessagingComponent +import net.corda.nodeapi.internal.InternalArtemisTcpTransport +import net.corda.nodeapi.internal.config.MutualSslConfiguration import org.apache.activemq.artemis.api.core.client.ActiveMQClient import org.apache.activemq.artemis.api.core.client.FailoverEventType import org.apache.activemq.artemis.api.core.client.ServerLocator @@ -34,13 +34,13 @@ class BridgeArtemisConnectionServiceImpl(val conf: FirewallConfiguration, } private val state = ThreadBox(InnerState()) - private val sslConfiguration: BridgeSSLConfiguration + private val sslConfiguration: MutualSslConfiguration private val statusFollower: ServiceStateCombiner private var statusSubscriber: Subscription? = null init { statusFollower = ServiceStateCombiner(listOf(auditService)) - sslConfiguration = conf.outboundConfig?.customSSLConfiguration ?: BridgeSSLConfigurationImpl(conf) + sslConfiguration = conf.outboundConfig?.customSSLConfiguration ?: conf.p2pSslOptions } override fun start() { @@ -61,7 +61,7 @@ class BridgeArtemisConnectionServiceImpl(val conf: FirewallConfiguration, log.info("Connecting to message broker: ${outboundConf.artemisBrokerAddress}") val brokerAddresses = listOf(outboundConf.artemisBrokerAddress) + outboundConf.alternateArtemisBrokerAddresses // TODO Add broker CN to config for host verification in case the embedded broker isn't used - val tcpTransports = brokerAddresses.map { ArtemisTcpTransport.p2pConnectorTcpTransport(it, sslConfiguration) } + val tcpTransports = brokerAddresses.map { InternalArtemisTcpTransport.p2pConnectorTcpTransport(it, sslConfiguration) } locator = ActiveMQClient.createServerLocatorWithoutHA(*tcpTransports.toTypedArray()).apply { // Never time out on our loopback Artemis connections. If we switch back to using the InVM transport this // would be the default and the two lines below can be deleted. diff --git a/bridge/src/main/kotlin/net/corda/bridge/services/config/FirewallConfigurationImpl.kt b/bridge/src/main/kotlin/net/corda/bridge/services/config/FirewallConfigurationImpl.kt index 42f3bb485a..980c145bc6 100644 --- a/bridge/src/main/kotlin/net/corda/bridge/services/config/FirewallConfigurationImpl.kt +++ b/bridge/src/main/kotlin/net/corda/bridge/services/config/FirewallConfigurationImpl.kt @@ -6,22 +6,23 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.internal.div import net.corda.core.utilities.NetworkHostAndPort import net.corda.nodeapi.internal.ArtemisMessagingComponent -import net.corda.nodeapi.internal.config.NodeSSLConfiguration +import net.corda.nodeapi.internal.config.FileBasedCertificateStoreSupplier +import net.corda.nodeapi.internal.config.SslConfiguration +import net.corda.nodeapi.internal.config.MutualSslConfiguration import net.corda.nodeapi.internal.config.parseAs import net.corda.nodeapi.internal.protonwrapper.netty.SocksProxyConfig import java.nio.file.Path -import java.nio.file.Paths - fun Config.parseAsFirewallConfiguration(): FirewallConfiguration = parseAs() -data class BridgeSSLConfigurationImpl(override val keyStorePassword: String, - override val trustStorePassword: String, - override val certificatesDirectory: Path = Paths.get("certificates"), - override val sslKeystore: Path = certificatesDirectory / "sslkeystore.jks", - override val trustStoreFile: Path = certificatesDirectory / "truststore.jks", - override val crlCheckSoftFail: Boolean) : BridgeSSLConfiguration { - constructor(config: NodeSSLConfiguration) : this(config.keyStorePassword, config.trustStorePassword, config.certificatesDirectory, config.sslKeystore, config.trustStoreFile, config.crlCheckSoftFail) +data class BridgeSSLConfigurationImpl(private val sslKeystore: Path, + private val keyStorePassword: String, + private val trustStoreFile: Path, + private val trustStorePassword: String, + private val crlCheckSoftFail: Boolean) : BridgeSSLConfiguration { + + override val keyStore = FileBasedCertificateStoreSupplier(sslKeystore, keyStorePassword) + override val trustStore = FileBasedCertificateStoreSupplier(trustStoreFile, trustStorePassword) } data class BridgeOutboundConfigurationImpl(override val artemisBrokerAddress: NetworkHostAndPort, @@ -45,12 +46,12 @@ data class BridgeHAConfigImpl(override val haConnectionString: String, override data class FirewallConfigurationImpl( override val baseDirectory: Path, - override val certificatesDirectory: Path = baseDirectory / "certificates", - override val sslKeystore: Path = certificatesDirectory / "sslkeystore.jks", - override val trustStoreFile: Path = certificatesDirectory / "truststore.jks", + private val certificatesDirectory: Path = baseDirectory / "certificates", + private val sslKeystore: Path = certificatesDirectory / "sslkeystore.jks", + private val trustStoreFile: Path = certificatesDirectory / "truststore.jks", override val crlCheckSoftFail: Boolean, - override val keyStorePassword: String, - override val trustStorePassword: String, + private val keyStorePassword: String, + private val trustStorePassword: String, override val firewallMode: FirewallMode, override val networkParametersPath: Path, override val outboundConfig: BridgeOutboundConfigurationImpl?, @@ -74,6 +75,12 @@ data class FirewallConfigurationImpl( require(inboundConfig != null && floatOuterConfig != null) { "Missing required configuration" } } } + + private val p2pKeystorePath = sslKeystore + private val p2pKeyStore = FileBasedCertificateStoreSupplier(p2pKeystorePath, keyStorePassword) + private val p2pTrustStoreFilePath = trustStoreFile + private val p2pTrustStore = FileBasedCertificateStoreSupplier(p2pTrustStoreFilePath, trustStorePassword) + override val p2pSslOptions: MutualSslConfiguration = SslConfiguration.mutual(p2pKeyStore, p2pTrustStore) } diff --git a/bridge/src/main/kotlin/net/corda/bridge/services/receiver/BridgeAMQPListenerServiceImpl.kt b/bridge/src/main/kotlin/net/corda/bridge/services/receiver/BridgeAMQPListenerServiceImpl.kt index 8371b5d112..6b14b98cc3 100644 --- a/bridge/src/main/kotlin/net/corda/bridge/services/receiver/BridgeAMQPListenerServiceImpl.kt +++ b/bridge/src/main/kotlin/net/corda/bridge/services/receiver/BridgeAMQPListenerServiceImpl.kt @@ -7,7 +7,9 @@ import net.corda.bridge.services.api.ServiceStateSupport import net.corda.bridge.services.util.ServiceStateCombiner import net.corda.bridge.services.util.ServiceStateHelper import net.corda.core.utilities.contextLogger +import net.corda.nodeapi.internal.config.CertificateStore import net.corda.nodeapi.internal.crypto.KEYSTORE_TYPE +import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.protonwrapper.messages.ReceivedMessage import net.corda.nodeapi.internal.protonwrapper.netty.AMQPConfiguration import net.corda.nodeapi.internal.protonwrapper.netty.AMQPServer @@ -48,13 +50,13 @@ class BridgeAMQPListenerServiceImpl(val conf: FirewallConfiguration, trustStorePassword: CharArray) { require(active) { "AuditService must be active" } require(keyStorePassword !== keyStorePrivateKeyPassword) { "keyStorePassword and keyStorePrivateKeyPassword must reference distinct arrays!" } - val keyStore = loadKeyStoreAndWipeKeys(keyStoreBytes, keyStorePassword) - val trustStore = loadKeyStoreAndWipeKeys(trustStoreBytes, trustStorePassword) + + val keyStore = CertificateStore.of(loadKeyStore(keyStoreBytes, keyStorePassword), java.lang.String.valueOf(keyStorePrivateKeyPassword)).also { wipeKeys(keyStoreBytes, keyStorePassword) } + val trustStore = CertificateStore.of(loadKeyStore(trustStoreBytes, trustStorePassword), java.lang.String.valueOf(trustStorePassword)).also { wipeKeys(trustStoreBytes, trustStorePassword) } val bindAddress = conf.inboundConfig!!.listeningAddress val amqpConfiguration = object : AMQPConfiguration { - override val keyStore: KeyStore = keyStore - override val keyStorePrivateKeyPassword: CharArray = keyStorePrivateKeyPassword - override val trustStore: KeyStore = trustStore + override val keyStore = keyStore + override val trustStore = trustStore override val crlCheckSoftFail: Boolean = conf.crlCheckSoftFail override val maxMessageSize: Int = maximumMessageSize override val trace: Boolean = conf.enableAMQPPacketTrace @@ -80,15 +82,18 @@ class BridgeAMQPListenerServiceImpl(val conf: FirewallConfiguration, consoleLogger.info(msg) } - private fun loadKeyStoreAndWipeKeys(keyStoreBytes: ByteArray, keyStorePassword: CharArray): KeyStore { + private fun wipeKeys(keyStoreBytes: ByteArray, keyStorePassword: CharArray) { + // We overwrite the keys we don't need anymore + Arrays.fill(keyStoreBytes, 0xAA.toByte()) + Arrays.fill(keyStorePassword, 0xAA55.toChar()) + } + + private fun loadKeyStore(keyStoreBytes: ByteArray, keyStorePassword: CharArray): X509KeyStore { val keyStore = KeyStore.getInstance(KEYSTORE_TYPE) ByteArrayInputStream(keyStoreBytes).use { keyStore.load(it, keyStorePassword) } - // We overwrite the keys we don't need anymore - Arrays.fill(keyStoreBytes, 0xAA.toByte()) - Arrays.fill(keyStorePassword, 0xAA55.toChar()) - return keyStore + return X509KeyStore(keyStore, java.lang.String.valueOf(keyStorePassword)) } override fun wipeKeysAndDeactivate() { diff --git a/bridge/src/main/kotlin/net/corda/bridge/services/receiver/FloatControlListenerService.kt b/bridge/src/main/kotlin/net/corda/bridge/services/receiver/FloatControlListenerService.kt index b7fe56ee52..6871ca6806 100644 --- a/bridge/src/main/kotlin/net/corda/bridge/services/receiver/FloatControlListenerService.kt +++ b/bridge/src/main/kotlin/net/corda/bridge/services/receiver/FloatControlListenerService.kt @@ -1,7 +1,6 @@ package net.corda.bridge.services.receiver import net.corda.bridge.services.api.* -import net.corda.bridge.services.config.BridgeSSLConfigurationImpl import net.corda.bridge.services.receiver.FloatControlTopics.FLOAT_DATA_TOPIC import net.corda.bridge.services.util.ServiceStateCombiner import net.corda.bridge.services.util.ServiceStateHelper @@ -12,13 +11,13 @@ import net.corda.core.serialization.serialize import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX +import net.corda.nodeapi.internal.config.MutualSslConfiguration import net.corda.nodeapi.internal.protonwrapper.messages.MessageStatus import net.corda.nodeapi.internal.protonwrapper.messages.ReceivedMessage import net.corda.nodeapi.internal.protonwrapper.netty.AMQPConfiguration import net.corda.nodeapi.internal.protonwrapper.netty.AMQPServer import net.corda.nodeapi.internal.protonwrapper.netty.ConnectionChange import rx.Subscription -import java.security.KeyStore import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock @@ -39,7 +38,7 @@ class FloatControlListenerService(val conf: FirewallConfiguration, private var connectSubscriber: Subscription? = null private var receiveSubscriber: Subscription? = null private var amqpControlServer: AMQPServer? = null - private val sslConfiguration: BridgeSSLConfiguration + private val sslConfiguration: MutualSslConfiguration private val floatControlAddress = conf.floatOuterConfig!!.floatAddress private val floatClientName = conf.floatOuterConfig!!.expectedCertificateSubject private var activeConnectionInfo: ConnectionChange? = null @@ -48,7 +47,7 @@ class FloatControlListenerService(val conf: FirewallConfiguration, init { statusFollower = ServiceStateCombiner(listOf(auditService, amqpListener)) - sslConfiguration = conf.floatOuterConfig?.customSSLConfiguration ?: BridgeSSLConfigurationImpl(conf) + sslConfiguration = conf.floatOuterConfig?.customSSLConfiguration ?: conf.p2pSslOptions } @@ -68,15 +67,13 @@ class FloatControlListenerService(val conf: FirewallConfiguration, private fun startControlListener() { lock.withLock { - val keyStore = sslConfiguration.loadSslKeyStore().internal - val keyStorePrivateKeyPassword = sslConfiguration.keyStorePassword - val trustStore = sslConfiguration.loadTrustStore().internal + val keyStore = sslConfiguration.keyStore.get() + val trustStore = sslConfiguration.trustStore.get() val amqpConfig = object : AMQPConfiguration { override val userName: String? = null override val password: String? = null - override val keyStore: KeyStore = keyStore - override val keyStorePrivateKeyPassword: CharArray = keyStorePrivateKeyPassword.toCharArray() - override val trustStore: KeyStore = trustStore + override val keyStore = keyStore + override val trustStore = trustStore override val crlCheckSoftFail: Boolean = conf.crlCheckSoftFail override val maxMessageSize: Int = maximumMessageSize override val trace: Boolean = conf.enableAMQPPacketTrace diff --git a/bridge/src/main/kotlin/net/corda/bridge/services/receiver/InProcessBridgeReceiverService.kt b/bridge/src/main/kotlin/net/corda/bridge/services/receiver/InProcessBridgeReceiverService.kt index bd751819ff..540cf2c320 100644 --- a/bridge/src/main/kotlin/net/corda/bridge/services/receiver/InProcessBridgeReceiverService.kt +++ b/bridge/src/main/kotlin/net/corda/bridge/services/receiver/InProcessBridgeReceiverService.kt @@ -5,7 +5,7 @@ import net.corda.bridge.services.util.ServiceStateCombiner import net.corda.bridge.services.util.ServiceStateHelper import net.corda.core.internal.readAll import net.corda.core.utilities.contextLogger -import net.corda.nodeapi.internal.config.SSLConfiguration +import net.corda.nodeapi.internal.config.MutualSslConfiguration import net.corda.nodeapi.internal.protonwrapper.messages.ReceivedMessage import rx.Subscription @@ -22,23 +22,23 @@ class InProcessBridgeReceiverService(val conf: FirewallConfiguration, private val statusFollower: ServiceStateCombiner private var statusSubscriber: Subscription? = null private var receiveSubscriber: Subscription? = null - private val sslConfiguration: SSLConfiguration + private val sslConfiguration: MutualSslConfiguration init { statusFollower = ServiceStateCombiner(listOf(auditService, haService, amqpListenerService, filterService)) - sslConfiguration = conf.inboundConfig?.customSSLConfiguration ?: conf + sslConfiguration = conf.inboundConfig?.customSSLConfiguration ?: conf.p2pSslOptions } override fun start() { statusSubscriber = statusFollower.activeChange.subscribe({ if (it) { - val keyStoreBytes = sslConfiguration.sslKeystore.readAll() - val trustStoreBytes = sslConfiguration.trustStoreFile.readAll() + val keyStoreBytes = sslConfiguration.keyStore.path.readAll() + val trustStoreBytes = sslConfiguration.trustStore.path.readAll() amqpListenerService.provisionKeysAndActivate(keyStoreBytes, - sslConfiguration.keyStorePassword.toCharArray(), - sslConfiguration.keyStorePassword.toCharArray(), + sslConfiguration.keyStore.password.toCharArray(), + sslConfiguration.keyStore.password.toCharArray(), trustStoreBytes, - sslConfiguration.trustStorePassword.toCharArray()) + sslConfiguration.trustStore.password.toCharArray()) } else { if (amqpListenerService.running) { amqpListenerService.wipeKeysAndDeactivate() diff --git a/bridge/src/main/kotlin/net/corda/bridge/services/receiver/TunnelingBridgeReceiverService.kt b/bridge/src/main/kotlin/net/corda/bridge/services/receiver/TunnelingBridgeReceiverService.kt index 5555455e57..887946d7a7 100644 --- a/bridge/src/main/kotlin/net/corda/bridge/services/receiver/TunnelingBridgeReceiverService.kt +++ b/bridge/src/main/kotlin/net/corda/bridge/services/receiver/TunnelingBridgeReceiverService.kt @@ -13,7 +13,7 @@ import net.corda.core.serialization.serialize import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger import net.corda.core.utilities.debug -import net.corda.nodeapi.internal.config.SSLConfiguration +import net.corda.nodeapi.internal.config.MutualSslConfiguration import net.corda.nodeapi.internal.protonwrapper.messages.MessageStatus import net.corda.nodeapi.internal.protonwrapper.messages.ReceivedMessage import net.corda.nodeapi.internal.protonwrapper.netty.AMQPClient @@ -21,7 +21,6 @@ import net.corda.nodeapi.internal.protonwrapper.netty.AMQPConfiguration import net.corda.nodeapi.internal.protonwrapper.netty.ConnectionChange import rx.Subscription import java.io.ByteArrayOutputStream -import java.security.KeyStore import java.security.SecureRandom import java.util.concurrent.TimeUnit import java.util.concurrent.TimeoutException @@ -41,15 +40,15 @@ class TunnelingBridgeReceiverService(val conf: FirewallConfiguration, private var connectSubscriber: Subscription? = null private var receiveSubscriber: Subscription? = null private var amqpControlClient: AMQPClient? = null - private val controlLinkSSLConfiguration: SSLConfiguration - private val floatListenerSSLConfiguration: SSLConfiguration + private val controlLinkSSLConfiguration: MutualSslConfiguration + private val floatListenerSSLConfiguration: MutualSslConfiguration private val expectedCertificateSubject: CordaX500Name private val secureRandom: SecureRandom = newSecureRandom() init { statusFollower = ServiceStateCombiner(listOf(auditService, haService, filterService)) - controlLinkSSLConfiguration = conf.bridgeInnerConfig?.customSSLConfiguration ?: conf - floatListenerSSLConfiguration = conf.bridgeInnerConfig?.customFloatOuterSSLConfiguration ?: conf + controlLinkSSLConfiguration = conf.bridgeInnerConfig?.customSSLConfiguration ?: conf.p2pSslOptions + floatListenerSSLConfiguration = conf.bridgeInnerConfig?.customFloatOuterSSLConfiguration ?: conf.p2pSslOptions expectedCertificateSubject = conf.bridgeInnerConfig!!.expectedCertificateSubject } @@ -58,15 +57,13 @@ class TunnelingBridgeReceiverService(val conf: FirewallConfiguration, statusSubscriber = statusFollower.activeChange.subscribe({ if (it) { val floatAddresses = conf.bridgeInnerConfig!!.floatAddresses - val controlLinkKeyStore = controlLinkSSLConfiguration.loadSslKeyStore().internal - val controLinkKeyStorePrivateKeyPassword = controlLinkSSLConfiguration.keyStorePassword - val controlLinkTrustStore = controlLinkSSLConfiguration.loadTrustStore().internal + val controlLinkKeyStore = controlLinkSSLConfiguration.keyStore.get() + val controlLinkTrustStore = controlLinkSSLConfiguration.trustStore.get() val amqpConfig = object : AMQPConfiguration { override val userName: String? = null override val password: String? = null - override val keyStore: KeyStore = controlLinkKeyStore - override val keyStorePrivateKeyPassword: CharArray = controLinkKeyStorePrivateKeyPassword.toCharArray() - override val trustStore: KeyStore = controlLinkTrustStore + override val keyStore = controlLinkKeyStore + override val trustStore = controlLinkTrustStore override val crlCheckSoftFail: Boolean = conf.crlCheckSoftFail override val maxMessageSize: Int = maximumMessageSize override val trace: Boolean = conf.enableAMQPPacketTrace @@ -124,12 +121,12 @@ class TunnelingBridgeReceiverService(val conf: FirewallConfiguration, auditService.statusChangeEvent("Connection change on float control port $connectionChange") if (connectionChange.connected) { val (freshKeyStorePassword, freshKeyStoreKeyPassword, recodedKeyStore) = recodeKeyStore(floatListenerSSLConfiguration) - val trustStoreBytes = floatListenerSSLConfiguration.trustStoreFile.readAll() + val trustStoreBytes = floatListenerSSLConfiguration.trustStore.path.readAll() val activateMessage = ActivateFloat(recodedKeyStore, freshKeyStorePassword, freshKeyStoreKeyPassword, trustStoreBytes, - floatListenerSSLConfiguration.trustStorePassword.toCharArray()) + floatListenerSSLConfiguration.trustStore.password.toCharArray()) val amqpActivateMessage = amqpControlClient!!.createMessage(activateMessage.serialize(context = SerializationDefaults.P2P_CONTEXT).bytes, FLOAT_CONTROL_TOPIC, expectedCertificateSubject.toString(), @@ -150,9 +147,9 @@ class TunnelingBridgeReceiverService(val conf: FirewallConfiguration, } // Recode KeyStore to use a fresh random password for entries and overall - private fun recodeKeyStore(sslConfiguration: SSLConfiguration): Triple { - val keyStoreOriginal = sslConfiguration.loadSslKeyStore().internal - val originalKeyStorePassword = sslConfiguration.keyStorePassword.toCharArray() + private fun recodeKeyStore(sslConfiguration: MutualSslConfiguration): Triple { + val keyStoreOriginal = sslConfiguration.keyStore.get().value.internal + val originalKeyStorePassword = sslConfiguration.keyStore.password.toCharArray() val freshKeyStorePassword = CharArray(20) { secureRandom.nextInt(0xD800).toChar() } // Stick to single character Unicode range val freshPrivateKeyPassword = CharArray(20) { secureRandom.nextInt(0xD800).toChar() } // Stick to single character Unicode range for (alias in keyStoreOriginal.aliases()) { diff --git a/bridge/src/main/kotlin/net/corda/bridge/services/sender/DirectBridgeSenderService.kt b/bridge/src/main/kotlin/net/corda/bridge/services/sender/DirectBridgeSenderService.kt index 9948394432..e27f788a0c 100644 --- a/bridge/src/main/kotlin/net/corda/bridge/services/sender/DirectBridgeSenderService.kt +++ b/bridge/src/main/kotlin/net/corda/bridge/services/sender/DirectBridgeSenderService.kt @@ -23,7 +23,7 @@ class DirectBridgeSenderService(val conf: FirewallConfiguration, private val statusFollower: ServiceStateCombiner private var statusSubscriber: Subscription? = null private var listenerActiveSubscriber: Subscription? = null - private var bridgeControlListener: BridgeControlListener = BridgeControlListener(conf, conf.outboundConfig!!.socksProxyConfig, maxMessageSize, { ForwardingArtemisMessageClient(artemisConnectionService) }) + private var bridgeControlListener: BridgeControlListener = BridgeControlListener(conf.p2pSslOptions, conf.outboundConfig!!.socksProxyConfig, maxMessageSize, { ForwardingArtemisMessageClient(artemisConnectionService) }) init { statusFollower = ServiceStateCombiner(listOf(auditService, artemisConnectionService, haService)) diff --git a/bridge/src/test/kotlin/net/corda/bridge/BridgeTestHelper.kt b/bridge/src/test/kotlin/net/corda/bridge/BridgeTestHelper.kt index 467a533423..a9a9bde0c0 100644 --- a/bridge/src/test/kotlin/net/corda/bridge/BridgeTestHelper.kt +++ b/bridge/src/test/kotlin/net/corda/bridge/BridgeTestHelper.kt @@ -7,9 +7,8 @@ import net.corda.core.internal.createDirectories import net.corda.core.internal.exists import net.corda.core.node.NetworkParameters import net.corda.core.node.NotaryInfo -import net.corda.core.node.services.AttachmentId import net.corda.nodeapi.internal.* -import net.corda.nodeapi.internal.config.SSLConfiguration +import net.corda.nodeapi.internal.config.MutualSslConfiguration import net.corda.nodeapi.internal.crypto.* import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.testing.core.DUMMY_NOTARY_NAME @@ -33,7 +32,7 @@ fun createNetworkParams(baseDirectory: Path): Int { maxMessageSize = 10485760, maxTransactionSize = 40000, epoch = 1, - whitelistedContractImplementations = emptyMap>() + whitelistedContractImplementations = emptyMap() ) val copier = NetworkParametersCopier(networkParameters, overwriteFile = true) copier.install(baseDirectory) @@ -55,18 +54,22 @@ fun createAndLoadConfigFromResource(baseDirectory: Path, configResource: String) return config } -fun SSLConfiguration.createBridgeKeyStores(legalName: CordaX500Name, - rootCert: X509Certificate = DEV_ROOT_CA.certificate, - intermediateCa: CertificateAndKeyPair = DEV_INTERMEDIATE_CA) { +fun FirewallConfiguration.createBridgeKeyStores(legalName: CordaX500Name, + rootCert: X509Certificate = DEV_ROOT_CA.certificate, + intermediateCa: CertificateAndKeyPair = DEV_INTERMEDIATE_CA) = p2pSslOptions.createBridgeKeyStores(legalName, rootCert, intermediateCa) - certificatesDirectory.createDirectories() - if (!trustStoreFile.exists()) { - loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/${DEV_CA_TRUST_STORE_FILE}"), DEV_CA_TRUST_STORE_PASS).save(trustStoreFile, trustStorePassword) +fun MutualSslConfiguration.createBridgeKeyStores(legalName: CordaX500Name, + rootCert: X509Certificate = DEV_ROOT_CA.certificate, + intermediateCa: CertificateAndKeyPair = DEV_INTERMEDIATE_CA) { + + if (!trustStore.path.exists()) { + val trustStore = trustStore.get(true) + loadDevCaTrustStore().copyTo(trustStore) } val (nodeCaCert, nodeCaKeyPair) = createDevNodeCa(intermediateCa, legalName) - val sslKeyStore = loadSslKeyStore(createNew = true) + val sslKeyStore = keyStore.get(createNew = true) sslKeyStore.update { val tlsKeyPair = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, nodeCaCert, nodeCaKeyPair, legalName.x500Principal, tlsKeyPair.public) diff --git a/bridge/src/test/kotlin/net/corda/bridge/ConfigTest.kt b/bridge/src/test/kotlin/net/corda/bridge/ConfigTest.kt index ce660ce9fc..2bdada596c 100644 --- a/bridge/src/test/kotlin/net/corda/bridge/ConfigTest.kt +++ b/bridge/src/test/kotlin/net/corda/bridge/ConfigTest.kt @@ -62,23 +62,23 @@ class ConfigTest { fun `Load overridden cert config`() { val configResource = "/net/corda/bridge/custombasecerts/firewall.conf" val config = createAndLoadConfigFromResource(tempFolder.root.toPath(), configResource) - assertEquals(Paths.get("customcerts/mysslkeystore.jks"), config.sslKeystore) - assertEquals(Paths.get("customcerts/mytruststore.jks"), config.trustStoreFile) + assertEquals(Paths.get("customcerts/mysslkeystore.jks"), config.p2pSslOptions.keyStore.path) + assertEquals(Paths.get("customcerts/mytruststore.jks"), config.p2pSslOptions.trustStore.path) } @Test fun `Load custom inner certificate config`() { val configResource = "/net/corda/bridge/separatedwithcustomcerts/bridge/firewall.conf" val config = createAndLoadConfigFromResource(tempFolder.root.toPath(), configResource) - assertEquals(Paths.get("outboundcerts/outboundkeys.jks"), config.outboundConfig!!.customSSLConfiguration!!.sslKeystore) - assertEquals(Paths.get("outboundcerts/outboundtrust.jks"), config.outboundConfig!!.customSSLConfiguration!!.trustStoreFile) - assertEquals("outboundkeypassword", config.outboundConfig!!.customSSLConfiguration!!.keyStorePassword) - assertEquals("outboundtrustpassword", config.outboundConfig!!.customSSLConfiguration!!.trustStorePassword) + assertEquals(Paths.get("outboundcerts/outboundkeys.jks"), config.outboundConfig!!.customSSLConfiguration!!.keyStore.path) + assertEquals(Paths.get("outboundcerts/outboundtrust.jks"), config.outboundConfig!!.customSSLConfiguration!!.trustStore.path) + assertEquals("outboundkeypassword", config.outboundConfig!!.customSSLConfiguration!!.keyStore.password) + assertEquals("outboundtrustpassword", config.outboundConfig!!.customSSLConfiguration!!.trustStore.password) assertNull(config.inboundConfig) - assertEquals(Paths.get("tunnelcerts/tunnelkeys.jks"), config.bridgeInnerConfig!!.customSSLConfiguration!!.sslKeystore) - assertEquals(Paths.get("tunnelcerts/tunneltrust.jks"), config.bridgeInnerConfig!!.customSSLConfiguration!!.trustStoreFile) - assertEquals("tunnelkeypassword", config.bridgeInnerConfig!!.customSSLConfiguration!!.keyStorePassword) - assertEquals("tunneltrustpassword", config.bridgeInnerConfig!!.customSSLConfiguration!!.trustStorePassword) + assertEquals(Paths.get("tunnelcerts/tunnelkeys.jks"), config.bridgeInnerConfig!!.customSSLConfiguration!!.keyStore.path) + assertEquals(Paths.get("tunnelcerts/tunneltrust.jks"), config.bridgeInnerConfig!!.customSSLConfiguration!!.trustStore.path) + assertEquals("tunnelkeypassword", config.bridgeInnerConfig!!.customSSLConfiguration!!.keyStore.password) + assertEquals("tunneltrustpassword", config.bridgeInnerConfig!!.customSSLConfiguration!!.trustStore.password) assertNull(config.floatOuterConfig) } @@ -86,15 +86,15 @@ class ConfigTest { fun `Load custom outer certificate config`() { val configResource = "/net/corda/bridge/separatedwithcustomcerts/float/firewall.conf" val config = createAndLoadConfigFromResource(tempFolder.root.toPath(), configResource) - assertEquals(Paths.get("inboundcerts/inboundkeys.jks"), config.inboundConfig!!.customSSLConfiguration!!.sslKeystore) - assertEquals(Paths.get("inboundcerts/inboundtrust.jks"), config.inboundConfig!!.customSSLConfiguration!!.trustStoreFile) - assertEquals("inboundkeypassword", config.inboundConfig!!.customSSLConfiguration!!.keyStorePassword) - assertEquals("inboundtrustpassword", config.inboundConfig!!.customSSLConfiguration!!.trustStorePassword) + assertEquals(Paths.get("inboundcerts/inboundkeys.jks"), config.inboundConfig!!.customSSLConfiguration!!.keyStore.path) + assertEquals(Paths.get("inboundcerts/inboundtrust.jks"), config.inboundConfig!!.customSSLConfiguration!!.trustStore.path) + assertEquals("inboundkeypassword", config.inboundConfig!!.customSSLConfiguration!!.keyStore.password) + assertEquals("inboundtrustpassword", config.inboundConfig!!.customSSLConfiguration!!.trustStore.password) assertNull(config.outboundConfig) - assertEquals(Paths.get("tunnelcerts/tunnelkeys.jks"), config.floatOuterConfig!!.customSSLConfiguration!!.sslKeystore) - assertEquals(Paths.get("tunnelcerts/tunneltrust.jks"), config.floatOuterConfig!!.customSSLConfiguration!!.trustStoreFile) - assertEquals("tunnelkeypassword", config.floatOuterConfig!!.customSSLConfiguration!!.keyStorePassword) - assertEquals("tunneltrustpassword", config.floatOuterConfig!!.customSSLConfiguration!!.trustStorePassword) + assertEquals(Paths.get("tunnelcerts/tunnelkeys.jks"), config.floatOuterConfig!!.customSSLConfiguration!!.keyStore.path) + assertEquals(Paths.get("tunnelcerts/tunneltrust.jks"), config.floatOuterConfig!!.customSSLConfiguration!!.trustStore.path) + assertEquals("tunnelkeypassword", config.floatOuterConfig!!.customSSLConfiguration!!.keyStore.password) + assertEquals("tunneltrustpassword", config.floatOuterConfig!!.customSSLConfiguration!!.trustStore.password) assertNull(config.bridgeInnerConfig) } diff --git a/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java b/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java index ed26949527..97fbc522f6 100644 --- a/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java +++ b/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java @@ -61,7 +61,6 @@ public class CordaRPCJavaClientTest extends NodeBasedTest { @Before public void setUp() throws Exception { - super.setUp(); node = startNode(ALICE_NAME, 1000, singletonList(rpcUser)); client = new CordaRPCClient(requireNonNull(node.getNode().getConfiguration().getRpcOptions().getAddress())); } diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt index f1ec9fcad9..9a90b28476 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt @@ -291,7 +291,7 @@ class CordaRPCClient private constructor( sslConfiguration: ClientRpcSslOptions? = null, classLoader: ClassLoader? = null ): CordaRPCClient { - return CordaRPCClient(haAddressPool.first(), configuration, sslConfiguration, null, classLoader, haAddressPool) + return CordaRPCClient(haAddressPool.first(), configuration, sslConfiguration, classLoader, haAddressPool) } } @@ -309,7 +309,7 @@ class CordaRPCClient private constructor( private fun getRpcClient(): RPCClient { return when { - // Client->RPC broker + // Client->RPC broker haAddressPool.isEmpty() -> RPCClient( rpcConnectorTcpTransport(hostAndPort, config = sslConfiguration), configuration, diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/CordaRPCClientUtils.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/CordaRPCClientUtils.kt index 9b07c405a9..2f60b2514a 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/CordaRPCClientUtils.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/CordaRPCClientUtils.kt @@ -16,6 +16,13 @@ fun createCordaRPCClientWithSslAndClassLoader( classLoader: ClassLoader? = null ) = CordaRPCClient.createWithSslAndClassLoader(hostAndPort, configuration, sslConfiguration, classLoader) +fun createCordaRPCClientWithSslAndClassLoader( + haAddressPool: List, + configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT, + sslConfiguration: ClientRpcSslOptions? = null, + classLoader: ClassLoader? = null +) = CordaRPCClient.createWithSslAndClassLoader(haAddressPool, configuration, sslConfiguration, classLoader) + fun CordaRPCOps.drainAndShutdown(): Observable { setFlowsDrainingModeEnabled(true) diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index 078cfcaa11..6d11610d5f 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -5,6 +5,9 @@ package net.corda.core.internal import net.corda.core.DeleteForDJVM import net.corda.core.KeepForDJVM import net.corda.core.crypto.* +import net.corda.core.flows.FlowLogic +import net.corda.core.node.ServicesForResolution +import net.corda.core.schemas.MappedSchema import net.corda.core.serialization.* import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.UntrustworthyData diff --git a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt index 6213c7794a..29d261cb92 100644 --- a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt +++ b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt @@ -96,6 +96,12 @@ data class StateMachineTransactionMapping(val stateMachineRunId: StateMachineRun /** RPC operations that the node exposes to clients. */ interface CordaRPCOps : RPCOps { + /** + * Returns the RPC protocol version, which is the same the node's Platform Version. Exists since version 1 so guaranteed + * to be present. + */ + override val protocolVersion: Int get() = nodeInfo().platformVersion + /** Returns a list of currently in-progress state machine infos. */ fun stateMachinesSnapshot(): List diff --git a/experimental/flow-worker/src/integration-test/kotlin/net/corda/flowworker/FlowWorkerTest.kt b/experimental/flow-worker/src/integration-test/kotlin/net/corda/flowworker/FlowWorkerTest.kt index b0d5513ef8..8ac9d49e09 100644 --- a/experimental/flow-worker/src/integration-test/kotlin/net/corda/flowworker/FlowWorkerTest.kt +++ b/experimental/flow-worker/src/integration-test/kotlin/net/corda/flowworker/FlowWorkerTest.kt @@ -91,8 +91,8 @@ class FlowWorkerTest { // create test certificates config.configureWithDevSSLCertificate() - val trustRoot = config.loadTrustStore().getCertificate(X509Utilities.CORDA_ROOT_CA) - val nodeCa = config.loadNodeKeyStore().getCertificate(X509Utilities.CORDA_CLIENT_CA) + val trustRoot = config.p2pSslOptions.trustStore.get().query { getCertificate(X509Utilities.CORDA_ROOT_CA) } + val nodeCa = config.signingCertificateStore.get().query { getCertificate(X509Utilities.CORDA_CLIENT_CA) } val broker = createFlowWorkerBroker(config, networkParameters.maxMessageSize) val bridgeControlListener = createBridgeControlListener(config, networkParameters.maxMessageSize) @@ -146,8 +146,8 @@ class FlowWorkerTest { // create test certificates bankAConfig.configureWithDevSSLCertificate() - val bankATrustRoot = bankAConfig.loadTrustStore().getCertificate(X509Utilities.CORDA_ROOT_CA) - val bankANodeCa = bankAConfig.loadNodeKeyStore().getCertificate(X509Utilities.CORDA_CLIENT_CA) + val bankATrustRoot = bankAConfig.p2pSslOptions.trustStore.get().query { getCertificate(X509Utilities.CORDA_ROOT_CA) } + val bankANodeCa = bankAConfig.signingCertificateStore.get().query { getCertificate(X509Utilities.CORDA_CLIENT_CA) } val bankABroker = createFlowWorkerBroker(bankAConfig, networkParameters.maxMessageSize) val bankABridgeControlListener = createBridgeControlListener(bankAConfig, networkParameters.maxMessageSize) @@ -166,8 +166,8 @@ class FlowWorkerTest { // create test certificates bankBConfig.configureWithDevSSLCertificate() - val bankBTrustRoot = bankBConfig.loadTrustStore().getCertificate(X509Utilities.CORDA_ROOT_CA) - val bankBNodeCa = bankBConfig.loadNodeKeyStore().getCertificate(X509Utilities.CORDA_CLIENT_CA) + val bankBTrustRoot = bankBConfig.p2pSslOptions.trustStore.get().query { getCertificate(X509Utilities.CORDA_ROOT_CA) } + val bankBNodeCa = bankBConfig.signingCertificateStore.get().query { getCertificate(X509Utilities.CORDA_CLIENT_CA) } // NetworkParametersCopier(networkParameters).install(bankBConfig.baseDirectory) val bankBBroker = createFlowWorkerBroker(bankBConfig, networkParameters.maxMessageSize) @@ -234,13 +234,13 @@ class FlowWorkerTest { } private fun createBridgeControlListener(config: NodeConfiguration, maxMessageSize: Int): BridgeControlListener { - val bridgeControlListener = BridgeControlListener(config, config.messagingServerAddress!!, maxMessageSize) + val bridgeControlListener = BridgeControlListener(config.p2pSslOptions, config.messagingServerAddress!!, maxMessageSize) bridgeControlListener.start() return bridgeControlListener } private fun createArtemisClient(config: NodeConfiguration, queueAddress: String): Triple { - val artemisClient = ArtemisMessagingClient(config, config.messagingServerAddress!!, MAX_MESSAGE_SIZE) + val artemisClient = ArtemisMessagingClient(config.p2pSslOptions, config.messagingServerAddress!!, MAX_MESSAGE_SIZE) val started = artemisClient.start() started.session.createQueue(queueAddress, RoutingType.ANYCAST, queueAddress, true) return Triple(started.session, started.session.createConsumer(queueAddress), started.session.createProducer()) diff --git a/experimental/flow-worker/src/main/kotlin/net/corda/flowworker/FlowWorker.kt b/experimental/flow-worker/src/main/kotlin/net/corda/flowworker/FlowWorker.kt index c13de9727c..4454d99e09 100644 --- a/experimental/flow-worker/src/main/kotlin/net/corda/flowworker/FlowWorker.kt +++ b/experimental/flow-worker/src/main/kotlin/net/corda/flowworker/FlowWorker.kt @@ -43,7 +43,7 @@ class FlowWorker(flowWorkerId: String, private val flowWorkerServiceHub: FlowWor flowWorkerServiceHub.start() runOnStop += { flowWorkerServiceHub.stop() } - val flowWorkerMessagingClient = ArtemisMessagingClient(flowWorkerServiceHub.configuration, flowWorkerServiceHub.configuration.messagingServerAddress!!, flowWorkerServiceHub.networkParameters.maxMessageSize) + val flowWorkerMessagingClient = ArtemisMessagingClient(flowWorkerServiceHub.configuration.p2pSslOptions, flowWorkerServiceHub.configuration.messagingServerAddress!!, flowWorkerServiceHub.networkParameters.maxMessageSize) runOnStop += { flowWorkerMessagingClient.stop() } val session = flowWorkerMessagingClient.start().session diff --git a/experimental/rpc-worker/src/integration-test/kotlin/net/corda/rpcWorker/RpcFlowWorkerDriver.kt b/experimental/rpc-worker/src/integration-test/kotlin/net/corda/rpcWorker/RpcFlowWorkerDriver.kt index 8b0a913352..4e2b152b43 100644 --- a/experimental/rpc-worker/src/integration-test/kotlin/net/corda/rpcWorker/RpcFlowWorkerDriver.kt +++ b/experimental/rpc-worker/src/integration-test/kotlin/net/corda/rpcWorker/RpcFlowWorkerDriver.kt @@ -59,8 +59,8 @@ data class RpcFlowWorkerDriverDSL(private val driverDSL: DriverDSLImpl) : Intern fun startRpcFlowWorker(myLegalName: CordaX500Name, rpcUsers: List, numberOfFlowWorkers: Int = 1): CordaFuture { val (config, rpcWorkerConfig, flowWorkerConfigs) = generateConfigs(myLegalName, rpcUsers, numberOfFlowWorkers) - val trustRoot = rpcWorkerConfig.loadTrustStore().getCertificate(X509Utilities.CORDA_ROOT_CA) - val nodeCa = rpcWorkerConfig.loadNodeKeyStore().getCertificate(X509Utilities.CORDA_CLIENT_CA) + val trustRoot = rpcWorkerConfig.p2pSslOptions.trustStore.get().query { getCertificate(X509Utilities.CORDA_ROOT_CA) } + val nodeCa = rpcWorkerConfig.signingCertificateStore.get().query { getCertificate(X509Utilities.CORDA_CLIENT_CA) } val ourKeyPair = Crypto.generateKeyPair() val ourParty = Party(myLegalName, ourKeyPair.public) @@ -151,9 +151,9 @@ private fun createRpcWorkerBroker(config: NodeConfiguration, maxMessageSize: Int val rpcOptions = config.rpcOptions val securityManager = RPCSecurityManagerImpl(SecurityConfiguration.AuthService.fromUsers(config.rpcUsers)) val broker = if (rpcOptions.useSsl) { - ArtemisRpcBroker.withSsl(config, rpcOptions.address, rpcOptions.adminAddress, rpcOptions.sslConfig!!, securityManager, maxMessageSize, false, config.baseDirectory / "artemis", false) + ArtemisRpcBroker.withSsl(config.p2pSslOptions, rpcOptions.address, rpcOptions.adminAddress, rpcOptions.sslConfig!!, securityManager, maxMessageSize, false, config.baseDirectory / "artemis", false) } else { - ArtemisRpcBroker.withoutSsl(config, rpcOptions.address, rpcOptions.adminAddress, securityManager, maxMessageSize, false, config.baseDirectory / "artemis", false) + ArtemisRpcBroker.withoutSsl(config.p2pSslOptions, rpcOptions.address, rpcOptions.adminAddress, securityManager, maxMessageSize, false, config.baseDirectory / "artemis", false) } broker.start() return broker @@ -180,7 +180,7 @@ private fun createFlowWorker(config: NodeConfiguration, myInfo: NodeInfo, networ } private fun createBridgeControlListener(config: NodeConfiguration, maxMessageSize: Int): BridgeControlListener { - val bridgeControlListener = BridgeControlListener(config, config.messagingServerAddress!!, maxMessageSize) + val bridgeControlListener = BridgeControlListener(config.p2pSslOptions, config.messagingServerAddress!!, maxMessageSize) bridgeControlListener.start() return bridgeControlListener } \ No newline at end of file diff --git a/experimental/rpc-worker/src/main/kotlin/net/corda/rpcWorker/CordaRpcWorkerOps.kt b/experimental/rpc-worker/src/main/kotlin/net/corda/rpcWorker/CordaRpcWorkerOps.kt index 074a933db5..21628ed90f 100644 --- a/experimental/rpc-worker/src/main/kotlin/net/corda/rpcWorker/CordaRpcWorkerOps.kt +++ b/experimental/rpc-worker/src/main/kotlin/net/corda/rpcWorker/CordaRpcWorkerOps.kt @@ -56,14 +56,13 @@ class CordaRpcWorkerOps( const val RPC_WORKER_QUEUE_ADDRESS_PREFIX = "${ArtemisMessagingComponent.INTERNAL_PREFIX}rpc.worker." } - override val protocolVersion: Int = 1000 private val flowWorkerQueueAddress = "${FlowWorker.FLOW_WORKER_QUEUE_ADDRESS_PREFIX}${services.myInfo.legalIdentities[0].owningKey.toStringShort()}" private val rpcWorkerQueueAddress = "$RPC_WORKER_QUEUE_ADDRESS_PREFIX${services.myInfo.legalIdentities[0].owningKey.toStringShort()}" private val rpcWorkerId = UUID.randomUUID().toString() private val rpcWorkerQueueName = "$rpcWorkerQueueAddress.$rpcWorkerId" - private val artemisClient = ArtemisMessagingClient(services.configuration, services.configuration.messagingServerAddress!!, services.networkParameters.maxMessageSize) + private val artemisClient = ArtemisMessagingClient(services.configuration.p2pSslOptions, services.configuration.messagingServerAddress!!, services.networkParameters.maxMessageSize) private lateinit var session: ClientSession private lateinit var producer: ClientProducer diff --git a/experimental/rpc-worker/src/main/kotlin/net/corda/rpcWorker/RpcWorker.kt b/experimental/rpc-worker/src/main/kotlin/net/corda/rpcWorker/RpcWorker.kt index bdd71d0acc..ff2b8b1430 100644 --- a/experimental/rpc-worker/src/main/kotlin/net/corda/rpcWorker/RpcWorker.kt +++ b/experimental/rpc-worker/src/main/kotlin/net/corda/rpcWorker/RpcWorker.kt @@ -77,8 +77,8 @@ class Main : Runnable { val ourKeyPair = getIdentity() val myInfo = getNodeInfo() - val trustRoot = rpcWorkerConfig.loadTrustStore().getCertificate(X509Utilities.CORDA_ROOT_CA) - val nodeCa = rpcWorkerConfig.loadNodeKeyStore().getCertificate(X509Utilities.CORDA_CLIENT_CA) + val trustRoot = rpcWorkerConfig.p2pSslOptions.trustStore.get().query { getCertificate(X509Utilities.CORDA_ROOT_CA) } + val nodeCa = rpcWorkerConfig.signingCertificateStore.get().query { getCertificate(X509Utilities.CORDA_CLIENT_CA) } val signedNetworkParameters = NetworkParametersReader(trustRoot, null, rpcWorkerConfig.baseDirectory).read() val rpcWorkerBroker = createRpcWorkerBroker(rpcWorkerConfig, signedNetworkParameters.networkParameters.maxMessageSize) @@ -101,9 +101,9 @@ class Main : Runnable { val rpcOptions = config.rpcOptions val securityManager = RPCSecurityManagerImpl(SecurityConfiguration.AuthService.fromUsers(config.rpcUsers)) val broker = if (rpcOptions.useSsl) { - ArtemisRpcBroker.withSsl(config, rpcOptions.address, rpcOptions.adminAddress, rpcOptions.sslConfig!!, securityManager, maxMessageSize, false, config.baseDirectory / "artemis", false) + ArtemisRpcBroker.withSsl(config.p2pSslOptions, rpcOptions.address, rpcOptions.adminAddress, rpcOptions.sslConfig!!, securityManager, maxMessageSize, false, config.baseDirectory / "artemis", false) } else { - ArtemisRpcBroker.withoutSsl(config, rpcOptions.address, rpcOptions.adminAddress, securityManager, maxMessageSize, false, config.baseDirectory / "artemis", false) + ArtemisRpcBroker.withoutSsl(config.p2pSslOptions, rpcOptions.address, rpcOptions.adminAddress, securityManager, maxMessageSize, false, config.baseDirectory / "artemis", false) } broker.start() return broker @@ -126,9 +126,9 @@ class RpcWorker(private val rpcWorkerServiceHub: RpcWorkerServiceHub, private va rpcThreadPoolSize = rpcWorkerServiceHub.configuration.enterpriseConfiguration.tuning.rpcThreadPoolSize ) val securityManager = RPCSecurityManagerImpl(SecurityConfiguration.AuthService.fromUsers(rpcWorkerServiceHub.configuration.rpcUsers)) - val nodeName = CordaX500Name.build(rpcWorkerServiceHub.configuration.loadSslKeyStore().getCertificate(X509Utilities.CORDA_CLIENT_TLS).subjectX500Principal) + val nodeName = CordaX500Name.build(rpcWorkerServiceHub.configuration.p2pSslOptions.keyStore.get().query { getCertificate(X509Utilities.CORDA_CLIENT_TLS).subjectX500Principal }) - val internalRpcMessagingClient = InternalRPCMessagingClient(rpcWorkerServiceHub.configuration, rpcWorkerServiceHub.configuration.rpcOptions.adminAddress, Node.MAX_RPC_MESSAGE_SIZE, nodeName, rpcServerConfiguration) + val internalRpcMessagingClient = InternalRPCMessagingClient(rpcWorkerServiceHub.configuration.p2pSslOptions, rpcWorkerServiceHub.configuration.rpcOptions.adminAddress, Node.MAX_RPC_MESSAGE_SIZE, nodeName, rpcServerConfiguration) internalRpcMessagingClient.init(rpcWorkerServiceHub.rpcOps, securityManager) internalRpcMessagingClient.start(serverControl) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/ArtemisMessagingClient.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/ArtemisMessagingClient.kt index 055c96ffe6..ae1a9defca 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/ArtemisMessagingClient.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/ArtemisMessagingClient.kt @@ -6,10 +6,8 @@ import net.corda.core.utilities.loggerFor import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_P2P_USER import net.corda.nodeapi.internal.config.MutualSslConfiguration import org.apache.activemq.artemis.api.core.client.ActiveMQClient +import org.apache.activemq.artemis.api.core.client.* import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE -import org.apache.activemq.artemis.api.core.client.ClientProducer -import org.apache.activemq.artemis.api.core.client.ClientSession -import org.apache.activemq.artemis.api.core.client.ClientSessionFactory interface ArtemisSessionProvider { fun start(): ArtemisMessagingClient.Started @@ -19,12 +17,15 @@ interface ArtemisSessionProvider { class ArtemisMessagingClient(private val config: MutualSslConfiguration, private val serverAddress: NetworkHostAndPort, - private val maxMessageSize: Int) : ArtemisSessionProvider { + private val maxMessageSize: Int, + private val autoCommitSends: Boolean = true, + private val autoCommitAcks: Boolean = true, + private val confirmationWindowSize: Int = -1) : ArtemisSessionProvider { companion object { private val log = loggerFor() } - class Started(val sessionFactory: ClientSessionFactory, val session: ClientSession, val producer: ClientProducer) + class Started(val serverLocator: ServerLocator, val sessionFactory: ClientSessionFactory, val session: ClientSession, val producer: ClientProducer) override var started: Started? = null private set @@ -41,6 +42,7 @@ class ArtemisMessagingClient(private val config: MutualSslConfiguration, clientFailureCheckPeriod = 30000 minLargeMessageSize = maxMessageSize isUseGlobalPools = nodeSerializationEnv != null + confirmationWindowSize = this@ArtemisMessagingClient.confirmationWindowSize addIncomingInterceptor(ArtemisMessageSizeChecksInterceptor(maxMessageSize)) } val sessionFactory = locator.createSessionFactory() @@ -48,11 +50,11 @@ class ArtemisMessagingClient(private val config: MutualSslConfiguration, // using our TLS certificate. // Note that the acknowledgement of messages is not flushed to the Artermis journal until the default buffer // size of 1MB is acknowledged. - val session = sessionFactory!!.createSession(NODE_P2P_USER, NODE_P2P_USER, false, true, true, false, DEFAULT_ACK_BATCH_SIZE) + val session = sessionFactory!!.createSession(NODE_P2P_USER, NODE_P2P_USER, false, autoCommitSends, autoCommitAcks, false, DEFAULT_ACK_BATCH_SIZE) session.start() // Create a general purpose producer. val producer = session.createProducer() - return Started(sessionFactory, session, producer).also { started = it } + return Started(locator, sessionFactory, session, producer).also { started = it } } override fun stop() = synchronized(this) { @@ -62,6 +64,7 @@ class ArtemisMessagingClient(private val config: MutualSslConfiguration, session.commit() // Closing the factory closes all the sessions it produced as well. sessionFactory.close() + serverLocator.close() } started = null } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/AMQPBridgeManager.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/AMQPBridgeManager.kt index 5836920f6d..26093597ab 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/AMQPBridgeManager.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/AMQPBridgeManager.kt @@ -38,22 +38,23 @@ import kotlin.concurrent.withLock * The Netty thread pool used by the AMQPBridges is also shared and managed by the AMQPBridgeManager. */ @VisibleForTesting -class AMQPBridgeManager(config: MutualSslConfiguration, maxMessageSize: Int, private val artemisMessageClientFactory: () -> ArtemisSessionProvider) : BridgeManager { +class AMQPBridgeManager(config: MutualSslConfiguration, socksProxyConfig: SocksProxyConfig? = null, maxMessageSize: Int, private val artemisMessageClientFactory: () -> ArtemisSessionProvider) : BridgeManager { private val lock = ReentrantLock() private val bridgeNameToBridgeMap = mutableMapOf() private class AMQPConfigurationImpl private constructor(override val keyStore: CertificateStore, override val trustStore: CertificateStore, + override val socksProxyConfig: SocksProxyConfig?, override val maxMessageSize: Int) : AMQPConfiguration { - constructor(config: MutualSslConfiguration, maxMessageSize: Int) : this(config.keyStore.get(), config.trustStore.get(), maxMessageSize) + constructor(config: MutualSslConfiguration, socksProxyConfig: SocksProxyConfig?, maxMessageSize: Int) : this(config.keyStore.get(), config.trustStore.get(), socksProxyConfig, maxMessageSize) } private val amqpConfig: AMQPConfiguration = AMQPConfigurationImpl(config, socksProxyConfig, maxMessageSize) private var sharedEventLoopGroup: EventLoopGroup? = null private var artemis: ArtemisSessionProvider? = null - constructor(config: MutualSslConfiguration, p2pAddress: NetworkHostAndPort, maxMessageSize: Int) : this(config, maxMessageSize, { ArtemisMessagingClient(config, p2pAddress, maxMessageSize) }) + constructor(config: MutualSslConfiguration, p2pAddress: NetworkHostAndPort, maxMessageSize: Int, socksProxyConfig: SocksProxyConfig? = null) : this(config, socksProxyConfig, maxMessageSize, { ArtemisMessagingClient(config, p2pAddress, maxMessageSize) }) companion object { private const val NUM_BRIDGE_THREADS = 0 // Default sized pool diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/BridgeControlListener.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/BridgeControlListener.kt index c9d6e23af7..b9037e3f87 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/BridgeControlListener.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/BridgeControlListener.kt @@ -11,30 +11,50 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.BRIDGE_NOT import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREFIX import net.corda.nodeapi.internal.ArtemisSessionProvider +import net.corda.nodeapi.internal.protonwrapper.netty.SocksProxyConfig +import org.apache.activemq.artemis.api.core.ActiveMQQueueExistsException import net.corda.nodeapi.internal.config.MutualSslConfiguration import org.apache.activemq.artemis.api.core.RoutingType import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.client.ClientConsumer import org.apache.activemq.artemis.api.core.client.ClientMessage +import org.apache.activemq.artemis.api.core.client.ClientSession +import rx.Observable +import rx.subjects.PublishSubject import java.util.* class BridgeControlListener(val config: MutualSslConfiguration, + socksProxyConfig: SocksProxyConfig? = null, maxMessageSize: Int, val artemisMessageClientFactory: () -> ArtemisSessionProvider) : AutoCloseable { private val bridgeId: String = UUID.randomUUID().toString() - private val bridgeManager: BridgeManager = AMQPBridgeManager(config, maxMessageSize, artemisMessageClientFactory) + private val bridgeControlQueue = "$BRIDGE_CONTROL.$bridgeId" + private val bridgeNotifyQueue = "$BRIDGE_NOTIFY.$bridgeId" + private val bridgeManager: BridgeManager = AMQPBridgeManager(config, socksProxyConfig, maxMessageSize, + artemisMessageClientFactory) private val validInboundQueues = mutableSetOf() private var artemis: ArtemisSessionProvider? = null private var controlConsumer: ClientConsumer? = null + private var notifyConsumer: ClientConsumer? = null constructor(config: MutualSslConfiguration, p2pAddress: NetworkHostAndPort, - maxMessageSize: Int) : this(config, maxMessageSize, { ArtemisMessagingClient(config, p2pAddress, maxMessageSize) }) + + maxMessageSize: Int, + socksProxy: SocksProxyConfig? = null) : this(config, socksProxy, maxMessageSize, { ArtemisMessagingClient(config, p2pAddress, maxMessageSize) }) + companion object { private val log = contextLogger() } + val active: Boolean + get() = validInboundQueues.isNotEmpty() + + private val _activeChange = PublishSubject.create().toSerialized() + val activeChange: Observable + get() = _activeChange + fun start() { stop() bridgeManager.start() @@ -43,8 +63,21 @@ class BridgeControlListener(val config: MutualSslConfiguration, artemis.start() val artemisClient = artemis.started!! val artemisSession = artemisClient.session - val bridgeControlQueue = "$BRIDGE_CONTROL.$bridgeId" - artemisSession.createTemporaryQueue(BRIDGE_CONTROL, RoutingType.MULTICAST, bridgeControlQueue) + registerBridgeControlListener(artemisSession) + registerBridgeDuplicateChecker(artemisSession) + val startupMessage = BridgeControl.BridgeToNodeSnapshotRequest(bridgeId).serialize(context = SerializationDefaults.P2P_CONTEXT).bytes + val bridgeRequest = artemisSession.createMessage(false) + bridgeRequest.writeBodyBufferBytes(startupMessage) + artemisClient.producer.send(BRIDGE_NOTIFY, bridgeRequest) + } + + private fun registerBridgeControlListener(artemisSession: ClientSession) { + try { + artemisSession.createTemporaryQueue(BRIDGE_CONTROL, RoutingType.MULTICAST, bridgeControlQueue) + } catch (ex: ActiveMQQueueExistsException) { + // Ignore if there is a queue still not cleaned up + } + val control = artemisSession.createConsumer(bridgeControlQueue) controlConsumer = control control.setMessageHandler { msg -> @@ -54,17 +87,44 @@ class BridgeControlListener(val config: MutualSslConfiguration, log.error("Unable to process bridge control message", ex) } } - val startupMessage = BridgeControl.BridgeToNodeSnapshotRequest(bridgeId).serialize(context = SerializationDefaults.P2P_CONTEXT).bytes - val bridgeRequest = artemisSession.createMessage(false) - bridgeRequest.writeBodyBufferBytes(startupMessage) - artemisClient.producer.send(BRIDGE_NOTIFY, bridgeRequest) + } + + private fun registerBridgeDuplicateChecker(artemisSession: ClientSession) { + try { + artemisSession.createTemporaryQueue(BRIDGE_NOTIFY, RoutingType.MULTICAST, bridgeNotifyQueue) + } catch (ex: ActiveMQQueueExistsException) { + // Ignore if there is a queue still not cleaned up + } + val notify = artemisSession.createConsumer(bridgeNotifyQueue) + notifyConsumer = notify + notify.setMessageHandler { msg -> + try { + val data: ByteArray = ByteArray(msg.bodySize).apply { msg.bodyBuffer.readBytes(this) } + val notifyMessage = data.deserialize(context = SerializationDefaults.P2P_CONTEXT) + if (notifyMessage.bridgeIdentity != bridgeId) { + log.error("Fatal Error! Two bridges have been configured simultaneously! Check the enterpriseConfiguration.externalBridge status") + System.exit(1) + } + } catch (ex: Exception) { + log.error("Unable to process bridge notification message", ex) + } + } } fun stop() { + if (active) { + _activeChange.onNext(false) + } validInboundQueues.clear() controlConsumer?.close() controlConsumer = null - artemis?.stop() + notifyConsumer?.close() + notifyConsumer = null + artemis?.apply { + started?.session?.deleteQueue(bridgeControlQueue) + started?.session?.deleteQueue(bridgeNotifyQueue) + stop() + } artemis = null bridgeManager.stop() } @@ -100,7 +160,11 @@ class BridgeControlListener(val config: MutualSslConfiguration, for (outQueue in controlMessage.sendQueues) { bridgeManager.deployBridge(outQueue.queueName, outQueue.targets.first(), outQueue.legalNames.toSet()) } + val wasActive = active validInboundQueues.addAll(controlMessage.inboxQueues) + if (!wasActive && active) { + _activeChange.onNext(true) + } } is BridgeControl.BridgeToNodeSnapshotRequest -> { log.error("Message from Bridge $controlMessage detected on wrong topic!") diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPServer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPServer.kt index 56c8b8bfda..d65b77139c 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPServer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPServer.kt @@ -60,7 +60,7 @@ class AMQPServer(val hostName: String, private val conf = parent.configuration init { - keyManagerFactory.init(conf.keyStore) + keyManagerFactory.init(conf.keyStore.value.internal, conf.keyStore.password.toCharArray()) trustManagerFactory.init(initialiseTrustStoreAndEnableCrlChecking(conf.trustStore, conf.crlCheckSoftFail)) } diff --git a/node/src/integration-test/kotlin/net/corda/node/BootTests.kt b/node/src/integration-test/kotlin/net/corda/node/BootTests.kt index 410ce8b2de..94cafdf4f6 100644 --- a/node/src/integration-test/kotlin/net/corda/node/BootTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/BootTests.kt @@ -12,19 +12,15 @@ import net.corda.core.messaging.startFlow import net.corda.core.utilities.getOrThrow import net.corda.node.internal.NodeStartup import net.corda.node.services.Permissions.Companion.startFlow -import net.corda.nodeapi.exceptions.InternalNodeException import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.DUMMY_BANK_A_NAME import net.corda.testing.driver.DriverParameters -import net.corda.testing.driver.NodeHandle -import net.corda.testing.driver.NodeParameters import net.corda.testing.driver.driver import net.corda.testing.internal.IntegrationTest import net.corda.testing.internal.IntegrationTestSchemas import net.corda.testing.internal.toDatabaseSchemaName import net.corda.testing.node.User -import net.corda.testing.node.internal.startNode import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.ClassRule import org.junit.Test diff --git a/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt b/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt index 9bf90c5a48..7cf7830a5d 100644 --- a/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt @@ -7,14 +7,25 @@ import net.corda.node.services.config.configureDevKeyAndTrustStores import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.testing.core.ALICE_NAME +import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.driver.DriverParameters import net.corda.testing.driver.driver import net.corda.testing.internal.stubs.CertificateStoreStubs +import net.corda.testing.internal.IntegrationTest +import net.corda.testing.internal.IntegrationTestSchemas +import net.corda.testing.internal.toDatabaseSchemaName import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.ClassRule import org.junit.Test import javax.security.auth.x500.X500Principal -class NodeKeystoreCheckTest { +class NodeKeystoreCheckTest : IntegrationTest() { + companion object { + @ClassRule + @JvmField + val databaseSchemas = IntegrationTestSchemas(ALICE_NAME.toDatabaseSchemaName(), DUMMY_NOTARY_NAME.toDatabaseSchemaName()) + } + @Test fun `starting node in non-dev mode with no key store`() { driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) { diff --git a/node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt b/node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt index 1563abb0c8..9ce9900b62 100644 --- a/node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt @@ -4,11 +4,15 @@ import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.crypto.toStringShort import net.corda.core.internal.div +import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.loggerFor +import net.corda.node.services.config.EnterpriseConfiguration +import net.corda.node.services.config.MutualExclusionConfiguration import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.configureWithDevSSLCertificate import net.corda.node.services.messaging.ArtemisMessagingServer import net.corda.nodeapi.internal.ArtemisMessagingClient +import net.corda.nodeapi.internal.ArtemisMessagingComponent import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2PMessagingHeaders import net.corda.nodeapi.internal.bridging.AMQPBridgeManager import net.corda.nodeapi.internal.bridging.BridgeManager @@ -24,11 +28,15 @@ import net.corda.testing.internal.rigorousMock import org.apache.activemq.artemis.api.core.Message.HDR_DUPLICATE_DETECTION_ID import org.apache.activemq.artemis.api.core.RoutingType import org.apache.activemq.artemis.api.core.SimpleString +import org.apache.activemq.artemis.api.core.client.ClientMessage import org.junit.Assert.assertArrayEquals +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder import java.util.* +import kotlin.system.measureNanoTime +import kotlin.system.measureTimeMillis import kotlin.test.assertEquals class AMQPBridgeTest { @@ -167,6 +175,72 @@ class AMQPBridgeTest { artemisServer.stop() } + @Test + @Ignore("Run only manually to check the throughput of the AMQP bridge") + fun `AMQP full bridge throughput`() { + val numMessages = 10000 + // Create local queue + val sourceQueueName = "internal.peers." + BOB.publicKey.toStringShort() + val (artemisServer, artemisClient, bridgeManager) = createArtemis(sourceQueueName) + + val artemis = artemisClient.started!! + val queueName = ArtemisMessagingComponent.RemoteInboxAddress(BOB.publicKey).queueName + + val (artemisRecServer, artemisRecClient) = createArtemisReceiver(amqpAddress, "artemisBridge") + //artemisBridgeClient.started!!.session.createQueue(SimpleString(queueName), RoutingType.ANYCAST, SimpleString(queueName), true) + + var numReceived = 0 + + artemisRecClient.started!!.session.createQueue(SimpleString(queueName), RoutingType.ANYCAST, SimpleString(queueName), true) + val artemisConsumer = artemisRecClient.started!!.session.createConsumer(queueName) + + val rubbishPayload = ByteArray(10 * 1024) + var timeNanosCreateMessage = 0L + var timeNanosSendMessage = 0L + var timeMillisRead = 0L + val simpleSourceQueueName = SimpleString(sourceQueueName) + val totalTimeMillis = measureTimeMillis { + repeat(numMessages) { + var artemisMessage: ClientMessage? = null + timeNanosCreateMessage += measureNanoTime { + artemisMessage = artemis.session.createMessage(true).apply { + putIntProperty("CountProp", it) + writeBodyBufferBytes(rubbishPayload) + // Use the magic deduplication property built into Artemis as our message identity too + putStringProperty(HDR_DUPLICATE_DETECTION_ID, SimpleString(UUID.randomUUID().toString())) + } + } + timeNanosSendMessage += measureNanoTime { + artemis.producer.send(simpleSourceQueueName, artemisMessage, {}) + } + } + artemisClient.started!!.session.commit() + + + timeMillisRead = measureTimeMillis { + while (numReceived < numMessages) { + val current = artemisConsumer.receive() + val messageId = current.getIntProperty("CountProp") + assertEquals(numReceived, messageId) + ++numReceived + current.acknowledge() + } + } + } + println("Creating $numMessages messages took ${timeNanosCreateMessage / (1000 * 1000)} milliseconds") + println("Sending $numMessages messages took ${timeNanosSendMessage / (1000 * 1000)} milliseconds") + println("Receiving $numMessages messages took $timeMillisRead milliseconds") + println("Total took $totalTimeMillis milliseconds") + assertEquals(numMessages, numReceived) + + bridgeManager.stop() + artemisClient.stop() + artemisServer.stop() + artemisRecClient.stop() + artemisRecServer.stop() + } + + private fun createArtemis(sourceQueueName: String?): Triple { val baseDir = temporaryFolder.root.toPath() / "artemis" val certificatesDirectory = baseDir / "certificates" @@ -181,10 +255,13 @@ class AMQPBridgeTest { doReturn(true).whenever(it).crlCheckSoftFail doReturn(artemisAddress).whenever(it).p2pAddress doReturn(null).whenever(it).jmxMonitoringHttpPort + doReturn(EnterpriseConfiguration(MutualExclusionConfiguration(false, "", 20000, 40000))).whenever(it).enterpriseConfiguration } artemisConfig.configureWithDevSSLCertificate() + val artemisServer = ArtemisMessagingServer(artemisConfig, artemisAddress.copy(host = "0.0.0.0"), MAX_MESSAGE_SIZE) val artemisClient = ArtemisMessagingClient(artemisConfig.p2pSslOptions, artemisAddress, MAX_MESSAGE_SIZE) + artemisServer.start() artemisClient.start() val bridgeManager = AMQPBridgeManager(artemisConfig.p2pSslOptions, artemisAddress, MAX_MESSAGE_SIZE) @@ -198,6 +275,32 @@ class AMQPBridgeTest { return Triple(artemisServer, artemisClient, bridgeManager) } + + private fun createArtemisReceiver(targetAdress: NetworkHostAndPort, workingDir: String): Pair { + val baseDir = temporaryFolder.root.toPath() / workingDir + val certificatesDirectory = baseDir / "certificates" + val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory) + val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory) + val artemisConfig = rigorousMock().also { + doReturn(baseDir).whenever(it).baseDirectory + doReturn(certificatesDirectory).whenever(it).certificatesDirectory + doReturn(BOB_NAME).whenever(it).myLegalName + doReturn(signingCertificateStore).whenever(it).signingCertificateStore + doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions + doReturn(targetAdress).whenever(it).p2pAddress + doReturn("").whenever(it).jmxMonitoringHttpPort + doReturn(EnterpriseConfiguration(MutualExclusionConfiguration(false, "", 20000, 40000))).whenever(it).enterpriseConfiguration + } + artemisConfig.configureWithDevSSLCertificate() + val artemisServer = ArtemisMessagingServer(artemisConfig, NetworkHostAndPort("0.0.0.0", targetAdress.port), MAX_MESSAGE_SIZE) + val artemisClient = ArtemisMessagingClient(artemisConfig.p2pSslOptions, targetAdress, MAX_MESSAGE_SIZE, confirmationWindowSize = 10 * 1024) + artemisServer.start() + artemisClient.start() + + return Pair(artemisServer, artemisClient) + + } + private fun createAMQPServer(maxMessageSize: Int = MAX_MESSAGE_SIZE): AMQPServer { val baseDir = temporaryFolder.root.toPath() / "server" val certificatesDirectory = baseDir / "certificates" diff --git a/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt b/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt index 5ef36c2372..90b737b559 100644 --- a/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt @@ -9,6 +9,8 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.internal.div import net.corda.core.toFuture import net.corda.core.utilities.NetworkHostAndPort +import net.corda.node.services.config.EnterpriseConfiguration +import net.corda.node.services.config.MutualExclusionConfiguration import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.configureWithDevSSLCertificate import net.corda.node.services.messaging.ArtemisMessagingServer @@ -89,6 +91,22 @@ class ProtonWrapperTests { } } + @Test + fun `AMPQ Client fails to connect when crl soft fail check is disabled`() { + val amqpServer = createServer(serverPort, CordaX500Name("Rogue 1", "London", "GB"), + maxMessageSize = MAX_MESSAGE_SIZE, crlCheckSoftFail = false) + amqpServer.use { + amqpServer.start() + val amqpClient = createClient() + amqpClient.use { + val clientConnected = amqpClient.onConnection.toFuture() + amqpClient.start() + val clientConnect = clientConnected.get() + assertEquals(false, clientConnect.connected) + } + } + } + @Test fun `AMPQ Client refuses to connect to unexpected server`() { val amqpServer = createServer(serverPort, CordaX500Name("Rogue 1", "London", "GB")) @@ -398,6 +416,7 @@ class ProtonWrapperTests { doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions doReturn(NetworkHostAndPort("0.0.0.0", artemisPort)).whenever(it).p2pAddress doReturn(null).whenever(it).jmxMonitoringHttpPort + doReturn(EnterpriseConfiguration(MutualExclusionConfiguration(false, "", 20000, 40000))).whenever(it).enterpriseConfiguration doReturn(true).whenever(it).crlCheckSoftFail } artemisConfig.configureWithDevSSLCertificate() @@ -470,7 +489,7 @@ class ProtonWrapperTests { sharedThreadPool = sharedEventGroup) } - private fun createServer(port: Int, name: CordaX500Name = ALICE_NAME, maxMessageSize: Int = MAX_MESSAGE_SIZE): AMQPServer { + private fun createServer(port: Int, name: CordaX500Name = ALICE_NAME, maxMessageSize: Int = MAX_MESSAGE_SIZE, crlCheckSoftFail: Boolean = true): AMQPServer { val baseDirectory = temporaryFolder.root.toPath() / "server" val certificatesDirectory = baseDirectory / "certificates" val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory) @@ -481,7 +500,7 @@ class ProtonWrapperTests { doReturn(name).whenever(it).myLegalName doReturn(signingCertificateStore).whenever(it).signingCertificateStore doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions - doReturn(true).whenever(it).crlCheckSoftFail + doReturn(crlCheckSoftFail).whenever(it).crlCheckSoftFail } serverConfig.configureWithDevSSLCertificate() diff --git a/node/src/integration-test/kotlin/net/corda/node/amqp/SocksTests.kt b/node/src/integration-test/kotlin/net/corda/node/amqp/SocksTests.kt index 3c5f69db3a..a95a4b5917 100644 --- a/node/src/integration-test/kotlin/net/corda/node/amqp/SocksTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/amqp/SocksTests.kt @@ -26,6 +26,7 @@ import net.corda.nodeapi.internal.protonwrapper.netty.* import net.corda.testing.core.* import net.corda.testing.driver.PortAllocation import net.corda.testing.internal.rigorousMock +import net.corda.testing.internal.stubs.CertificateStoreStubs import org.apache.activemq.artemis.api.core.RoutingType import org.junit.After import org.junit.Assert.assertArrayEquals @@ -33,7 +34,6 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder -import java.security.KeyStore import kotlin.test.assertEquals class SocksTests { @@ -266,11 +266,17 @@ class SocksTests { } private fun createArtemisServerAndClient(): Pair { + val baseDirectory = temporaryFolder.root.toPath() / "artemis" + val certificatesDirectory = baseDirectory / "certificates" + val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory) + val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory) + val artemisConfig = rigorousMock().also { - doReturn(temporaryFolder.root.toPath() / "artemis").whenever(it).baseDirectory + doReturn(baseDirectory).whenever(it).baseDirectory + doReturn(certificatesDirectory).whenever(it).certificatesDirectory doReturn(CHARLIE_NAME).whenever(it).myLegalName - doReturn("trustpass").whenever(it).trustStorePassword - doReturn("cordacadevpass").whenever(it).keyStorePassword + doReturn(signingCertificateStore).whenever(it).signingCertificateStore + doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions doReturn(NetworkHostAndPort("0.0.0.0", artemisPort)).whenever(it).p2pAddress doReturn(null).whenever(it).jmxMonitoringHttpPort doReturn(EnterpriseConfiguration(MutualExclusionConfiguration(false, "", 20000, 40000))).whenever(it).enterpriseConfiguration @@ -278,27 +284,32 @@ class SocksTests { artemisConfig.configureWithDevSSLCertificate() val server = ArtemisMessagingServer(artemisConfig, NetworkHostAndPort("0.0.0.0", artemisPort), MAX_MESSAGE_SIZE) - val client = ArtemisMessagingClient(artemisConfig, NetworkHostAndPort("localhost", artemisPort), MAX_MESSAGE_SIZE) + val client = ArtemisMessagingClient(artemisConfig.p2pSslOptions, NetworkHostAndPort("localhost", artemisPort), MAX_MESSAGE_SIZE) server.start() client.start() return Pair(server, client) } private fun createClient(): AMQPClient { + val baseDirectory = temporaryFolder.root.toPath() / "client" + val certificatesDirectory = baseDirectory / "certificates" + val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory) + val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory) + val clientConfig = rigorousMock().also { doReturn(temporaryFolder.root.toPath() / "client").whenever(it).baseDirectory + doReturn(certificatesDirectory).whenever(it).certificatesDirectory doReturn(BOB_NAME).whenever(it).myLegalName - doReturn("trustpass").whenever(it).trustStorePassword - doReturn("cordacadevpass").whenever(it).keyStorePassword + doReturn(signingCertificateStore).whenever(it).signingCertificateStore + doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions } clientConfig.configureWithDevSSLCertificate() - val clientTruststore = clientConfig.loadTrustStore().internal - val clientKeystore = clientConfig.loadSslKeyStore().internal + val clientTruststore = clientConfig.p2pSslOptions.trustStore.get() + val clientKeystore = clientConfig.p2pSslOptions.keyStore.get() val amqpConfig = object : AMQPConfiguration { - override val keyStore: KeyStore = clientKeystore - override val keyStorePrivateKeyPassword: CharArray = clientConfig.keyStorePassword.toCharArray() - override val trustStore: KeyStore = clientTruststore + override val keyStore = clientKeystore + override val trustStore = clientTruststore override val trace: Boolean = true override val maxMessageSize: Int = MAX_MESSAGE_SIZE override val socksProxyConfig: SocksProxyConfig? = SocksProxyConfig(SocksProxyVersion.SOCKS5, NetworkHostAndPort("127.0.0.1", socksPort), null, null) @@ -312,20 +323,25 @@ class SocksTests { } private fun createSharedThreadsClient(sharedEventGroup: EventLoopGroup, id: Int): AMQPClient { + val baseDirectory = temporaryFolder.root.toPath() / "client_%$id" + val certificatesDirectory = baseDirectory / "certificates" + val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory) + val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory) + val clientConfig = rigorousMock().also { - doReturn(temporaryFolder.root.toPath() / "client_%$id").whenever(it).baseDirectory + doReturn(baseDirectory).whenever(it).baseDirectory + doReturn(certificatesDirectory).whenever(it).certificatesDirectory doReturn(CordaX500Name(null, "client $id", "Corda", "London", null, "GB")).whenever(it).myLegalName - doReturn("trustpass").whenever(it).trustStorePassword - doReturn("cordacadevpass").whenever(it).keyStorePassword + doReturn(signingCertificateStore).whenever(it).signingCertificateStore + doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions } clientConfig.configureWithDevSSLCertificate() - val clientTruststore = clientConfig.loadTrustStore().internal - val clientKeystore = clientConfig.loadSslKeyStore().internal + val clientTruststore = clientConfig.p2pSslOptions.trustStore.get() + val clientKeystore = clientConfig.p2pSslOptions.keyStore.get() val amqpConfig = object : AMQPConfiguration { - override val keyStore: KeyStore = clientKeystore - override val keyStorePrivateKeyPassword: CharArray = clientConfig.keyStorePassword.toCharArray() - override val trustStore: KeyStore = clientTruststore + override val keyStore = clientKeystore + override val trustStore = clientTruststore override val trace: Boolean = true override val maxMessageSize: Int = MAX_MESSAGE_SIZE override val socksProxyConfig: SocksProxyConfig? = SocksProxyConfig(SocksProxyVersion.SOCKS5, NetworkHostAndPort("127.0.0.1", socksPort), null, null) @@ -339,20 +355,24 @@ class SocksTests { } private fun createServer(port: Int, name: CordaX500Name = ALICE_NAME): AMQPServer { + val baseDirectory = temporaryFolder.root.toPath() / "server" + val certificatesDirectory = baseDirectory / "certificates" + val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory) + val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory) + val serverConfig = rigorousMock().also { - doReturn(temporaryFolder.root.toPath() / "server").whenever(it).baseDirectory + doReturn(baseDirectory).whenever(it).baseDirectory doReturn(name).whenever(it).myLegalName - doReturn("trustpass").whenever(it).trustStorePassword - doReturn("cordacadevpass").whenever(it).keyStorePassword + doReturn(signingCertificateStore).whenever(it).signingCertificateStore + doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions } serverConfig.configureWithDevSSLCertificate() - val serverTruststore = serverConfig.loadTrustStore().internal - val serverKeystore = serverConfig.loadSslKeyStore().internal + val serverTruststore = serverConfig.p2pSslOptions.trustStore.get() + val serverKeystore = serverConfig.p2pSslOptions.keyStore.get() val amqpConfig = object : AMQPConfiguration { - override val keyStore: KeyStore = serverKeystore - override val keyStorePrivateKeyPassword: CharArray = serverConfig.keyStorePassword.toCharArray() - override val trustStore: KeyStore = serverTruststore + override val keyStore = serverKeystore + override val trustStore = serverTruststore override val trace: Boolean = true override val maxMessageSize: Int = MAX_MESSAGE_SIZE } diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt index 3573ba6c9f..6a63669bce 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt @@ -1,8 +1,6 @@ package net.corda.node.services.network -import net.corda.core.concurrent.CordaFuture import net.corda.core.crypto.random63BitValue -import net.corda.core.identity.CordaX500Name import net.corda.core.internal.* import net.corda.core.internal.concurrent.transpose import net.corda.core.messaging.ParametersUpdateInfo @@ -10,7 +8,6 @@ import net.corda.core.node.NodeInfo import net.corda.core.serialization.serialize import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.seconds -import net.corda.node.services.config.configureDevKeyAndTrustStores import net.corda.nodeapi.internal.NODE_INFO_DIRECTORY import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME @@ -20,12 +17,14 @@ import net.corda.testing.core.* import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.PortAllocation import net.corda.testing.driver.internal.NodeHandleInternal -import net.corda.testing.internal.stubs.CertificateStoreStubs +import net.corda.testing.internal.IntegrationTest +import net.corda.testing.internal.IntegrationTestSchemas +import net.corda.testing.internal.toDatabaseSchemaName import net.corda.testing.node.internal.* import net.corda.testing.node.internal.network.NetworkMapServer import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy -import org.junit.After +import org.junit.* import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule @@ -36,7 +35,7 @@ import java.net.URL import java.time.Instant @RunWith(Parameterized::class) -class NetworkMapTest(var initFunc: (URL, NetworkMapServer) -> CompatibilityZoneParams) { +class NetworkMapTest(var initFunc: (URL, NetworkMapServer) -> CompatibilityZoneParams) : IntegrationTest() { @Rule @JvmField val testSerialization = SerializationEnvironmentRule(true) @@ -48,6 +47,13 @@ class NetworkMapTest(var initFunc: (URL, NetworkMapServer) -> CompatibilityZoneP private lateinit var compatibilityZone: CompatibilityZoneParams companion object { + @ClassRule + @JvmField + val databaseSchemas = IntegrationTestSchemas( + ALICE_NAME.toDatabaseSchemaName(), + BOB_NAME.toDatabaseSchemaName(), + DUMMY_NOTARY_NAME.toDatabaseSchemaName()) + @JvmStatic @Parameterized.Parameters(name = "{0}") fun runParams() = listOf( @@ -241,17 +247,4 @@ class NetworkMapTest(var initFunc: (URL, NetworkMapServer) -> CompatibilityZoneP } assertThat(rpc.networkMapSnapshot()).containsOnly(*nodes) } -} - -private fun DriverDSLImpl.startNode(providedName: CordaX500Name, devMode: Boolean): CordaFuture { - var customOverrides = emptyMap() - if (!devMode) { - val nodeDir = baseDirectory(providedName) - val certificatesDirectory = nodeDir / "certificates" - val signingCertStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory) - val p2pSslConfig = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory) - p2pSslConfig.configureDevKeyAndTrustStores(providedName, signingCertStore, certificatesDirectory) - customOverrides = mapOf("devMode" to "false") - } - return startNode(providedName = providedName, customOverrides = customOverrides) -} +} \ No newline at end of file diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt index 2f52ad269d..437239e1ad 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt @@ -21,9 +21,11 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NOTIFICATI import net.corda.nodeapi.internal.config.MutualSslConfiguration import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME +import net.corda.testing.internal.IntegrationTestSchemas +import net.corda.testing.node.User import net.corda.testing.core.singleIdentity import net.corda.testing.internal.configureTestSSL -import net.corda.testing.node.User +import net.corda.testing.internal.toDatabaseSchemaName import net.corda.testing.node.internal.NodeBasedTest import net.corda.testing.node.internal.startFlow import org.apache.activemq.artemis.api.core.ActiveMQNonExistentQueueException @@ -31,8 +33,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQSecurityException import org.apache.activemq.artemis.api.core.RoutingType import org.apache.activemq.artemis.api.core.SimpleString import org.assertj.core.api.Assertions.assertThatExceptionOfType -import org.junit.After -import org.junit.Before +import org.junit.ClassRule import org.junit.Test import java.util.* import kotlin.test.assertEquals @@ -42,13 +43,20 @@ import kotlin.test.assertEquals * the attacker to [alice]. */ abstract class MQSecurityTest : NodeBasedTest() { + companion object { + @ClassRule + @JvmField + val databaseSchemas = IntegrationTestSchemas(ALICE_NAME.toDatabaseSchemaName(), BOB_NAME.toDatabaseSchemaName()) + } + val rpcUser = User("user1", "pass", permissions = emptySet()) lateinit var alice: NodeWithInfo lateinit var attacker: SimpleMQClient private val clients = ArrayList() - @Before - fun start() { + override fun setUp() { + super.init() + super.setUp() alice = startNode(ALICE_NAME, rpcUsers = extraRPCUsers + rpcUser) attacker = createAttacker() startAttacker(attacker) @@ -60,9 +68,10 @@ abstract class MQSecurityTest : NodeBasedTest() { abstract fun startAttacker(attacker: SimpleMQClient) - @After - fun stopClients() { + override fun tearDown() { + rpcConnections.forEach { it.forceClose() } clients.forEach { it.stop() } + super.tearDown() } @Test @@ -105,11 +114,6 @@ abstract class MQSecurityTest : NodeBasedTest() { return CordaRPCClient(target).start(rpcUser.username, rpcUser.password).also { rpcConnections.add(it) }.proxy } - @After - fun closeRPCConnections() { - rpcConnections.forEach { it.forceClose() } - } - fun loginToRPCAndGetClientQueue(): String { loginToRPC(alice.node.configuration.rpcOptions.address, rpcUser) val clientQueueQuery = SimpleString("${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.${rpcUser.username}.*") diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 0ee1b63f59..215ecc19e4 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -747,7 +747,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, require(nodeCaCertChainRoot == trustRoot) { "Client CA certificate must chain to the trusted root." } if (configuration.devMode) { - val blacklisted = isCRLDistributionPointBlacklisted(configuration.loadNodeKeyStore().getCertificateChain(X509Utilities.CORDA_CLIENT_CA)) + val blacklisted = isCRLDistributionPointBlacklisted(configuration.signingCertificateStore.get().query { getCertificateChain(X509Utilities.CORDA_CLIENT_CA) }) if (blacklisted) { log.warn("The format of the autogenerated dev. mode certificate this system uses has been deprecated. Please contact support@r3.com for information on how to upgrade.") } diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index c135093517..51e84f2048 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -92,7 +92,7 @@ class NodeWithInfo(val node: Node, val info: NodeInfo) { open class Node(configuration: NodeConfiguration, versionInfo: VersionInfo, private val initialiseSerialization: Boolean = true, - cordappLoader: CordappLoader = makeCordappLoader(configuration) + cordappLoader: CordappLoader = makeCordappLoader(configuration, versionInfo) ) : AbstractNode( configuration, createClock(configuration), @@ -134,8 +134,11 @@ open class Node(configuration: NodeConfiguration, } private val sameVmNodeCounter = AtomicInteger() - private fun makeCordappLoader(configuration: NodeConfiguration): CordappLoader { - return JarScanningCordappLoader.fromDirectories(configuration.cordappDirectories) + + @JvmStatic + protected fun makeCordappLoader(configuration: NodeConfiguration, versionInfo: VersionInfo): CordappLoader { + + return JarScanningCordappLoader.fromDirectories(configuration.cordappDirectories, versionInfo) } // TODO: make this configurable. const val MAX_RPC_MESSAGE_SIZE = 10485760 @@ -195,6 +198,7 @@ open class Node(configuration: NodeConfiguration, nodeExecutor = serverThread, database = database, networkMap = networkMapCache, + metricRegistry = metricRegistry, isDrainingModeOn = nodeProperties.flowsDrainingMode::isEnabled, drainingModeWasChangedEvents = nodeProperties.flowsDrainingMode.values ) @@ -227,10 +231,18 @@ open class Node(configuration: NodeConfiguration, startLocalRpcBroker(securityManager) } - val bridgeControlListener = BridgeControlListener(configuration.p2pSslOptions, network.serverAddress, networkParameters.maxMessageSize) + val externalBridge = configuration.enterpriseConfiguration.externalBridge + val bridgeControlListener = if (externalBridge == null || !externalBridge) { + BridgeControlListener(configuration.p2pSslOptions, network.serverAddress, networkParameters.maxMessageSize) + } else { + null + } printBasicNodeInfo("Advertised P2P messaging addresses", nodeInfo.addresses.joinToString()) - val rpcServerConfiguration = RPCServerConfiguration.DEFAULT + + val rpcServerConfiguration = RPCServerConfiguration.DEFAULT.copy( + rpcThreadPoolSize = configuration.enterpriseConfiguration.tuning.rpcThreadPoolSize + ) rpcServerAddresses?.let { internalRpcMessagingClient = InternalRPCMessagingClient(configuration.p2pSslOptions, it.admin, MAX_RPC_MESSAGE_SIZE, CordaX500Name.build(configuration.p2pSslOptions.keyStore.get()[X509Utilities.CORDA_CLIENT_TLS].subjectX500Principal), rpcServerConfiguration) printBasicNodeInfo("RPC connection address", it.primary.toString()) @@ -247,7 +259,7 @@ open class Node(configuration: NodeConfiguration, start() } // Start P2P bridge service - bridgeControlListener.apply { + bridgeControlListener?.apply { closeOnStop() start() } @@ -260,8 +272,9 @@ open class Node(configuration: NodeConfiguration, network.start( myIdentity = nodeInfo.legalIdentities[0].owningKey, serviceIdentity = if (nodeInfo.legalIdentities.size == 1) null else nodeInfo.legalIdentities[1].owningKey, - advertisedAddress = nodeInfo.addresses[0], - maxMessageSize = networkParameters.maxMessageSize + advertisedAddress = nodeInfo.addresses.single(), + maxMessageSize = networkParameters.maxMessageSize, + legalName = nodeInfo.legalIdentities[0].name.toString() ) } @@ -286,12 +299,16 @@ open class Node(configuration: NodeConfiguration, private fun getAdvertisedAddress(): NetworkHostAndPort { return with(configuration) { - val host = if (detectPublicIp) { - tryDetectIfNotPublicHost(p2pAddress.host) ?: p2pAddress.host + if (relay != null) { + NetworkHostAndPort(relay!!.relayHost, relay!!.remoteInboundPort) } else { - p2pAddress.host + val host = if (detectPublicIp) { + tryDetectIfNotPublicHost(p2pAddress.host) ?: p2pAddress.host + } else { + p2pAddress.host + } + NetworkHostAndPort(host, p2pAddress.port) } - NetworkHostAndPort(host, p2pAddress.port) } } @@ -369,6 +386,8 @@ open class Node(configuration: NodeConfiguration, } printBasicNodeInfo("Database connection url is", "jdbc:h2:$url/node") } + } else if (databaseUrl != null) { + printBasicNodeInfo("Database connection url is", databaseUrl) } super.startDatabase() diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index 2754629f75..b8cd514f49 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -17,6 +17,7 @@ import net.corda.nodeapi.internal.config.MutualSslConfiguration import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.config.parseAs +import net.corda.nodeapi.internal.persistence.CordaPersistence.DataSourceConfigTag import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.tools.shell.SSHDConfiguration import org.slf4j.Logger @@ -27,6 +28,7 @@ import java.util.* import javax.security.auth.x500.X500Principal val Int.MB: Long get() = this * 1024L * 1024L +val Int.KB: Long get() = this * 1024L private val DEFAULT_FLOW_MONITOR_PERIOD_MILLIS: Duration = Duration.ofMinutes(1) private val DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS: Duration = Duration.ofMinutes(1) @@ -53,16 +55,20 @@ interface NodeConfiguration { val rpcOptions: NodeRpcOptions val messagingServerAddress: NetworkHostAndPort? val messagingServerExternal: Boolean + val enterpriseConfiguration: EnterpriseConfiguration // TODO Move into DevModeOptions val useTestClock: Boolean get() = false val lazyBridgeStart: Boolean val detectPublicIp: Boolean get() = true val sshd: SSHDConfiguration? val database: DatabaseConfig + val relay: RelayConfiguration? val noLocalShell: Boolean get() = false val transactionCacheSizeBytes: Long get() = defaultTransactionCacheSize val attachmentContentCacheSizeBytes: Long get() = defaultAttachmentContentCacheSize val attachmentCacheBound: Long get() = defaultAttachmentCacheBound + val graphiteOptions: GraphiteOptions? get() = null + // do not change this value without syncing it with ScheduledFlowsDrainingModeTest val drainingModePollPeriod: Duration get() = Duration.ofSeconds(5) val extraNetworkMapKeys: List @@ -110,6 +116,13 @@ enum class JmxReporterType { data class DevModeOptions(val disableCheckpointChecker: Boolean = false, val allowCompatibilityZone: Boolean = false) +data class GraphiteOptions( + val server: String, + val port: Int, + val prefix: String? = null, // defaults to org name and ip address when null + val sampleInvervallSeconds: Long = 60 +) + fun NodeConfiguration.shouldCheckCheckpoints(): Boolean { return this.devMode && this.devModeOptions?.disableCheckpointChecker != true } @@ -122,15 +135,47 @@ data class NotaryConfig(val validating: Boolean, val raft: RaftConfig? = null, val bftSMaRt: BFTSMaRtConfiguration? = null, val custom: Boolean = false, + val mysql: MySQLConfiguration? = null, val serviceLegalName: CordaX500Name? = null ) { init { - require(raft == null || bftSMaRt == null || !custom) { - "raft, bftSMaRt, and custom configs cannot be specified together" + require(raft == null || bftSMaRt == null || !custom || mysql == null) { + "raft, bftSMaRt, custom, and mysql configs cannot be specified together" } } - val isClusterConfig: Boolean get() = raft != null || bftSMaRt != null + val isClusterConfig: Boolean get() = raft != null || bftSMaRt != null || mysql != null +} + +data class MySQLConfiguration( + val dataSource: Properties, + /** + * Number of times to attempt to reconnect to the database. + */ + val connectionRetries: Int = 2, // Default value for a 3 server cluster. + /** + * Time increment between re-connection attempts. + * + * The total back-off duration is calculated as: backOffIncrement * backOffBase ^ currentRetryCount + */ + val backOffIncrement: Int = 500, + /** Exponential back-off multiplier base. */ + val backOffBase: Double = 1.5, + /** The maximum number of transactions processed in a single batch. */ + val maxBatchSize: Int = 500, + /** The maximum combined number of input states processed in a single batch. */ + val maxBatchInputStates: Int = 10_000, + /** A batch will be processed after a specified timeout even if it has not yet reached full capacity. */ + val batchTimeoutMs: Long = 200, + /** + * The maximum number of commit requests in flight. Once the capacity is reached the service will block on + * further commit requests. + */ + val maxQueueSize: Int = 100_000 +) { + init { + require(connectionRetries >= 0) { "connectionRetries cannot be negative" } + } } data class RaftConfig(val nodeAddress: NetworkHostAndPort, val clusterAddresses: List) @@ -200,8 +245,12 @@ data class NodeConfigurationImpl( override val p2pAddress: NetworkHostAndPort, private val rpcAddress: NetworkHostAndPort? = null, private val rpcSettings: NodeRpcSettings, + override val relay: RelayConfiguration?, + // TODO This field is slightly redundant as p2pAddress is sufficient to hold the address of the node's MQ broker. + // Instead this should be a Boolean indicating whether that broker is an internal one started by the node or an external one override val messagingServerAddress: NetworkHostAndPort?, override val messagingServerExternal: Boolean = (messagingServerAddress != null), + override val enterpriseConfiguration: EnterpriseConfiguration, override val notary: NotaryConfig?, @Suppress("DEPRECATION") @Deprecated("Do not configure") @@ -215,10 +264,11 @@ data class NodeConfigurationImpl( // TODO See TODO above. Rename this to nodeInfoPollingFrequency and make it of type Duration override val additionalNodeInfoPollingFrequencyMsec: Long = 5.seconds.toMillis(), override val sshd: SSHDConfiguration? = null, - override val database: DatabaseConfig = DatabaseConfig(initialiseSchema = devMode, exportHibernateJMXStatistics = devMode), + override val database: DatabaseConfig = DatabaseConfig(exportHibernateJMXStatistics = devMode), private val transactionCacheSizeMegaBytes: Int? = null, private val attachmentContentCacheSizeMegaBytes: Int? = null, override val attachmentCacheBound: Long = NodeConfiguration.defaultAttachmentCacheBound, + override val graphiteOptions: GraphiteOptions? = null, override val extraNetworkMapKeys: List = emptyList(), // do not use or remove (breaks DemoBench together with rejection of unknown configuration keys during parsing) private val h2port: Int? = null, @@ -323,7 +373,7 @@ data class NodeConfigurationImpl( } } - // if compatibiliZoneURL is set then it will be copied into the networkServices field and thus skipping + // if compatibilityZoneURL is set then it will be copied into the networkServices field and thus skipping // this check by returning above is fine. networkServices?.let { if (devModeOptions?.allowCompatibilityZone != true) { @@ -362,6 +412,31 @@ data class NodeConfigurationImpl( require(security == null || rpcUsers.isEmpty()) { "Cannot specify both 'rpcUsers' and 'security' in configuration" } + + // ensure our datasource configuration is sane + require(dataSourceProperties.get("autoCommit") != true) { "Datbase auto commit cannot be enabled, Corda requires transactional behaviour" } + dataSourceProperties.set("autoCommit", false) + if (dataSourceProperties.get("transactionIsolation") == null) { + dataSourceProperties["transactionIsolation"] = database.transactionIsolationLevel.jdbcString + } + + // enforce that SQLServer does not get sent all strings as Unicode - hibernate handles this "cleverly" + val dataSourceUrl = dataSourceProperties.getProperty(DataSourceConfigTag.DATA_SOURCE_URL, "") + if (dataSourceUrl.contains(":sqlserver:") && !dataSourceUrl.contains("sendStringParametersAsUnicode", true)) { + dataSourceProperties[DataSourceConfigTag.DATA_SOURCE_URL] = dataSourceUrl + ";sendStringParametersAsUnicode=false" + } + + // Adjust connection pool size depending on N=flow thread pool size. + // If there is no configured pool size set it to N + 1, otherwise check that it's greater than N. + val flowThreadPoolSize = enterpriseConfiguration.tuning.flowThreadPoolSize + val maxConnectionPoolSize = dataSourceProperties.getProperty("maximumPoolSize") + if (maxConnectionPoolSize == null) { + dataSourceProperties.setProperty("maximumPoolSize", (flowThreadPoolSize + 1).toString()) + } else { + require(maxConnectionPoolSize.toInt() > flowThreadPoolSize) + } + + // Check for usage of deprecated config @Suppress("DEPRECATION") if(certificateChainCheckPolicies.isNotEmpty()) { logger.warn("""You are configuring certificateChainCheckPolicies. This is a setting that is not used, and will be removed in a future version. @@ -499,3 +574,10 @@ data class SecurityConfiguration(val authService: SecurityConfiguration.AuthServ } } } + +data class RelayConfiguration(val relayHost: String, + val remoteInboundPort: Int, + val username: String, + val privateKeyFile: Path, + val publicKeyFile: Path, + val sshPort: Int = 22) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt index 05fc112907..69fab8f129 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt @@ -28,9 +28,6 @@ import net.corda.node.utilities.registration.NodeRegistrationHelper import net.corda.nodeapi.internal.DevIdentityGenerator import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.addShutdownHook - -import net.corda.nodeapi.internal.config.NodeSSLConfiguration -import net.corda.nodeapi.internal.config.parseAs import net.corda.nodeapi.internal.config.toConfig import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.crypto.X509Utilities @@ -46,6 +43,7 @@ import net.corda.testing.driver.internal.InProcessImpl import net.corda.testing.driver.internal.NodeHandleInternal import net.corda.testing.driver.internal.OutOfProcessImpl import net.corda.testing.internal.setGlobalSerialization +import net.corda.testing.internal.stubs.CertificateStoreStubs import net.corda.testing.node.ClusterSpec import net.corda.testing.node.NotarySpec import net.corda.testing.node.User @@ -1172,20 +1170,18 @@ private fun Config.toNodeOnly(): Config { return if (hasPath("webAddress")) withoutPath("webAddress").withoutPath("useHTTPS") else this } -internal fun DriverParameters.cordappsForAllNodes(): Set = cordappsForAllNodes ?: cordappsInCurrentAndAdditionalPackages(extraCordappPackagesToScan) +internal fun DriverParameters.cordappsForAllNodes(): Set = cordappsForAllNodes + ?: cordappsInCurrentAndAdditionalPackages(extraCordappPackagesToScan) fun DriverDSL.startNode(providedName: CordaX500Name, devMode: Boolean, parameters: NodeParameters = NodeParameters()): CordaFuture { var customOverrides = emptyMap() if (!devMode) { val nodeDir = baseDirectory(providedName) - val nodeSslConfig = object : NodeSSLConfiguration { - override val baseDirectory = nodeDir - override val keyStorePassword = "cordacadevpass" - override val trustStorePassword = "trustpass" - override val crlCheckSoftFail = true - } - nodeSslConfig.configureDevKeyAndTrustStores(providedName) + val certificatesDirectory = nodeDir / "certificates" + val signingCertStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory) + val p2pSslConfig = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory) + p2pSslConfig.configureDevKeyAndTrustStores(providedName, signingCertStore, certificatesDirectory) customOverrides = mapOf("devMode" to "false") } return startNode(parameters, providedName = providedName, customOverrides = customOverrides) -} +} \ No newline at end of file diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt index 8409d98d59..9a315bb2f1 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt @@ -10,15 +10,15 @@ import net.corda.core.node.NodeInfo import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.loggerFor import net.corda.node.VersionInfo -import net.corda.node.internal.EnterpriseNode import net.corda.node.internal.NodeWithInfo +import net.corda.node.internal.EnterpriseNode import net.corda.node.services.config.* import net.corda.nodeapi.internal.config.toConfig import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.SerializationEnvironmentRule -import net.corda.testing.driver.PortAllocation import net.corda.testing.internal.IntegrationTest +import net.corda.testing.driver.PortAllocation import net.corda.testing.internal.testThreadFactory import net.corda.testing.node.User import org.apache.logging.log4j.Level @@ -86,9 +86,9 @@ abstract class NodeBasedTest(private val cordappPackages: List = emptyLi @JvmOverloads fun initNode(legalName: CordaX500Name, - platformVersion: Int = 4, - rpcUsers: List = emptyList(), - configOverrides: Map = emptyMap()): InProcessNode { + platformVersion: Int = 4, + rpcUsers: List = emptyList(), + configOverrides: Map = emptyMap()): InProcessNode { val baseDirectory = baseDirectory(legalName).createDirectories() val p2pAddress = configOverrides["p2pAddress"] ?: portAllocation.nextHostAndPort().toString() val config = ConfigHelper.loadConfig( diff --git a/testing/qa/behave/tools/rpc-proxy/src/main/kotlin/net/corda/behave/service/proxy/CordaRPCProxyClient.kt b/testing/qa/behave/tools/rpc-proxy/src/main/kotlin/net/corda/behave/service/proxy/CordaRPCProxyClient.kt index 008d035f2d..eae71e575e 100644 --- a/testing/qa/behave/tools/rpc-proxy/src/main/kotlin/net/corda/behave/service/proxy/CordaRPCProxyClient.kt +++ b/testing/qa/behave/tools/rpc-proxy/src/main/kotlin/net/corda/behave/service/proxy/CordaRPCProxyClient.kt @@ -29,15 +29,11 @@ import java.security.PublicKey import java.time.Instant import javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM -// TODO: Make a shared implementation of CordaRPCOps where every method is unimplemented? - class CordaRPCProxyClient(private val targetHostAndPort: NetworkHostAndPort) : CordaRPCOps { companion object { val log = contextLogger() } - override val protocolVersion: Int = 1000 - init { try { AMQPClientSerializationScheme.initialiseSerialization() @@ -79,51 +75,51 @@ class CordaRPCProxyClient(private val targetHostAndPort: NetworkHostAndPort) : C } override fun stateMachinesSnapshot(): List { - TODO("not implemented") + TODO("not implemented") } override fun stateMachinesFeed(): DataFeed, StateMachineUpdate> { - TODO("not implemented") + TODO("not implemented") } override fun vaultQueryBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class): Vault.Page { - TODO("not implemented") + TODO("not implemented") } override fun vaultQueryByCriteria(criteria: QueryCriteria, contractStateType: Class): Vault.Page { - TODO("not implemented") + TODO("not implemented") } override fun vaultQueryByWithPagingSpec(contractStateType: Class, criteria: QueryCriteria, paging: PageSpecification): Vault.Page { - TODO("not implemented") + TODO("not implemented") } override fun vaultQueryByWithSorting(contractStateType: Class, criteria: QueryCriteria, sorting: Sort): Vault.Page { - TODO("not implemented") + TODO("not implemented") } override fun vaultTrackBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class): DataFeed, Vault.Update> { - TODO("not implemented") + TODO("not implemented") } override fun vaultTrack(contractStateType: Class): DataFeed, Vault.Update> { - TODO("not implemented") + TODO("not implemented") } override fun vaultTrackByCriteria(contractStateType: Class, criteria: QueryCriteria): DataFeed, Vault.Update> { - TODO("not implemented") + TODO("not implemented") } override fun vaultTrackByWithPagingSpec(contractStateType: Class, criteria: QueryCriteria, paging: PageSpecification): DataFeed, Vault.Update> { - TODO("not implemented") + TODO("not implemented") } override fun vaultTrackByWithSorting(contractStateType: Class, criteria: QueryCriteria, sorting: Sort): DataFeed, Vault.Update> { - TODO("not implemented") + TODO("not implemented") } override fun internalVerifiedTransactionsSnapshot(): List { - TODO("not implemented") + TODO("not implemented") } override fun internalFindVerifiedTransaction(txnId: SecureHash): SignedTransaction? { @@ -131,107 +127,107 @@ class CordaRPCProxyClient(private val targetHostAndPort: NetworkHostAndPort) : C } override fun internalVerifiedTransactionsFeed(): DataFeed, SignedTransaction> { - TODO("not implemented") + TODO("not implemented") } override fun stateMachineRecordedTransactionMappingSnapshot(): List { - TODO("not implemented") + TODO("not implemented") } override fun stateMachineRecordedTransactionMappingFeed(): DataFeed, StateMachineTransactionMapping> { - TODO("not implemented") + TODO("not implemented") } override fun networkMapFeed(): DataFeed, NetworkMapCache.MapChange> { - TODO("not implemented") + TODO("not implemented") } override fun networkParametersFeed(): DataFeed { - TODO("not implemented") + TODO("not implemented") } override fun acceptNewNetworkParameters(parametersHash: SecureHash) { - TODO("not implemented") + TODO("not implemented") } override fun startTrackedFlowDynamic(logicType: Class>, vararg args: Any?): FlowProgressHandle { - TODO("not implemented") + TODO("not implemented") } override fun addVaultTransactionNote(txnId: SecureHash, txnNote: String) { - TODO("not implemented") + TODO("not implemented") } override fun getVaultTransactionNotes(txnId: SecureHash): Iterable { - TODO("not implemented") + TODO("not implemented") } override fun attachmentExists(id: SecureHash): Boolean { - TODO("not implemented") + TODO("not implemented") } override fun openAttachment(id: SecureHash): InputStream { - TODO("not implemented") + TODO("not implemented") } override fun uploadAttachment(jar: InputStream): SecureHash { - TODO("not implemented") + TODO("not implemented") } override fun uploadAttachmentWithMetadata(jar: InputStream, uploader: String, filename: String): SecureHash { - TODO("not implemented") + TODO("not implemented") } override fun queryAttachments(query: AttachmentQueryCriteria, sorting: AttachmentSort?): List { - TODO("not implemented") + TODO("not implemented") } override fun currentNodeTime(): Instant { - TODO("not implemented") + TODO("not implemented") } override fun waitUntilNetworkReady(): CordaFuture { - TODO("not implemented") + TODO("not implemented") } override fun wellKnownPartyFromAnonymous(party: AbstractParty): Party? { - TODO("not implemented") + TODO("not implemented") } override fun partyFromKey(key: PublicKey): Party? { - TODO("not implemented") + TODO("not implemented") } override fun wellKnownPartyFromX500Name(x500Name: CordaX500Name): Party? { - TODO("not implemented") + TODO("not implemented") } override fun notaryPartyFromX500Name(x500Name: CordaX500Name): Party? { - TODO("not implemented") + TODO("not implemented") } override fun nodeInfoFromParty(party: AbstractParty): NodeInfo? { - TODO("not implemented") + TODO("not implemented") } override fun clearNetworkMapCache() { - TODO("not implemented") + TODO("not implemented") } override fun setFlowsDrainingModeEnabled(enabled: Boolean) { - TODO("not implemented") + TODO("not implemented") } override fun isFlowsDrainingModeEnabled(): Boolean { - TODO("not implemented") + TODO("not implemented") } override fun shutdown() { - TODO("not implemented") + TODO("not implemented") } override fun killFlow(id: StateMachineRunId): Boolean { - TODO("not implemented") + TODO("not implemented") } override fun refreshNetworkMapCache() { From c77a75bc10b3acca6fa9c84c7a1dda2dbaedb265 Mon Sep 17 00:00:00 2001 From: Michele Sollecito Date: Tue, 4 Sep 2018 11:28:21 +0100 Subject: [PATCH 3/3] [CORDA-1937]: Fixes to enterprise float and bridge. --- .../kotlin/net/corda/node/amqp/SocksTests.kt | 1 + .../vault/VaultQueryIntegrationTests.kt | 58 +++++++++---------- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/node/src/integration-test/kotlin/net/corda/node/amqp/SocksTests.kt b/node/src/integration-test/kotlin/net/corda/node/amqp/SocksTests.kt index a95a4b5917..006ee4c50c 100644 --- a/node/src/integration-test/kotlin/net/corda/node/amqp/SocksTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/amqp/SocksTests.kt @@ -362,6 +362,7 @@ class SocksTests { val serverConfig = rigorousMock().also { doReturn(baseDirectory).whenever(it).baseDirectory + doReturn(certificatesDirectory).whenever(it).certificatesDirectory doReturn(name).whenever(it).myLegalName doReturn(signingCertificateStore).whenever(it).signingCertificateStore doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions diff --git a/node/src/integration-test/kotlin/net/corda/node/services/vault/VaultQueryIntegrationTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/vault/VaultQueryIntegrationTests.kt index da7322d334..0bf1291493 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/vault/VaultQueryIntegrationTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/vault/VaultQueryIntegrationTests.kt @@ -1,29 +1,29 @@ -package net.corda.node.services.vault - - -import net.corda.core.identity.CordaX500Name -import net.corda.testing.core.TestIdentity -import net.corda.testing.internal.GlobalDatabaseRule -import net.corda.testing.internal.toDatabaseSchemaName -import org.junit.ClassRule -import org.junit.Rule -import org.junit.rules.RuleChain - -class VaultQueryIntegrationTests : VaultQueryTestsBase(), VaultQueryParties by vaultQueryTestRule { - - companion object { - val MEGA_CORP = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")).name - val databaseSchemas = listOf(Companion.MEGA_CORP.toDatabaseSchemaName()) - - val globalDatabaseRule = GlobalDatabaseRule(databaseSchemas) - val vaultQueryTestRule = VaultQueryTestRule() - - @ClassRule @JvmField - val ruleChain = RuleChain.outerRule(globalDatabaseRule).around(vaultQueryTestRule) - } - - @Suppress("LeakingThis") - @Rule - @JvmField - val transactionRule = VaultQueryRollbackRule(this) -} +//package net.corda.node.services.vault +// +// +//import net.corda.core.identity.CordaX500Name +//import net.corda.testing.core.TestIdentity +//import net.corda.testing.internal.GlobalDatabaseRule +//import net.corda.testing.internal.toDatabaseSchemaName +//import org.junit.ClassRule +//import org.junit.Rule +//import org.junit.rules.RuleChain +// +//class VaultQueryIntegrationTests : VaultQueryTestsBase(), VaultQueryParties by vaultQueryTestRule { +// +// companion object { +// val MEGA_CORP = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")).name +// val databaseSchemas = listOf(Companion.MEGA_CORP.toDatabaseSchemaName()) +// +// val globalDatabaseRule = GlobalDatabaseRule(databaseSchemas) +// val vaultQueryTestRule = VaultQueryTestRule() +// +// @ClassRule @JvmField +// val ruleChain = RuleChain.outerRule(globalDatabaseRule).around(vaultQueryTestRule) +// } +// +// @Suppress("LeakingThis") +// @Rule +// @JvmField +// val transactionRule = VaultQueryRollbackRule(this) +//}