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 d2b3fa0f09..f1ec9fcad9 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) } internal fun createWithSslAndClassLoader( @@ -321,9 +309,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 5908bb3fbe..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,20 +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 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/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 8de2892c33..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,22 +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.schemas.MappedSchema 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 f814f18795..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,11 +3,13 @@ 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 org.apache.activemq.artemis.api.core.client.* +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 +import org.apache.activemq.artemis.api.core.client.ClientSession +import org.apache.activemq.artemis.api.core.client.ClientSessionFactory interface ArtemisSessionProvider { fun start(): ArtemisMessagingClient.Started @@ -15,19 +17,14 @@ interface ArtemisSessionProvider { val started: ArtemisMessagingClient.Started? } -class ArtemisMessagingClient( - private val config: SSLConfiguration, - private val serverAddress: NetworkHostAndPort, - private val maxMessageSize: Int, - private val autoCommitSends: Boolean = true, - private val autoCommitAcks: Boolean = true, - private val confirmationWindowSize: Int = -1 -) : ArtemisSessionProvider { +class ArtemisMessagingClient(private val config: MutualSslConfiguration, + private val serverAddress: NetworkHostAndPort, + private val maxMessageSize: Int) : ArtemisSessionProvider { companion object { private val log = loggerFor() } - class Started(val serverLocator: ServerLocator, val sessionFactory: ClientSessionFactory, val session: ClientSession, val producer: ClientProducer) + class Started(val sessionFactory: ClientSessionFactory, val session: ClientSession, val producer: ClientProducer) override var started: Started? = null private set @@ -36,7 +33,7 @@ class ArtemisMessagingClient( 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. @@ -44,7 +41,6 @@ class ArtemisMessagingClient( clientFailureCheckPeriod = 30000 minLargeMessageSize = maxMessageSize isUseGlobalPools = nodeSerializationEnv != null - confirmationWindowSize = this@ArtemisMessagingClient.confirmationWindowSize addIncomingInterceptor(ArtemisMessageSizeChecksInterceptor(maxMessageSize)) } val sessionFactory = locator.createSessionFactory() @@ -52,11 +48,11 @@ class ArtemisMessagingClient( // 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, autoCommitSends, autoCommitAcks, false, DEFAULT_ACK_BATCH_SIZE) + val session = sessionFactory!!.createSession(NODE_P2P_USER, NODE_P2P_USER, false, true, true, false, DEFAULT_ACK_BATCH_SIZE) session.start() // Create a general purpose producer. val producer = session.createProducer() - return Started(locator, sessionFactory, session, producer).also { started = it } + return Started(sessionFactory, session, producer).also { started = it } } override fun stop() = synchronized(this) { @@ -66,7 +62,6 @@ class ArtemisMessagingClient( 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/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 cf46ee8b6a..5836920f6d 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 @@ -26,7 +27,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 @@ -38,30 +38,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, socksProxyConfig: SocksProxyConfig? = null, - 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, - override val socksProxyConfig: SocksProxyConfig?, + private class AMQPConfigurationImpl private constructor(override val keyStore: CertificateStore, + override val trustStore: CertificateStore, override val maxMessageSize: Int) : AMQPConfiguration { - constructor(config: NodeSSLConfiguration, socksProxyConfig: SocksProxyConfig?, maxMessageSize: Int) : this(config.loadSslKeyStore().internal, - config.keyStorePassword.toCharArray(), - config.loadTrustStore().internal, - socksProxyConfig, - maxMessageSize) + constructor(config: MutualSslConfiguration, maxMessageSize: Int) : this(config.keyStore.get(), config.trustStore.get(), maxMessageSize) } private val amqpConfig: AMQPConfiguration = AMQPConfigurationImpl(config, socksProxyConfig, maxMessageSize) private var sharedEventLoopGroup: EventLoopGroup? = null private var artemis: ArtemisSessionProvider? = null - constructor(config: NodeSSLConfiguration, p2pAddress: NetworkHostAndPort, maxMessageSize: Int, socksProxyConfig: SocksProxyConfig? = null) : this(config, socksProxyConfig, 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 dca59de998..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,51 +11,30 @@ 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.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: NodeSSLConfiguration, - - socksProxyConfig: SocksProxyConfig? = null, +class BridgeControlListener(val config: MutualSslConfiguration, maxMessageSize: Int, val artemisMessageClientFactory: () -> ArtemisSessionProvider) : AutoCloseable { private val bridgeId: String = UUID.randomUUID().toString() - private val bridgeControlQueue = "$BRIDGE_CONTROL.$bridgeId" - private val bridgeNotifyQueue = "$BRIDGE_NOTIFY.$bridgeId" - private val bridgeManager: BridgeManager = AMQPBridgeManager(config, socksProxyConfig, maxMessageSize, - artemisMessageClientFactory) + private val bridgeManager: BridgeManager = AMQPBridgeManager(config, maxMessageSize, artemisMessageClientFactory) private val validInboundQueues = mutableSetOf() private var artemis: ArtemisSessionProvider? = null private var controlConsumer: ClientConsumer? = null - private var notifyConsumer: ClientConsumer? = null - constructor(config: NodeSSLConfiguration, + constructor(config: MutualSslConfiguration, p2pAddress: NetworkHostAndPort, - - maxMessageSize: Int, - socksProxy: SocksProxyConfig? = null) : this(config, socksProxy, maxMessageSize, { ArtemisMessagingClient(config, p2pAddress, maxMessageSize) }) - + maxMessageSize: Int) : this(config, 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() @@ -64,21 +43,8 @@ class BridgeControlListener(val config: NodeSSLConfiguration, artemis.start() val artemisClient = artemis.started!! val artemisSession = artemisClient.session - 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 bridgeControlQueue = "$BRIDGE_CONTROL.$bridgeId" + artemisSession.createTemporaryQueue(BRIDGE_CONTROL, RoutingType.MULTICAST, bridgeControlQueue) val control = artemisSession.createConsumer(bridgeControlQueue) controlConsumer = control control.setMessageHandler { msg -> @@ -88,44 +54,17 @@ class BridgeControlListener(val config: NodeSSLConfiguration, log.error("Unable to process bridge control message", ex) } } - } - - 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) - } - } + 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) } fun stop() { - if (active) { - _activeChange.onNext(false) - } validInboundQueues.clear() controlConsumer?.close() controlConsumer = null - notifyConsumer?.close() - notifyConsumer = null - artemis?.apply { - started?.session?.deleteQueue(bridgeControlQueue) - started?.session?.deleteQueue(bridgeNotifyQueue) - stop() - } + artemis?.stop() artemis = null bridgeManager.stop() } @@ -161,11 +100,7 @@ class BridgeControlListener(val config: NodeSSLConfiguration, 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/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 b73b6a8bb4..db2bba18d3 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 @@ -132,7 +132,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 4e873cdbaa..d39c5b4647 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 e2a3cd4eec..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,29 +4,17 @@ 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.core.DUMMY_NOTARY_NAME import net.corda.testing.driver.DriverParameters 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.internal.stubs.CertificateStoreStubs import org.assertj.core.api.Assertions.assertThatThrownBy -import org.junit.ClassRule import org.junit.Test -import java.nio.file.Path import javax.security.auth.x500.X500Principal -class NodeKeystoreCheckTest : IntegrationTest() { - companion object { - @ClassRule - @JvmField - val databaseSchemas = IntegrationTestSchemas(ALICE_NAME.toDatabaseSchemaName(), DUMMY_NOTARY_NAME.toDatabaseSchemaName()) - } - +class NodeKeystoreCheckTest { @Test fun `starting node in non-dev mode with no key store`() { driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) { @@ -41,13 +29,11 @@ class NodeKeystoreCheckTest : IntegrationTest() { 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( @@ -59,7 +45,7 @@ class NodeKeystoreCheckTest : IntegrationTest() { 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 6d1d97f6a9..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 @@ -4,15 +4,11 @@ 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 @@ -23,20 +19,16 @@ 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 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.security.KeyStore import java.util.* -import kotlin.system.measureNanoTime -import kotlin.system.measureTimeMillis import kotlin.test.assertEquals class AMQPBridgeTest { @@ -175,91 +167,27 @@ 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" + 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 - 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, 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) { @@ -270,40 +198,24 @@ class AMQPBridgeTest { return Triple(artemisServer, artemisClient, bridgeManager) } - - private fun createArtemisReceiver(targetAdress: NetworkHostAndPort, workingDir: String): Pair { - val artemisConfig = rigorousMock().also { - doReturn(temporaryFolder.root.toPath() / workingDir).whenever(it).baseDirectory - doReturn(BOB_NAME).whenever(it).myLegalName - doReturn("trustpass").whenever(it).trustStorePassword - doReturn("cordacadevpass").whenever(it).keyStorePassword - 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, 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" + 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 fa83c8fd71..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 @@ -9,21 +9,21 @@ 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 -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 @@ -31,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 @@ -89,22 +89,6 @@ 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")) @@ -120,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) @@ -157,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 @@ -406,41 +386,49 @@ 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(EnterpriseConfiguration(MutualExclusionConfiguration(false, "", 20000, 40000))).whenever(it).enterpriseConfiguration doReturn(true).whenever(it).crlCheckSoftFail } 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 } @@ -453,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 } @@ -478,25 +470,26 @@ class ProtonWrapperTests { sharedThreadPool = sharedEventGroup) } - - private fun createServer(port: Int, name: CordaX500Name = ALICE_NAME, maxMessageSize: Int = MAX_MESSAGE_SIZE, - crlCheckSoftFail: Boolean = true - ): AMQPServer { + 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(crlCheckSoftFail).whenever(it).crlCheckSoftFail + 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 82b5e96725..0ca24ab327 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 @@ -5,6 +5,7 @@ import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import com.typesafe.config.ConfigFactory 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.internal.MOCK_VERSION_INFO @@ -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(EnterpriseConfiguration(MutualExclusionConfiguration(false, "", 20000, 40000))).whenever(it).enterpriseConfiguration 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 b556a6f2c1..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 @@ -1,8 +1,8 @@ package net.corda.node.services.network -import net.corda.cordform.CordformNode 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 @@ -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,15 +20,12 @@ 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.IntegrationTest -import net.corda.testing.internal.IntegrationTestSchemas -import net.corda.testing.internal.toDatabaseSchemaName +import net.corda.testing.internal.stubs.CertificateStoreStubs import net.corda.testing.node.internal.* import net.corda.testing.node.internal.network.NetworkMapServer -import net.corda.testing.node.internal.startNode import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy -import org.junit.* +import org.junit.After import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule @@ -40,7 +36,7 @@ import java.net.URL import java.time.Instant @RunWith(Parameterized::class) -class NetworkMapTest(var initFunc: (URL, NetworkMapServer) -> CompatibilityZoneParams) : IntegrationTest() { +class NetworkMapTest(var initFunc: (URL, NetworkMapServer) -> CompatibilityZoneParams) { @Rule @JvmField val testSerialization = SerializationEnvironmentRule(true) @@ -52,13 +48,6 @@ 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( @@ -252,4 +241,17 @@ class NetworkMapTest(var initFunc: (URL, NetworkMapServer) -> CompatibilityZoneP } assertThat(rpc.networkMapSnapshot()).containsOnly(*nodes) } -} \ No newline at end of file +} + +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) +} 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 2ba6d4c917..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,14 +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.internal.IntegrationTestSchemas -import net.corda.testing.node.User import net.corda.testing.core.singleIdentity import net.corda.testing.internal.configureTestSSL -import net.corda.testing.internal.toDatabaseSchemaName +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 @@ -33,7 +31,8 @@ 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.ClassRule +import org.junit.After +import org.junit.Before import org.junit.Test import java.util.* import kotlin.test.assertEquals @@ -43,20 +42,13 @@ 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() - override fun setUp() { - super.init() - super.setUp() + @Before + fun start() { alice = startNode(ALICE_NAME, rpcUsers = extraRPCUsers + rpcUser) attacker = createAttacker() startAttacker(attacker) @@ -68,10 +60,9 @@ abstract class MQSecurityTest : NodeBasedTest() { abstract fun startAttacker(attacker: SimpleMQClient) - override fun tearDown() { - rpcConnections.forEach { it.forceClose() } + @After + fun stopClients() { clients.forEach { it.stop() } - super.tearDown() } @Test @@ -103,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 @@ -114,6 +105,11 @@ 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/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 2c47696745..0ee1b63f59 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -46,6 +46,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 @@ -257,7 +258,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 { @@ -287,7 +288,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 { @@ -392,10 +393,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) } } @@ -720,8 +721,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.") @@ -738,9 +739,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." } @@ -797,7 +798,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 -> { @@ -844,7 +845,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 @@ -859,25 +860,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 } @@ -1075,4 +1076,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 6757953e9a..c135093517 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, versionInfo) + cordappLoader: CordappLoader = makeCordappLoader(configuration) ) : AbstractNode( configuration, createClock(configuration), @@ -134,11 +134,8 @@ open class Node(configuration: NodeConfiguration, } private val sameVmNodeCounter = AtomicInteger() - - @JvmStatic - protected fun makeCordappLoader(configuration: NodeConfiguration, versionInfo: VersionInfo): CordappLoader { - - return JarScanningCordappLoader.fromDirectories(configuration.cordappDirectories, versionInfo) + private fun makeCordappLoader(configuration: NodeConfiguration): CordappLoader { + return JarScanningCordappLoader.fromDirectories(configuration.cordappDirectories) } // TODO: make this configurable. const val MAX_RPC_MESSAGE_SIZE = 10485760 @@ -198,7 +195,6 @@ open class Node(configuration: NodeConfiguration, nodeExecutor = serverThread, database = database, networkMap = networkMapCache, - metricRegistry = metricRegistry, isDrainingModeOn = nodeProperties.flowsDrainingMode::isEnabled, drainingModeWasChangedEvents = nodeProperties.flowsDrainingMode.values ) @@ -231,20 +227,12 @@ open class Node(configuration: NodeConfiguration, startLocalRpcBroker(securityManager) } - val externalBridge = configuration.enterpriseConfiguration.externalBridge - val bridgeControlListener = if (externalBridge == null || !externalBridge) { - BridgeControlListener(configuration, network.serverAddress, networkParameters.maxMessageSize) - } else { - null - } + val bridgeControlListener = BridgeControlListener(configuration.p2pSslOptions, network.serverAddress, networkParameters.maxMessageSize) printBasicNodeInfo("Advertised P2P messaging addresses", nodeInfo.addresses.joinToString()) - - val rpcServerConfiguration = RPCServerConfiguration.DEFAULT.copy( - rpcThreadPoolSize = configuration.enterpriseConfiguration.tuning.rpcThreadPoolSize - ) + 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()) } @@ -259,7 +247,7 @@ open class Node(configuration: NodeConfiguration, start() } // Start P2P bridge service - bridgeControlListener?.apply { + bridgeControlListener.apply { closeOnStop() start() } @@ -272,9 +260,8 @@ 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.single(), - maxMessageSize = networkParameters.maxMessageSize, - legalName = nodeInfo.legalIdentities[0].name.toString() + advertisedAddress = nodeInfo.addresses[0], + maxMessageSize = networkParameters.maxMessageSize ) } @@ -284,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() @@ -299,16 +286,12 @@ open class Node(configuration: NodeConfiguration, private fun getAdvertisedAddress(): NetworkHostAndPort { return with(configuration) { - if (relay != null) { - NetworkHostAndPort(relay!!.relayHost, relay!!.remoteInboundPort) + val host = if (detectPublicIp) { + tryDetectIfNotPublicHost(p2pAddress.host) ?: p2pAddress.host } else { - val host = if (detectPublicIp) { - tryDetectIfNotPublicHost(p2pAddress.host) ?: p2pAddress.host - } else { - p2pAddress.host - } - NetworkHostAndPort(host, p2pAddress.port) + p2pAddress.host } + NetworkHostAndPort(host, p2pAddress.port) } } @@ -386,8 +369,6 @@ 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/ConfigUtilities.kt b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt index 81c7992d13..96f0383fba 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 @@ -10,11 +10,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 import kotlin.math.min @@ -83,22 +82,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 ddecc2fd7d..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,11 +11,12 @@ 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 -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 @@ -26,13 +27,12 @@ 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) private const val CORDAPPS_DIR_NAME_DEFAULT = "cordapps" -interface NodeConfiguration : NodeSSLConfiguration { +interface NodeConfiguration { val myLegalName: CordaX500Name val emailAddress: String val jmxMonitoringHttpPort: Int? @@ -53,20 +53,16 @@ interface NodeConfiguration : NodeSSLConfiguration { 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 @@ -75,8 +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 { @@ -106,13 +110,6 @@ 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 } @@ -125,47 +122,15 @@ 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 || mysql == null) { - "raft, bftSMaRt, custom, and mysql configs cannot be specified together" + require(raft == null || bftSMaRt == null || !custom) { + "raft, bftSMaRt, and custom configs cannot be specified together" } } - 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" } - } + val isClusterConfig: Boolean get() = raft != null || bftSMaRt != null } data class RaftConfig(val nodeAddress: NetworkHostAndPort, val clusterAddresses: List) @@ -220,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, @@ -235,12 +200,8 @@ 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") @@ -254,11 +215,10 @@ 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(exportHibernateJMXStatistics = devMode), + override val database: DatabaseConfig = DatabaseConfig(initialiseSchema = devMode, 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, @@ -292,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() @@ -352,7 +323,7 @@ data class NodeConfigurationImpl( } } - // if compatibilityZoneURL is set then it will be copied into the networkServices field and thus skipping + // if compatibiliZoneURL 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) { @@ -391,31 +362,6 @@ 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. @@ -430,8 +376,6 @@ data class NodeConfigurationImpl( } } - - data class NodeRpcSettings( val address: NetworkHostAndPort?, val adminAddress: NetworkHostAndPort?, @@ -555,10 +499,3 @@ 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/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 11c6b8a37c..0569e71258 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 @@ -163,8 +162,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 84d7a6185d..e022b46d0d 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 @@ -27,7 +27,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 @@ -35,6 +34,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 @@ -152,7 +152,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 504dac7262..05fc112907 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 @@ -20,6 +20,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 @@ -172,7 +173,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) } @@ -842,8 +844,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 7214fd450c..a514351492 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 @@ -54,6 +54,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(makeInternalTestDataSourceProperties("node_$id", "net_$networkId")).whenever(it).dataSourceProperties doReturn(makeTestDatabaseProperties("node_$id")).whenever(it).database @@ -562,12 +566,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 ceda937104..89d01a0a31 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 cf8882a6b5..dbae24f6ff 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 @@ -145,7 +145,7 @@ class InteractiveShellIntegrationTest : IntegrationTest() { 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")