Merge remote-tracking branch 'open/master' into os_ent_merges/CORDA-1937

# Conflicts:
#	client/rpc/src/main/kotlin/net/corda/client/rpc/internal/CordaRPCClientUtils.kt
#	core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt
#	node-api/src/main/kotlin/net/corda/nodeapi/internal/ArtemisMessagingClient.kt
#	node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/AMQPBridgeManager.kt
#	node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/BridgeControlListener.kt
#	node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt
#	node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt
#	node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt
#	node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt
#	node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt
#	node/src/main/kotlin/net/corda/node/internal/Node.kt
#	node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt
This commit is contained in:
Michele Sollecito 2018-09-04 11:07:50 +01:00
commit fbaa31e9d2
55 changed files with 1023 additions and 978 deletions

View File

@ -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<NetworkHostAndPort> = emptyList(),
private val internalConnection: Boolean = false
private val haAddressPool: List<NetworkHostAndPort> = 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<NetworkHostAndPort>, configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT) : this(haAddressPool.first(), configuration, null, null, null, haAddressPool)
constructor(haAddressPool: List<NetworkHostAndPort>, 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<CordaRPCOps> {
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),

View File

@ -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<NetworkHostAndPort>,
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
sslConfiguration: ClientRpcSslOptions? = null,
classLoader: ClassLoader? = null
) = CordaRPCClient.createWithSslAndClassLoader(haAddressPool, configuration, sslConfiguration, classLoader)
fun CordaRPCOps.drainAndShutdown(): Observable<Unit> {
setFlowsDrainingModeEnabled(true)

View File

@ -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<I : RPCOps>(
constructor(
hostAndPort: NetworkHostAndPort,
sslConfiguration: SSLConfiguration,
sslConfiguration: SslConfiguration,
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
serializationContext: SerializationContext = SerializationDefaults.RPC_CLIENT_CONTEXT
) : this(rpcInternalClientTcpTransport(hostAndPort, sslConfiguration), configuration, serializationContext)

View File

@ -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

View File

@ -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)
}
}
}

View File

@ -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<ArtemisMessagingClient>()
}
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
}

View File

@ -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

View File

@ -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<String, Any> {
val options = mutableMapOf<String, Any>()
(keyStore to trustStore).addToTransportOptions(options)
return options
}
private fun Pair<FileBasedCertificateStoreSupplier?, FileBasedCertificateStoreSupplier?>.addToTransportOptions(options: MutableMap<String, Any>) {
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<NetworkHostAndPort>, config: ClientRpcSslOptions?, enableSSL: Boolean = true): List<TransportConfiguration> = 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())
}
}
}

View File

@ -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<X509KeyStore, X509KeyStore> {
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)

View File

@ -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<String, AMQPBridge>()
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

View File

@ -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<String>()
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<Boolean>().toSerialized()
val activeChange: Observable<Boolean>
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<BridgeControl.BridgeToNodeSnapshotRequest>(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!")

View File

@ -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<Pair<String, X509Certificate>> {
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 <RESULT> 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<Pair<String, X509Certificate>> {
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

View File

@ -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)
}

View File

@ -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"
}
}

View File

@ -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)

View File

@ -132,7 +132,7 @@ class AMQPClient(val targets: List<NetworkHostAndPort>,
private val conf = parent.configuration
init {
keyManagerFactory.init(conf.keyStore, conf.keyStorePrivateKeyPassword)
keyManagerFactory.init(conf.keyStore)
trustManagerFactory.init(initialiseTrustStoreAndEnableCrlChecking(conf.trustStore, conf.crlCheckSoftFail))
}

View File

@ -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

View File

@ -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))
}

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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<ArtemisMessagingServer, ArtemisMessagingClient, BridgeManager> {
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<AbstractNodeConfiguration>().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<ArtemisMessagingServer, ArtemisMessagingClient> {
val artemisConfig = rigorousMock<AbstractNodeConfiguration>().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<AbstractNodeConfiguration>().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
}

View File

@ -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<AMQPClient, X509Certificate> {
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<AbstractNodeConfiguration>().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<AMQPServer, X509Certificate> {
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<AbstractNodeConfiguration>().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<CertificateStoreSupplier, MutualSslConfiguration>.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)
}

View File

@ -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<ArtemisMessagingServer, ArtemisMessagingClient> {
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<AbstractNodeConfiguration>().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<AbstractNodeConfiguration>().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<AbstractNodeConfiguration>().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<AbstractNodeConfiguration>().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
}

View File

@ -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<AbstractNodeConfiguration>().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

View File

@ -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)
}
}
}
private fun DriverDSLImpl.startNode(providedName: CordaX500Name, devMode: Boolean): CordaFuture<NodeHandle> {
var customOverrides = emptyMap<String, String>()
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)
}

View File

@ -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?,

View File

@ -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)
}

View File

@ -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<SimpleMQClient>()
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}.*")

View File

@ -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

View File

@ -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<S>(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<S>(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<S>(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<S>(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<S>(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<S>(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<S>(val configuration: NodeConfiguration,
networkParameters: NetworkParameters)
private fun obtainIdentity(notaryConfig: NotaryConfig?): Pair<PartyAndCertificate, KeyPair> {
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<S>(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)
}

View File

@ -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<NodeInfo>(
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()

View File

@ -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))

View File

@ -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<UUID>
@ -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<Path> 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<Path>
fun validate(): List<String>
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<NetworkHostAndPort>)
@ -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<UUID> = 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)

View File

@ -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)

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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<ArtemisRpcBroker>()
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<AppConfigurationEntry> {
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))
}

View File

@ -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 {

View File

@ -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<CopycatClient>
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()
}

View File

@ -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<X509Certificate>, keyAlias: String) {
private fun storePrivateKeyWithCertificates(nodeKeystore: CertificateStore, keyPair: KeyPair, certificates: List<X509Certificate>, 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<X509Certificate>, 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
}

View File

@ -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<AbstractNodeConfiguration>().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,

View File

@ -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<out Process>): CordaFuture<CordaRPCOps> {
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
}

View File

@ -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<AbstractNodeConfiguration>().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<User>()).whenever(it).rpcUsers
doReturn(null).whenever(it).notary
doReturn(DatabaseConfig()).whenever(it).database

View File

@ -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<I> {
return driverDSL.executorService.fork {
val client = RPCClient<I>(ArtemisTcpTransport.rpcConnectorTcpTransport(rpcAddress, null), configuration)
val client = RPCClient<I>(InternalArtemisTcpTransport.rpcConnectorTcpTransport(rpcAddress, null), configuration)
val connection = client.start(rpcOpsClass, username, password, externalTrace)
driverDSL.shutdownManager.registerShutdown {
connection.close()

View File

@ -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<String, UnsafeCertificate>
@ -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())

View File

@ -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 <reified T : Any> T.amqpSpecific(reason: String, function: () -> Unit
loggerFor<T>().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<String, String> {
)
}
fun SSLConfiguration.noSslRpcOverrides(rpcAdminAddress: NetworkHostAndPort): Map<String, Any> {
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
}

View File

@ -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)
}
}
}
}
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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) {

View File

@ -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 {

View File

@ -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")