mirror of
https://github.com/corda/corda.git
synced 2025-06-22 09:08:49 +00:00
Add X509 creation and manipulation utilities to core and enable SSL in ArtemisMQ
This commit is contained in:
@ -71,7 +71,10 @@ class Node(dir: Path, val p2pAddr: HostAndPort, val webServerAddr: HostAndPort,
|
||||
|
||||
override fun startMessagingService() {
|
||||
// Start up the MQ service.
|
||||
(net as ArtemisMessagingService).start()
|
||||
(net as ArtemisMessagingService).apply {
|
||||
configureWithDevSSLCertificate() //Provision a dev certificate and private key if required
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
private fun initWebServer(): Server {
|
||||
|
@ -3,6 +3,7 @@ package com.r3corda.node.services.messaging
|
||||
import com.google.common.net.HostAndPort
|
||||
import com.r3corda.core.RunOnCallerThread
|
||||
import com.r3corda.core.ThreadBox
|
||||
import com.r3corda.core.crypto.X509Utilities
|
||||
import com.r3corda.core.crypto.newSecureRandom
|
||||
import com.r3corda.core.messaging.*
|
||||
import com.r3corda.core.serialization.SingletonSerializeAsToken
|
||||
@ -15,12 +16,9 @@ import org.apache.activemq.artemis.core.config.BridgeConfiguration
|
||||
import org.apache.activemq.artemis.core.config.Configuration
|
||||
import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl
|
||||
import org.apache.activemq.artemis.core.config.impl.SecurityConfiguration
|
||||
import org.apache.activemq.artemis.core.remoting.impl.invm.InVMAcceptorFactory
|
||||
import org.apache.activemq.artemis.core.remoting.impl.invm.InVMConnectorFactory
|
||||
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptorFactory
|
||||
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory
|
||||
import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants.HOST_PROP_NAME
|
||||
import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants.PORT_PROP_NAME
|
||||
import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants.*
|
||||
import org.apache.activemq.artemis.core.security.Role
|
||||
import org.apache.activemq.artemis.core.server.ActiveMQServer
|
||||
import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl
|
||||
@ -28,6 +26,7 @@ import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager
|
||||
import org.apache.activemq.artemis.spi.core.security.jaas.InVMLoginModule
|
||||
import java.math.BigInteger
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
@ -37,13 +36,12 @@ import javax.annotation.concurrent.ThreadSafe
|
||||
|
||||
// TODO: Verify that nobody can connect to us and fiddle with our config over the socket due to the secman.
|
||||
// TODO: Implement a discovery engine that can trigger builds of new connections when another node registers? (later)
|
||||
// TODO: SSL
|
||||
|
||||
/**
|
||||
* This class implements the [MessagingService] API using Apache Artemis, the successor to their ActiveMQ product.
|
||||
* Artemis is a message queue broker and here, we embed the entire server inside our own process. Nodes communicate
|
||||
* with each other using (by default) an Artemis specific protocol, but it supports other protocols like AQMP/1.0
|
||||
* as well.
|
||||
* with each other using an Artemis specific protocol, but it supports other protocols like AQMP/1.0
|
||||
* as well for interop.
|
||||
*
|
||||
* The current implementation is skeletal and lacks features like security or firewall tunnelling (that is, you must
|
||||
* be able to receive TCP connections in order to receive messages). It is good enough for local communication within
|
||||
@ -98,6 +96,10 @@ class ArtemisMessagingService(val directory: Path,
|
||||
// TODO: This is not robust and needs to be replaced by more intelligently using the message queue server.
|
||||
private val undeliveredMessages = CopyOnWriteArrayList<Message>()
|
||||
|
||||
private val keyStorePath = directory.resolve("certificates").resolve("sslkeystore.jks")
|
||||
private val trustStorePath = directory.resolve("certificates").resolve("truststore.jks")
|
||||
private val KEYSTORE_PASSWORD = "cordacadevpass" // TODO we need a proper way of managing keystores and passwords
|
||||
|
||||
init {
|
||||
require(directory.fileSystem == FileSystems.getDefault()) { "Artemis only uses the default file system" }
|
||||
}
|
||||
@ -138,9 +140,9 @@ class ArtemisMessagingService(val directory: Path,
|
||||
activeMQServer.registerActivationFailureListener { exception -> throw exception }
|
||||
activeMQServer.start()
|
||||
|
||||
// Connect to our in-memory server.
|
||||
// Connect to our server.
|
||||
clientFactory = ActiveMQClient.createServerLocatorWithoutHA(
|
||||
TransportConfiguration(InVMConnectorFactory::class.java.name)).createSessionFactory()
|
||||
tcpTransport(ConnectionDirection.OUTBOUND, myHostPort.hostText, myHostPort.port)).createSessionFactory()
|
||||
|
||||
// Create a queue on which to receive messages and set up the handler.
|
||||
val session = clientFactory.createSession()
|
||||
@ -303,8 +305,7 @@ class ArtemisMessagingService(val directory: Path,
|
||||
setConfigDirectories(config, directory)
|
||||
// We will be talking to our server purely in memory.
|
||||
config.acceptorConfigurations = setOf(
|
||||
tcpTransport(ConnectionDirection.INBOUND, "0.0.0.0", hp.port),
|
||||
TransportConfiguration(InVMAcceptorFactory::class.java.name)
|
||||
tcpTransport(ConnectionDirection.INBOUND, "0.0.0.0", hp.port)
|
||||
)
|
||||
return config
|
||||
}
|
||||
@ -316,9 +317,46 @@ class ArtemisMessagingService(val directory: Path,
|
||||
ConnectionDirection.OUTBOUND -> NettyConnectorFactory::class.java.name
|
||||
},
|
||||
mapOf(
|
||||
// Basic TCP target details
|
||||
HOST_PROP_NAME to host,
|
||||
PORT_PROP_NAME to port.toInt()
|
||||
PORT_PROP_NAME to port.toInt(),
|
||||
|
||||
// Turn on AMQP support, which needs the protoclo 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
|
||||
PROTOCOLS_PROP_NAME to "CORE,AMQP",
|
||||
|
||||
// Enable TLS transport layer with client certs and restrict to at least SHA256 in handshake
|
||||
// and AES encryption
|
||||
SSL_ENABLED_PROP_NAME to true,
|
||||
KEYSTORE_PROVIDER_PROP_NAME to "JKS",
|
||||
KEYSTORE_PATH_PROP_NAME to keyStorePath,
|
||||
KEYSTORE_PASSWORD_PROP_NAME to KEYSTORE_PASSWORD, // TODO proper management of keystores and password
|
||||
TRUSTSTORE_PROVIDER_PROP_NAME to "JKS",
|
||||
TRUSTSTORE_PATH_PROP_NAME to trustStorePath,
|
||||
TRUSTSTORE_PASSWORD_PROP_NAME to "trustpass",
|
||||
ENABLED_CIPHER_SUITES_PROP_NAME to "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256,TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256,TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_DSS_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256,TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,TLS_DHE_DSS_WITH_AES_128_GCM_SHA256",
|
||||
ENABLED_PROTOCOLS_PROP_NAME to "TLSv1.2",
|
||||
NEED_CLIENT_AUTH_PROP_NAME to true
|
||||
)
|
||||
)
|
||||
|
||||
/**
|
||||
* Strictly for dev only automatically construct a server certificate\private key signed from
|
||||
* the CA certs in Node resources.
|
||||
*/
|
||||
fun configureWithDevSSLCertificate() {
|
||||
Files.createDirectories(directory.resolve("certificates"))
|
||||
if (!Files.exists(trustStorePath)) {
|
||||
Files.copy(javaClass.classLoader.getResourceAsStream("com/r3corda/node/internal/certificates/cordatruststore.jks"),
|
||||
trustStorePath)
|
||||
}
|
||||
if (!Files.exists(keyStorePath)) {
|
||||
val caKeyStore = X509Utilities.loadKeyStore(
|
||||
javaClass.classLoader.getResourceAsStream("com/r3corda/node/internal/certificates/cordadevcakeys.jks"),
|
||||
"cordacadevpass")
|
||||
X509Utilities.createKeystoreForSSL(keyStorePath, KEYSTORE_PASSWORD, KEYSTORE_PASSWORD, caKeyStore, "cordacadevkeypass")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Binary file not shown.
Binary file not shown.
@ -56,6 +56,7 @@ class ArtemisMessagingServiceTests {
|
||||
|
||||
private fun createMessagingService(): ArtemisMessagingService {
|
||||
return ArtemisMessagingService(temporaryFolder.newFolder().toPath(), hostAndPort).apply {
|
||||
configureWithDevSSLCertificate()
|
||||
messagingNetwork = this
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user