Merge OS commit 'ab80df342ab8a7ede0539daaee9fcecd02f6aeeb' into enterprise

This commit is contained in:
Michal Kit 2018-05-02 17:32:01 +01:00
commit e5e98f3540
51 changed files with 662 additions and 79 deletions

View File

@ -94,7 +94,8 @@ class AMQPListenerTest {
PEER_USER,
clientKeyStore,
clientConfig.keyStorePassword,
clientTrustStore)
clientTrustStore,
true)
amqpClient.start()
// Should see events to show we got a valid connection
@ -163,7 +164,8 @@ class AMQPListenerTest {
PEER_USER,
clientKeyStore.internal,
"password",
clientTrustStore.internal)
clientTrustStore.internal,
true)
amqpClient.start()
val connectionEvent = connectionFollower.next()
assertEquals(false, connectionEvent.connected)

View File

@ -29,8 +29,9 @@ data class BridgeSSLConfigurationImpl(override val keyStorePassword: String,
override val trustStorePassword: String,
override val certificatesDirectory: Path = Paths.get("certificates"),
override val sslKeystore: Path = certificatesDirectory / "sslkeystore.jks",
override val trustStoreFile: Path = certificatesDirectory / "truststore.jks") : BridgeSSLConfiguration {
constructor(config: NodeSSLConfiguration) : this(config.keyStorePassword, config.trustStorePassword, config.certificatesDirectory, config.sslKeystore, config.trustStoreFile)
override val trustStoreFile: Path = certificatesDirectory / "truststore.jks",
override val crlCheckSoftFail: Boolean) : BridgeSSLConfiguration {
constructor(config: NodeSSLConfiguration) : this(config.keyStorePassword, config.trustStorePassword, config.certificatesDirectory, config.sslKeystore, config.trustStoreFile, config.crlCheckSoftFail)
}
data class BridgeOutboundConfigurationImpl(override val artemisBrokerAddress: NetworkHostAndPort,
@ -57,6 +58,7 @@ data class BridgeConfigurationImpl(
override val certificatesDirectory: Path = baseDirectory / "certificates",
override val sslKeystore: Path = certificatesDirectory / "sslkeystore.jks",
override val trustStoreFile: Path = certificatesDirectory / "truststore.jks",
override val crlCheckSoftFail: Boolean,
override val keyStorePassword: String,
override val trustStorePassword: String,
override val bridgeMode: BridgeMode,

View File

@ -60,7 +60,7 @@ class BridgeAMQPListenerServiceImpl(val conf: BridgeConfiguration,
val keyStore = loadKeyStoreAndWipeKeys(keyStoreBytes, keyStorePassword)
val trustStore = loadKeyStoreAndWipeKeys(trustStoreBytes, trustStorePassword)
val bindAddress = conf.inboundConfig!!.listeningAddress
val server = AMQPServer(bindAddress.host, bindAddress.port, PEER_USER, PEER_USER, keyStore, keyStorePrivateKeyPassword, trustStore, conf.enableAMQPPacketTrace)
val server = AMQPServer(bindAddress.host, bindAddress.port, PEER_USER, PEER_USER, keyStore, keyStorePrivateKeyPassword, trustStore, conf.crlCheckSoftFail, conf.enableAMQPPacketTrace)
onConnectSubscription = server.onConnection.subscribe(_onConnection)
onConnectAuditSubscription = server.onConnection.subscribe {
if (it.connected) {

View File

@ -82,7 +82,7 @@ class FloatControlListenerService(val conf: BridgeConfiguration,
private fun startControlListener() {
lock.withLock {
val controlServer = AMQPServer(floatControlAddress.host, floatControlAddress.port, null, null, keyStore, keyStorePrivateKeyPassword, trustStore, conf.enableAMQPPacketTrace)
val controlServer = AMQPServer(floatControlAddress.host, floatControlAddress.port, null, null, keyStore, keyStorePrivateKeyPassword, trustStore, conf.crlCheckSoftFail, conf.enableAMQPPacketTrace)
connectSubscriber = controlServer.onConnection.subscribe { onConnectToControl(it) }
receiveSubscriber = controlServer.onReceive.subscribe { onControlMessage(it) }
amqpControlServer = controlServer

View File

@ -72,7 +72,7 @@ class TunnelingBridgeReceiverService(val conf: BridgeConfiguration,
statusSubscriber = statusFollower.activeChange.subscribe {
if (it) {
val floatAddresses = conf.floatInnerConfig!!.floatAddresses
val controlClient = AMQPClient(floatAddresses, setOf(expectedCertificateSubject), null, null, controlLinkKeyStore, controLinkKeyStorePrivateKeyPassword, controlLinkTrustStore, conf.enableAMQPPacketTrace)
val controlClient = AMQPClient(floatAddresses, setOf(expectedCertificateSubject), null, null, controlLinkKeyStore, controLinkKeyStorePrivateKeyPassword, controlLinkTrustStore, conf.crlCheckSoftFail, conf.enableAMQPPacketTrace)
connectSubscriber = controlClient.onConnection.subscribe { onConnectToControl(it) }
receiveSubscriber = controlClient.onReceive.subscribe { onFloatMessage(it) }
amqpControlClient = controlClient

View File

@ -11,4 +11,5 @@ keyStorePassword = "cordacadevpass"
trustStorePassword = "trustpass"
enableAMQPPacketTrace = false
artemisReconnectionInterval = 5000
politeShutdownPeriod = 1000
politeShutdownPeriod = 1000
crlCheckSoftFail = true

View File

@ -15,4 +15,4 @@ outboundConfig : {
inboundConfig : {
listeningAddress = "0.0.0.0:10005"
}
networkParametersPath = network-parameters
networkParametersPath = network-parameters

View File

@ -16,4 +16,4 @@ floatInnerConfig : {
floatAddresses = [ "localhost:13005" ]
expectedCertificateSubject = "O=Bank A, L=London, C=GB"
}
networkParametersPath = network-parameters
networkParametersPath = network-parameters

View File

@ -15,4 +15,4 @@ floatOuterConfig : {
floatAddress = "localhost:13005"
expectedCertificateSubject = "O=Bank A, L=London, C=GB"
}
networkParametersPath = network-parameters
networkParametersPath = network-parameters

View File

@ -16,4 +16,4 @@ outboundConfig : {
inboundConfig : {
listeningAddress = "0.0.0.0:10005"
}
networkParametersPath = network-parameters
networkParametersPath = network-parameters

View File

@ -15,4 +15,4 @@ floatOuterConfig : {
floatAddress = "localhost:12005"
expectedCertificateSubject = "O=Bank A, L=London, C=GB"
}
networkParametersPath = network-parameters
networkParametersPath = network-parameters

View File

@ -15,6 +15,7 @@ outboundConfig : {
trustStorePassword = "outboundtrustpassword"
sslKeystore = "outboundcerts/outboundkeys.jks"
trustStoreFile = "outboundcerts/outboundtrust.jks"
crlCheckSoftFail = true
}
}
floatInnerConfig : {
@ -25,6 +26,7 @@ floatInnerConfig : {
trustStorePassword = "tunneltrustpassword"
sslKeystore = "tunnelcerts/tunnelkeys.jks"
trustStoreFile = "tunnelcerts/tunneltrust.jks"
crlCheckSoftFail = true
}
}
networkParametersPath = network-parameters
networkParametersPath = network-parameters

View File

@ -15,6 +15,7 @@ inboundConfig : {
trustStorePassword = "inboundtrustpassword"
sslKeystore = "inboundcerts/inboundkeys.jks"
trustStoreFile = "inboundcerts/inboundtrust.jks"
crlCheckSoftFail = true
}
}
floatOuterConfig : {
@ -25,6 +26,7 @@ floatOuterConfig : {
trustStorePassword = "tunneltrustpassword"
sslKeystore = "tunnelcerts/tunnelkeys.jks"
trustStoreFile = "tunnelcerts/tunneltrust.jks"
crlCheckSoftFail = true
}
}
networkParametersPath = network-parameters
networkParametersPath = network-parameters

View File

@ -14,4 +14,4 @@ outboundConfig : {
inboundConfig : {
listeningAddress = "0.0.0.0:10005"
}
networkParametersPath = network-parameters
networkParametersPath = network-parameters

View File

@ -15,4 +15,4 @@ floatInnerConfig : {
floatAddresses = [ "localhost:12005" ]
expectedCertificateSubject = "O=Bank A, L=London, C=GB"
}
networkParametersPath = network-parameters
networkParametersPath = network-parameters

View File

@ -15,4 +15,4 @@ floatOuterConfig : {
floatAddress = "localhost:12005"
expectedCertificateSubject = "O=Bank A, L=London, C=GB"
}
networkParametersPath = network-parameters
networkParametersPath = network-parameters

View File

@ -21,3 +21,4 @@ inboundConfig : {
listeningAddress = "0.0.0.0:10005"
}
networkParametersPath = network-parameters
crlCheckSoftFail = true

View File

@ -21,3 +21,4 @@ inboundConfig : {
listeningAddress = "0.0.0.0:10005"
}
networkParametersPath = network-parameters
crlCheckSoftFail = true

View File

@ -21,3 +21,4 @@ inboundConfig : {
listeningAddress = "0.0.0.0:10005"
}
networkParametersPath = network-parameters
crlCheckSoftFail = true

View File

@ -20,4 +20,4 @@ outboundConfig : {
inboundConfig : {
listeningAddress = "0.0.0.0:10005"
}
networkParametersPath = network-parameters
networkParametersPath = network-parameters

View File

@ -360,13 +360,14 @@ fun ExecutorService.join() {
}
}
fun CertPath.validate(trustAnchor: TrustAnchor): PKIXCertPathValidatorResult {
val parameters = PKIXParameters(setOf(trustAnchor)).apply { isRevocationEnabled = false }
// TODO: Currently the certificate revocation status is not handled here. Nowhere in the code the second parameter is used. Consider adding the support in the future.
fun CertPath.validate(trustAnchor: TrustAnchor, checkRevocation: Boolean = false): PKIXCertPathValidatorResult {
val parameters = PKIXParameters(setOf(trustAnchor)).apply { isRevocationEnabled = checkRevocation }
try {
return CertPathValidator.getInstance("PKIX").validate(this, parameters) as PKIXCertPathValidatorResult
} catch (e: CertPathValidatorException) {
throw CertPathValidatorException(
"""Cert path failed to validate against trust anchor.
"""Cert path failed to validate.
Reason: ${e.reason}
Offending cert index: ${e.index}
Cert path: $this

View File

@ -67,6 +67,11 @@ absolute path to the node's base directory.
.. note:: Longer term these keys will be managed in secure hardware devices.
:crlCheckSoftFail: This is a boolean flag that when enabled (i.e. `true` value is set) the certificate revocation list (CRL) checking will use the soft fail mode.
The soft fail mode allows the revocation check to succeed if the revocation status cannot be determined because of a network error.
If this parameter is set to `false` the rigorous CRL checking takes place, meaning that each certificate in the
certificate path being checked needs to have the CRL distribution point extension set and pointing to a URL serving a valid CRL.
.. _database_properties_ref:
:database: This section is used to configure JDBC and Hibernate related properties:

View File

@ -1,6 +1,7 @@
myLegalName : "O=Bank A,L=London,C=GB"
keyStorePassword : "cordacadevpass"
trustStorePassword : "trustpass"
crlCheckSoftFail: true
dataSourceProperties : {
dataSourceClassName : org.h2.jdbcx.JdbcDataSource
dataSource.url : "jdbc:h2:file:"${baseDirectory}"/persistence"

View File

@ -47,6 +47,7 @@ fun RegistrationOption.runRegistration() {
val parent = configFile.parent
override val certificatesDirectory: Path = if (parent != null) parent / "certificates" else Paths.get("certificates")
override val nodeKeystore: Path get() = config.keystorePath ?: certificatesDirectory/"notaryidentitykeystore.jks"
override val crlCheckSoftFail: Boolean = config.crlCheckSoftFail
}
NetworkRegistrationHelper(sslConfig,
@ -66,4 +67,5 @@ data class NotaryRegistrationConfig(val legalName: CordaX500Name,
val keyStorePassword: String?,
val networkRootTrustStorePassword: String?,
val trustStorePassword: String?,
val keystorePath: Path?)
val keystorePath: Path?,
val crlCheckSoftFail: Boolean)

View File

@ -13,9 +13,9 @@ package com.r3.corda.networkmanage.registration
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.CertRole
import net.corda.nodeapi.internal.config.parseAs
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import java.nio.file.Paths
@ -37,6 +37,7 @@ keystorePath = "notaryidentitykeystore.jks"
networkRootTrustStorePassword = "password"
keyStorePassword = "password"
trustStorePassword = "password"
crlCheckSoftFail = true
""".trimIndent()
val config = ConfigFactory.parseString(testConfig, ConfigParseOptions.defaults().setAllowMissing(false))
@ -49,5 +50,6 @@ trustStorePassword = "password"
assertEquals(Paths.get("networkRootTrustStore.jks"), config.networkRootTrustStorePath)
assertEquals("password", config.networkRootTrustStorePassword)
assertEquals(Paths.get("notaryidentitykeystore.jks"), config.keystorePath)
assertTrue(config.crlCheckSoftFail)
}
}

View File

@ -45,6 +45,7 @@ object DevIdentityGenerator {
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
}
nodeSslConfig.certificatesDirectory.createDirectories()

View File

@ -57,6 +57,7 @@ class AMQPBridgeManager(config: NodeSSLConfiguration, private val socksProxyConf
private val keyStorePrivateKeyPassword: String = config.keyStorePassword
private val trustStore = config.loadTrustStore().internal
private var artemis: ArtemisSessionProvider? = null
private val crlCheckSoftFail: Boolean = config.crlCheckSoftFail
constructor(config: NodeSSLConfiguration, p2pAddress: NetworkHostAndPort, maxMessageSize: Int, socksProxyConfig: SocksProxyConfig? = null) : this(config, socksProxyConfig, { ArtemisMessagingClient(config, p2pAddress, maxMessageSize) })
@ -78,6 +79,7 @@ class AMQPBridgeManager(config: NodeSSLConfiguration, private val socksProxyConf
keyStore: KeyStore,
keyStorePrivateKeyPassword: String,
trustStore: KeyStore,
crlCheckSoftFail: Boolean,
sharedEventGroup: EventLoopGroup,
socksProxyConfig: SocksProxyConfig?,
private val artemis: ArtemisSessionProvider) {
@ -87,7 +89,7 @@ class AMQPBridgeManager(config: NodeSSLConfiguration, private val socksProxyConf
private val log = LoggerFactory.getLogger("$bridgeName:${legalNames.first()}")
val amqpClient = AMQPClient(listOf(target), legalNames, PEER_USER, PEER_USER, keyStore, keyStorePrivateKeyPassword, trustStore, sharedThreadPool = sharedEventGroup, socksProxyConfig = socksProxyConfig)
val amqpClient = AMQPClient(listOf(target), legalNames, PEER_USER, PEER_USER, keyStore, keyStorePrivateKeyPassword, trustStore, crlCheckSoftFail, sharedThreadPool = sharedEventGroup, socksProxyConfig = socksProxyConfig)
val bridgeName: String get() = getBridgeName(queueName, target)
private val lock = ReentrantLock() // lock to serialise session level access
private var session: ClientSession? = null
@ -181,7 +183,7 @@ class AMQPBridgeManager(config: NodeSSLConfiguration, private val socksProxyConf
if (bridgeExists(getBridgeName(queueName, target))) {
return
}
val newBridge = AMQPBridge(queueName, target, legalNames, keyStore, keyStorePrivateKeyPassword, trustStore, sharedEventLoopGroup!!, socksProxyConfig, artemis!!)
val newBridge = AMQPBridge(queueName, target, legalNames, keyStore, keyStorePrivateKeyPassword, trustStore, crlCheckSoftFail, sharedEventLoopGroup!!, socksProxyConfig, artemis!!)
lock.withLock {
bridgeNameToBridgeMap[newBridge.bridgeName] = newBridge
}

View File

@ -22,6 +22,7 @@ interface SSLConfiguration {
// 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
fun loadTrustStore(createNew: Boolean = false): X509KeyStore {
return X509KeyStore.fromFile(trustStoreFile, trustStorePassword, createNew)

View File

@ -158,7 +158,7 @@ object X509Utilities {
* @param crlDistPoint CRL distribution point.
* @param crlIssuer X500Name of the CRL issuer.
*/
private fun createPartialCertificate(certificateType: CertificateType,
fun createPartialCertificate(certificateType: CertificateType,
issuer: X500Principal,
issuerPublicKey: PublicKey,
subject: X500Principal,

View File

@ -64,6 +64,7 @@ class AMQPClient(val targets: List<NetworkHostAndPort>,
private val keyStore: KeyStore,
private val keyStorePrivateKeyPassword: String,
private val trustStore: KeyStore,
private val crlCheckSoftFail: Boolean,
private val trace: Boolean = false,
private val sharedThreadPool: EventLoopGroup? = null,
private val socksProxyConfig: SocksProxyConfig? = null) : AutoCloseable {
@ -129,7 +130,7 @@ class AMQPClient(val targets: List<NetworkHostAndPort>,
init {
keyManagerFactory.init(parent.keyStore, parent.keyStorePrivateKeyPassword.toCharArray())
trustManagerFactory.init(parent.trustStore)
trustManagerFactory.init(initialiseTrustStoreAndEnableCrlChecking(parent.trustStore, parent.crlCheckSoftFail))
}
override fun initChannel(ch: SocketChannel) {
@ -178,9 +179,7 @@ class AMQPClient(val targets: List<NetworkHostAndPort>,
private fun restart() {
val bootstrap = Bootstrap()
// TODO Needs more configuration control when we profile. e.g. to use EPOLL on Linux
bootstrap.group(workerGroup).
channel(NioSocketChannel::class.java).
handler(ClientChannelInitializer(this))
bootstrap.group(workerGroup).channel(NioSocketChannel::class.java).handler(ClientChannelInitializer(this))
currentTarget = targets[targetIndex]
val clientFuture = bootstrap.connect(currentTarget.host, currentTarget.port)
clientFuture.addListener(connectListener)

View File

@ -50,6 +50,7 @@ class AMQPServer(val hostName: String,
private val keyStore: KeyStore,
private val keyStorePrivateKeyPassword: CharArray,
private val trustStore: KeyStore,
private val crlCheckSoftFail: Boolean,
private val trace: Boolean = false) : AutoCloseable {
companion object {
@ -76,7 +77,8 @@ class AMQPServer(val hostName: String,
keyStore: KeyStore,
keyStorePrivateKeyPassword: String,
trustStore: KeyStore,
trace: Boolean = false) : this(hostName, port, userName, password, keyStore, keyStorePrivateKeyPassword.toCharArray(), trustStore, trace)
crlCheckSoftFail: Boolean,
trace: Boolean = false) : this(hostName, port, userName, password, keyStore, keyStorePrivateKeyPassword.toCharArray(), trustStore, crlCheckSoftFail, trace)
private class ServerChannelInitializer(val parent: AMQPServer) : ChannelInitializer<SocketChannel>() {
private val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
@ -84,7 +86,7 @@ class AMQPServer(val hostName: String,
init {
keyManagerFactory.init(parent.keyStore, parent.keyStorePrivateKeyPassword)
trustManagerFactory.init(parent.trustStore)
trustManagerFactory.init(initialiseTrustStoreAndEnableCrlChecking(parent.trustStore, parent.crlCheckSoftFail))
}
override fun initChannel(ch: SocketChannel) {
@ -118,11 +120,7 @@ class AMQPServer(val hostName: String,
val server = ServerBootstrap()
// TODO Needs more configuration control when we profile. e.g. to use EPOLL on Linux
server.group(bossGroup, workerGroup).
channel(NioServerSocketChannel::class.java).
option(ChannelOption.SO_BACKLOG, 100).
handler(LoggingHandler(LogLevel.INFO)).
childHandler(ServerChannelInitializer(this))
server.group(bossGroup, workerGroup).channel(NioServerSocketChannel::class.java).option(ChannelOption.SO_BACKLOG, 100).handler(LoggingHandler(LogLevel.INFO)).childHandler(ServerChannelInitializer(this))
log.info("Try to bind $port")
val channelFuture = server.bind(hostName, port).sync() // block/throw here as better to know we failed to claim port than carry on

View File

@ -13,10 +13,14 @@ package net.corda.nodeapi.internal.protonwrapper.netty
import io.netty.handler.ssl.SslHandler
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.nodeapi.ArtemisTcpTransport
import java.security.KeyStore
import java.security.SecureRandom
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManagerFactory
import java.security.cert.CertPathBuilder
import java.security.cert.PKIXBuilderParameters
import java.security.cert.PKIXRevocationChecker
import java.security.cert.X509CertSelector
import java.util.*
import javax.net.ssl.*
internal fun createClientSslHelper(target: NetworkHostAndPort,
keyManagerFactory: KeyManagerFactory,
@ -46,4 +50,22 @@ internal fun createServerSslHelper(keyManagerFactory: KeyManagerFactory,
sslEngine.enabledCipherSuites = ArtemisTcpTransport.CIPHER_SUITES.toTypedArray()
sslEngine.enableSessionCreation = true
return SslHandler(sslEngine)
}
}
internal fun initialiseTrustStoreAndEnableCrlChecking(trustStore: KeyStore, crlCheckSoftFail: Boolean): ManagerFactoryParameters {
val certPathBuilder = CertPathBuilder.getInstance("PKIX")
val revocationChecker = certPathBuilder.revocationChecker as PKIXRevocationChecker
revocationChecker.options = EnumSet.of(
// Prefer CRL over OCSP
PKIXRevocationChecker.Option.PREFER_CRLS,
// Don't fall back to OCSP checking
PKIXRevocationChecker.Option.NO_FALLBACK)
if (crlCheckSoftFail) {
// Allow revocation check to succeed if the revocation status cannot be determined for one of
// 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())
pkixParams.addCertPathChecker(revocationChecker)
return CertPathTrustManagerParameters(pkixParams)
}

View File

@ -193,6 +193,7 @@ class X509UtilitiesTest {
override val certificatesDirectory = tempFolder.root.toPath()
override val keyStorePassword = "serverstorepass"
override val trustStorePassword = "trustpass"
override val crlCheckSoftFail: Boolean = true
}
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
@ -228,6 +229,7 @@ class X509UtilitiesTest {
override val certificatesDirectory = tempFolder.root.toPath()
override val keyStorePassword = "serverstorepass"
override val trustStorePassword = "trustpass"
override val crlCheckSoftFail: Boolean = true
}
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()

View File

@ -55,6 +55,7 @@ class NodeKeystoreCheckTest : IntegrationTest() {
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)

View File

@ -89,7 +89,7 @@ class AMQPBridgeTest {
fun formatMessage(expected: String, actual: Int, received: List<Int>): String {
return "Expected message with id $expected, got $actual, previous message receive sequence: " +
"${received.joinToString(", ", "[", "]")}."
"${received.joinToString(", ", "[", "]")}."
}
val received1 = receive.next()
@ -251,6 +251,7 @@ class AMQPBridgeTest {
doReturn(temporaryFolder.root.toPath() / "artemis").whenever(it).baseDirectory
doReturn(ALICE_NAME).whenever(it).myLegalName
doReturn("trustpass").whenever(it).trustStorePassword
doReturn(true).whenever(it).crlCheckSoftFail
doReturn("cordacadevpass").whenever(it).keyStorePassword
doReturn(artemisAddress).whenever(it).p2pAddress
doReturn(null).whenever(it).jmxMonitoringHttpPort
@ -311,6 +312,7 @@ class AMQPBridgeTest {
serverConfig.loadSslKeyStore().internal,
serverConfig.keyStorePassword,
serverConfig.loadTrustStore().internal,
crlCheckSoftFail = true,
trace = true
)
}

View File

@ -0,0 +1,473 @@
package net.corda.node.amqp
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.crypto.Crypto
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.core.utilities.days
import net.corda.core.utilities.minutes
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.ArtemisMessagingComponent.Companion.PEER_USER
import net.corda.nodeapi.internal.config.SSLConfiguration
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.AMQPServer
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.CHARLIE_NAME
import net.corda.testing.core.freePort
import net.corda.testing.internal.DEV_INTERMEDIATE_CA
import net.corda.testing.internal.DEV_ROOT_CA
import net.corda.testing.internal.rigorousMock
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.*
import org.bouncycastle.cert.jcajce.JcaX509CRLConverter
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils
import org.bouncycastle.cert.jcajce.JcaX509v2CRLBuilder
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.ServerConnector
import org.eclipse.jetty.server.handler.HandlerCollection
import org.eclipse.jetty.servlet.ServletContextHandler
import org.eclipse.jetty.servlet.ServletHolder
import org.glassfish.jersey.server.ResourceConfig
import org.glassfish.jersey.servlet.ServletContainer
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.io.Closeable
import java.math.BigInteger
import java.net.InetSocketAddress
import java.security.KeyPair
import java.security.PrivateKey
import java.security.Security
import java.security.cert.X509CRL
import java.security.cert.X509Certificate
import java.util.*
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.Produces
import javax.ws.rs.core.Response
import kotlin.test.assertEquals
class CertificateRevocationListNodeTests {
@Rule
@JvmField
val temporaryFolder = TemporaryFolder()
private val ROOT_CA = DEV_ROOT_CA
private lateinit var INTERMEDIATE_CA: CertificateAndKeyPair
private val serverPort = freePort()
private lateinit var server: CrlServer
private val revokedNodeCerts: MutableList<BigInteger> = mutableListOf()
private val revokedIntermediateCerts: MutableList<BigInteger> = mutableListOf()
private abstract class AbstractNodeConfiguration : NodeConfiguration
@Before
fun setUp() {
Security.addProvider(BouncyCastleProvider())
revokedNodeCerts.clear()
server = CrlServer(NetworkHostAndPort("localhost", 0))
server.start()
INTERMEDIATE_CA = CertificateAndKeyPair(replaceCrlDistPointCaCertificate(
DEV_INTERMEDIATE_CA.certificate,
CertificateType.INTERMEDIATE_CA,
ROOT_CA.keyPair,
"http://${server.hostAndPort}/crl/intermediate.crl"), DEV_INTERMEDIATE_CA.keyPair)
}
@After
fun tearDown() {
server.close()
revokedNodeCerts.clear()
}
@Test
fun `Simple AMPQ Client to Server connection works`() {
val crlCheckSoftFail = true
val (amqpServer, _) = createServer(serverPort, crlCheckSoftFail = crlCheckSoftFail)
amqpServer.use {
amqpServer.start()
val receiveSubs = amqpServer.onReceive.subscribe {
assertEquals(BOB_NAME.toString(), it.sourceLegalName)
assertEquals(P2P_PREFIX + "Test", it.topic)
assertEquals("Test", String(it.payload))
it.complete(true)
}
val (amqpClient, _) = createClient(serverPort, crlCheckSoftFail)
amqpClient.use {
val serverConnected = amqpServer.onConnection.toFuture()
val clientConnected = amqpClient.onConnection.toFuture()
amqpClient.start()
val serverConnect = serverConnected.get()
assertEquals(true, serverConnect.connected)
val clientConnect = clientConnected.get()
assertEquals(true, clientConnect.connected)
val msg = amqpClient.createMessage("Test".toByteArray(),
P2P_PREFIX + "Test",
ALICE_NAME.toString(),
emptyMap())
amqpClient.write(msg)
assertEquals(MessageStatus.Acknowledged, msg.onComplete.get())
receiveSubs.unsubscribe()
}
}
}
@Test
fun `AMPQ Client to Server connection fails when client's certificate is revoked`() {
val crlCheckSoftFail = true
val (amqpServer, _) = createServer(serverPort, crlCheckSoftFail = crlCheckSoftFail)
amqpServer.use {
amqpServer.start()
amqpServer.onReceive.subscribe {
it.complete(true)
}
val (amqpClient, clientCert) = createClient(serverPort, crlCheckSoftFail)
revokedNodeCerts.add(clientCert.serialNumber)
amqpClient.use {
val serverConnected = amqpServer.onConnection.toFuture()
amqpClient.onConnection.toFuture()
amqpClient.start()
val serverConnect = serverConnected.get()
assertEquals(false, serverConnect.connected)
}
}
}
@Test
fun `AMPQ Client to Server connection fails when servers's certificate is revoked`() {
val crlCheckSoftFail = true
val (amqpServer, serverCert) = createServer(serverPort, crlCheckSoftFail = crlCheckSoftFail)
revokedNodeCerts.add(serverCert.serialNumber)
amqpServer.use {
amqpServer.start()
amqpServer.onReceive.subscribe {
it.complete(true)
}
val (amqpClient, _) = createClient(serverPort, crlCheckSoftFail)
amqpClient.use {
val serverConnected = amqpServer.onConnection.toFuture()
amqpClient.onConnection.toFuture()
amqpClient.start()
val serverConnect = serverConnected.get()
assertEquals(false, serverConnect.connected)
}
}
}
@Test
fun `AMPQ Client to Server connection fails when servers's certificate is revoked and soft fail is enabled`() {
val crlCheckSoftFail = true
val (amqpServer, serverCert) = createServer(serverPort, crlCheckSoftFail = crlCheckSoftFail)
revokedNodeCerts.add(serverCert.serialNumber)
amqpServer.use {
amqpServer.start()
amqpServer.onReceive.subscribe {
it.complete(true)
}
val (amqpClient, _) = createClient(serverPort, crlCheckSoftFail)
amqpClient.use {
val serverConnected = amqpServer.onConnection.toFuture()
amqpClient.onConnection.toFuture()
amqpClient.start()
val serverConnect = serverConnected.get()
assertEquals(false, serverConnect.connected)
}
}
}
@Test
fun `AMPQ Client to Server connection succeeds when CRL cannot be obtained and soft fail is enabled`() {
val crlCheckSoftFail = true
val (amqpServer, serverCert) = createServer(
serverPort,
crlCheckSoftFail = crlCheckSoftFail,
nodeCrlDistPoint = "http://${server.hostAndPort}/crl/invalid.crl")
amqpServer.use {
amqpServer.start()
amqpServer.onReceive.subscribe {
it.complete(true)
}
val (amqpClient, _) = createClient(
serverPort,
crlCheckSoftFail,
nodeCrlDistPoint = "http://${server.hostAndPort}/crl/invalid.crl")
amqpClient.use {
val serverConnected = amqpServer.onConnection.toFuture()
amqpClient.onConnection.toFuture()
amqpClient.start()
val serverConnect = serverConnected.get()
assertEquals(true, serverConnect.connected)
}
}
}
@Test
fun `Revocation status chceck fails when the CRL distribution point is not set and soft fail is disabled`() {
val crlCheckSoftFail = false
val (amqpServer, _) = createServer(
serverPort,
crlCheckSoftFail = crlCheckSoftFail,
tlsCrlDistPoint = null)
amqpServer.use {
amqpServer.start()
amqpServer.onReceive.subscribe {
it.complete(true)
}
val (amqpClient, _) = createClient(
serverPort,
crlCheckSoftFail,
tlsCrlDistPoint = null)
amqpClient.use {
val serverConnected = amqpServer.onConnection.toFuture()
amqpClient.onConnection.toFuture()
amqpClient.start()
val serverConnect = serverConnected.get()
assertEquals(false, serverConnect.connected)
}
}
}
@Test
fun `Revocation status chceck succeds when the CRL distribution point is not set and soft fail is enabled`() {
val crlCheckSoftFail = true
val (amqpServer, _) = createServer(
serverPort,
crlCheckSoftFail = crlCheckSoftFail,
tlsCrlDistPoint = null)
amqpServer.use {
amqpServer.start()
amqpServer.onReceive.subscribe {
it.complete(true)
}
val (amqpClient, _) = createClient(
serverPort,
crlCheckSoftFail,
tlsCrlDistPoint = null)
amqpClient.use {
val serverConnected = amqpServer.onConnection.toFuture()
amqpClient.onConnection.toFuture()
amqpClient.start()
val serverConnect = serverConnected.get()
assertEquals(true, serverConnect.connected)
}
}
}
private fun createClient(targetPort: Int,
crlCheckSoftFail: Boolean,
nodeCrlDistPoint: String = "http://${server.hostAndPort}/crl/node.crl",
tlsCrlDistPoint: String? = "http://${server.hostAndPort}/crl/empty.crl"): Pair<AMQPClient, X509Certificate> {
val clientConfig = rigorousMock<AbstractNodeConfiguration>().also {
doReturn(temporaryFolder.root.toPath() / "client").whenever(it).baseDirectory
doReturn(BOB_NAME).whenever(it).myLegalName
doReturn("trustpass").whenever(it).trustStorePassword
doReturn("cordacadevpass").whenever(it).keyStorePassword
doReturn(crlCheckSoftFail).whenever(it).crlCheckSoftFail
}
clientConfig.configureWithDevSSLCertificate()
val nodeCert = clientConfig.recreateNodeCaAndTlsCertificates(nodeCrlDistPoint, tlsCrlDistPoint)
val clientTruststore = clientConfig.loadTrustStore().internal
val clientKeystore = clientConfig.loadSslKeyStore().internal
return Pair(AMQPClient(
listOf(NetworkHostAndPort("localhost", targetPort)),
setOf(ALICE_NAME, CHARLIE_NAME),
PEER_USER,
PEER_USER,
clientKeystore,
clientConfig.keyStorePassword,
clientTruststore,
crlCheckSoftFail), nodeCert)
}
private fun createServer(port: Int, name: CordaX500Name = ALICE_NAME,
crlCheckSoftFail: Boolean,
nodeCrlDistPoint: String = "http://${server.hostAndPort}/crl/node.crl",
tlsCrlDistPoint: String? = "http://${server.hostAndPort}/crl/empty.crl"): Pair<AMQPServer, X509Certificate> {
val serverConfig = rigorousMock<AbstractNodeConfiguration>().also {
doReturn(temporaryFolder.root.toPath() / "server").whenever(it).baseDirectory
doReturn(name).whenever(it).myLegalName
doReturn("trustpass").whenever(it).trustStorePassword
doReturn("cordacadevpass").whenever(it).keyStorePassword
doReturn(crlCheckSoftFail).whenever(it).crlCheckSoftFail
}
serverConfig.configureWithDevSSLCertificate()
val nodeCert = serverConfig.recreateNodeCaAndTlsCertificates(nodeCrlDistPoint, tlsCrlDistPoint)
val serverTruststore = serverConfig.loadTrustStore().internal
val serverKeystore = serverConfig.loadSslKeyStore().internal
return Pair(AMQPServer(
"0.0.0.0",
port,
PEER_USER,
PEER_USER,
serverKeystore,
serverConfig.keyStorePassword,
serverTruststore,
crlCheckSoftFail), nodeCert)
}
private fun SSLConfiguration.recreateNodeCaAndTlsCertificates(nodeCaCrlDistPoint: String, tlsCrlDistPoint: String?): X509Certificate {
val nodeKeyStore = loadNodeKeyStore()
val (nodeCert, nodeKeys) = nodeKeyStore.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()
nodeKeyStore.update {
setPrivateKey(X509Utilities.CORDA_CLIENT_CA, nodeKeys.private, nodeCertChain)
}
val sslKeyStore = loadSslKeyStore()
val (tlsCert, tlsKeys) = sslKeyStore.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()
sslKeyStore.update {
setPrivateKey(X509Utilities.CORDA_CLIENT_TLS, tlsKeys.private, sslCertChain)
}
return newNodeCert
}
private fun replaceCrlDistPointCaCertificate(currentCaCert: X509Certificate, certType: CertificateType, issuerKeyPair: KeyPair, crlDistPoint: String?, crlIssuer: X500Name? = null): X509Certificate {
val signatureScheme = Crypto.findSignatureScheme(issuerKeyPair.private)
val provider = Crypto.findProvider(signatureScheme.providerName)
val issuerSigner = ContentSignerBuilder.build(signatureScheme, issuerKeyPair.private, provider)
val builder = X509Utilities.createPartialCertificate(
certType,
currentCaCert.issuerX500Principal,
issuerKeyPair.public,
currentCaCert.subjectX500Principal,
currentCaCert.publicKey,
Pair(Date(System.currentTimeMillis() - 5.minutes.toMillis()), Date(System.currentTimeMillis() + 10.days.toMillis())),
null
)
crlDistPoint?.let {
val distPointName = DistributionPointName(GeneralNames(GeneralName(GeneralName.uniformResourceIdentifier, it)))
val crlIssuerGeneralNames = crlIssuer?.let {
GeneralNames(GeneralName(crlIssuer))
}
val distPoint = DistributionPoint(distPointName, null, crlIssuerGeneralNames)
builder.addExtension(Extension.cRLDistributionPoints, false, CRLDistPoint(arrayOf(distPoint)))
}
return builder.build(issuerSigner).toJca()
}
@Path("crl")
inner class CrlServlet(private val server: CrlServer) {
private val SIGNATURE_ALGORITHM = "SHA256withECDSA"
private val NODE_CRL = "node.crl"
private val INTEMEDIATE_CRL = "intermediate.crl"
private val EMPTY_CRL = "empty.crl"
@GET
@Path("node.crl")
@Produces("application/pkcs7-crl")
fun getNodeCRL(): Response {
return Response.ok(createRevocationList(
INTERMEDIATE_CA.certificate,
INTERMEDIATE_CA.keyPair.private,
NODE_CRL,
false,
*revokedNodeCerts.toTypedArray()).encoded).build()
}
@GET
@Path("intermediate.crl")
@Produces("application/pkcs7-crl")
fun getIntermediateCRL(): Response {
return Response.ok(createRevocationList(
ROOT_CA.certificate,
ROOT_CA.keyPair.private,
INTEMEDIATE_CRL,
false,
*revokedIntermediateCerts.toTypedArray()).encoded).build()
}
@GET
@Path("empty.crl")
@Produces("application/pkcs7-crl")
fun getEmptyCRL(): Response {
return Response.ok(createRevocationList(
ROOT_CA.certificate,
ROOT_CA.keyPair.private,
EMPTY_CRL, true).encoded).build()
}
private fun createRevocationList(caCertificate: X509Certificate,
caPrivateKey: PrivateKey,
endpoint: String,
indirect: Boolean,
vararg serialNumbers: BigInteger): X509CRL {
println("Generating CRL for $endpoint")
val builder = JcaX509v2CRLBuilder(caCertificate.subjectX500Principal, Date(System.currentTimeMillis() - 1.minutes.toMillis()))
val extensionUtils = JcaX509ExtensionUtils()
builder.addExtension(Extension.authorityKeyIdentifier,
false, extensionUtils.createAuthorityKeyIdentifier(caCertificate))
val issuingDistPointName = GeneralName(
GeneralName.uniformResourceIdentifier,
"http://${server.hostAndPort.host}:${server.hostAndPort.port}/crl/$endpoint")
// This is required and needs to match the certificate settings with respect to being indirect
val issuingDistPoint = IssuingDistributionPoint(DistributionPointName(GeneralNames(issuingDistPointName)), indirect, false)
builder.addExtension(Extension.issuingDistributionPoint, true, issuingDistPoint)
builder.setNextUpdate(Date(System.currentTimeMillis() + 1.seconds.toMillis()))
serialNumbers.forEach {
builder.addCRLEntry(it, Date(System.currentTimeMillis() - 10.minutes.toMillis()), ReasonFlags.certificateHold)
}
val signer = JcaContentSignerBuilder(SIGNATURE_ALGORITHM).setProvider(BouncyCastleProvider.PROVIDER_NAME).build(caPrivateKey)
return JcaX509CRLConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCRL(builder.build(signer))
}
}
inner class CrlServer(hostAndPort: NetworkHostAndPort) : Closeable {
private val server: Server = Server(InetSocketAddress(hostAndPort.host, hostAndPort.port)).apply {
handler = HandlerCollection().apply {
addHandler(buildServletContextHandler())
}
}
val hostAndPort: NetworkHostAndPort
get() = server.connectors.mapNotNull { it as? ServerConnector }
.map { NetworkHostAndPort(it.host, it.localPort) }
.first()
override fun close() {
println("Shutting down network management web services...")
server.stop()
server.join()
}
fun start() {
server.start()
println("Network management web services started on $hostAndPort")
}
private fun buildServletContextHandler(): ServletContextHandler {
val crlServer = this
return ServletContextHandler().apply {
contextPath = "/"
val resourceConfig = ResourceConfig().apply {
register(CrlServlet(crlServer))
}
val jerseyServlet = ServletHolder(ServletContainer(resourceConfig)).apply { initOrder = 0 }
addServlet(jerseyServlet, "/*")
}
}
}
}

View File

@ -79,6 +79,21 @@ 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"), 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"))
@ -233,6 +248,7 @@ class ProtonWrapperTests {
doReturn(null).whenever(it).jmxMonitoringHttpPort
doReturn(emptyList<CertChainPolicyConfig>()).whenever(it).certificateChainCheckPolicies
doReturn(EnterpriseConfiguration(MutualExclusionConfiguration(false, "", 20000, 40000))).whenever(it).enterpriseConfiguration
doReturn(true).whenever(it).crlCheckSoftFail
}
artemisConfig.configureWithDevSSLCertificate()
@ -249,6 +265,7 @@ class ProtonWrapperTests {
doReturn(BOB_NAME).whenever(it).myLegalName
doReturn("trustpass").whenever(it).trustStorePassword
doReturn("cordacadevpass").whenever(it).keyStorePassword
doReturn(true).whenever(it).crlCheckSoftFail
}
clientConfig.configureWithDevSSLCertificate()
@ -256,14 +273,15 @@ class ProtonWrapperTests {
val clientKeystore = clientConfig.loadSslKeyStore().internal
return AMQPClient(
listOf(NetworkHostAndPort("localhost", serverPort),
NetworkHostAndPort("localhost", serverPort2),
NetworkHostAndPort("localhost", artemisPort)),
NetworkHostAndPort("localhost", serverPort2),
NetworkHostAndPort("localhost", artemisPort)),
setOf(ALICE_NAME, CHARLIE_NAME),
PEER_USER,
PEER_USER,
clientKeystore,
clientConfig.keyStorePassword,
clientTruststore, true)
clientTruststore,
true)
}
private fun createSharedThreadsClient(sharedEventGroup: EventLoopGroup, id: Int): AMQPClient {
@ -272,6 +290,7 @@ class ProtonWrapperTests {
doReturn(CordaX500Name(null, "client $id", "Corda", "London", null, "GB")).whenever(it).myLegalName
doReturn("trustpass").whenever(it).trustStorePassword
doReturn("cordacadevpass").whenever(it).keyStorePassword
doReturn(true).whenever(it).crlCheckSoftFail
}
clientConfig.configureWithDevSSLCertificate()
@ -284,15 +303,18 @@ class ProtonWrapperTests {
PEER_USER,
clientKeystore,
clientConfig.keyStorePassword,
clientTruststore, true, sharedEventGroup)
clientTruststore,
true,
sharedThreadPool = sharedEventGroup)
}
private fun createServer(port: Int, name: CordaX500Name = ALICE_NAME): AMQPServer {
private fun createServer(port: Int, name: CordaX500Name = ALICE_NAME, crlCheckSoftFail: Boolean = true): AMQPServer {
val serverConfig = rigorousMock<AbstractNodeConfiguration>().also {
doReturn(temporaryFolder.root.toPath() / "server").whenever(it).baseDirectory
doReturn(name).whenever(it).myLegalName
doReturn("trustpass").whenever(it).trustStorePassword
doReturn("cordacadevpass").whenever(it).keyStorePassword
doReturn(crlCheckSoftFail).whenever(it).crlCheckSoftFail
}
serverConfig.configureWithDevSSLCertificate()
@ -305,6 +327,7 @@ class ProtonWrapperTests {
PEER_USER,
serverKeystore,
serverConfig.keyStorePassword,
serverTruststore)
serverTruststore,
crlCheckSoftFail = true)
}
}

View File

@ -335,7 +335,7 @@ class SocksTests {
PEER_USER,
clientKeystore,
clientConfig.keyStorePassword,
clientTruststore, true, sharedEventGroup,
clientTruststore, true, true, sharedEventGroup,
socksProxyConfig = SocksProxyConfig(SocksProxyVersion.SOCKS5, NetworkHostAndPort("127.0.0.1", socksPort), null, null))
}
@ -357,6 +357,7 @@ class SocksTests {
PEER_USER,
serverKeystore,
serverConfig.keyStorePassword,
serverTruststore)
serverTruststore,
true)
}
}

View File

@ -98,6 +98,7 @@ class MQSecurityAsNodeTest : P2PMQSecurityTest() {
override val certificatesDirectory = Files.createTempDirectory("certs")
override val keyStorePassword: String get() = "cordacadevpass"
override val trustStorePassword: String get() = "trustpass"
override val crlCheckSoftFail: Boolean = true
init {
val legalName = CordaX500Name("MegaCorp", "London", "GB")

View File

@ -163,6 +163,7 @@ data class NodeConfigurationImpl(
override val emailAddress: String,
override val keyStorePassword: String,
override val trustStorePassword: String,
override val crlCheckSoftFail: Boolean,
override val dataSourceProperties: Properties,
override val compatibilityZoneURL: URL? = null,
override val rpcUsers: List<User>,
@ -198,12 +199,12 @@ data class NodeConfigurationImpl(
private val h2port: Int = 0,
// do not use or remove (used by Capsule)
private val jarDirs: List<String> = emptyList()
) : NodeConfiguration {
) : NodeConfiguration {
companion object {
private val logger = loggerFor<NodeConfigurationImpl>()
}
override val rpcOptions: NodeRpcOptions = initialiseRpcOptions(rpcAddress, rpcSettings, SslOptions(baseDirectory / "certificates", keyStorePassword, trustStorePassword))
override val rpcOptions: NodeRpcOptions = initialiseRpcOptions(rpcAddress, rpcSettings, SslOptions(baseDirectory / "certificates", keyStorePassword, trustStorePassword, crlCheckSoftFail))
private fun initialiseRpcOptions(explicitAddress: NetworkHostAndPort?, settings: NodeRpcSettings, fallbackSslOptions: SSLConfiguration): NodeRpcOptions {
return when {
@ -384,8 +385,8 @@ data class SecurityConfiguration(val authService: SecurityConfiguration.AuthServ
}
}
fun copyWithAdditionalUser(user: User) : DataSource{
val extendedList = this.users?.toMutableList()?: mutableListOf()
fun copyWithAdditionalUser(user: User): DataSource {
val extendedList = this.users?.toMutableList() ?: mutableListOf()
extendedList.add(user)
return DataSource(this.type, this.passwordEncryption, this.connection, listOf(*extendedList.toTypedArray()))
}

View File

@ -14,9 +14,20 @@ import net.corda.nodeapi.internal.config.SSLConfiguration
import java.nio.file.Path
import java.nio.file.Paths
data class SslOptions(override val certificatesDirectory: Path, override val keyStorePassword: String, override val trustStorePassword: String) : SSLConfiguration {
// TODO: we use both SSL and Ssl for names. We should pick one of them, or even better change to TLS
data class SslOptions(override val certificatesDirectory: Path,
override val keyStorePassword: String,
override val trustStorePassword: String,
override val crlCheckSoftFail: Boolean) : SSLConfiguration {
fun copy(certificatesDirectory: String = this.certificatesDirectory.toString(), keyStorePassword: String = this.keyStorePassword, trustStorePassword: String = this.trustStorePassword): SslOptions = copy(certificatesDirectory = certificatesDirectory.toAbsolutePath(), keyStorePassword = keyStorePassword, trustStorePassword = trustStorePassword)
fun copy(certificatesDirectory: String = this.certificatesDirectory.toString(),
keyStorePassword: String = this.keyStorePassword,
trustStorePassword: String = this.trustStorePassword,
crlCheckSoftFail: Boolean = this.crlCheckSoftFail): SslOptions = copy(
certificatesDirectory = certificatesDirectory.toAbsolutePath(),
keyStorePassword = keyStorePassword,
trustStorePassword = trustStorePassword,
crlCheckSoftFail = crlCheckSoftFail)
}
private fun String.toAbsolutePath() = Paths.get(this).toAbsolutePath()

View File

@ -4,7 +4,6 @@ import net.corda.core.internal.div
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.services.Permissions
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.shouldInitCrashShell
import net.corda.nodeapi.internal.config.User
import net.corda.tools.shell.ShellConfiguration
import net.corda.tools.shell.ShellConfiguration.Companion.COMMANDS_DIR
@ -22,7 +21,8 @@ fun NodeConfiguration.toShellConfig(): ShellConfiguration {
ShellSslOptions(sslKeystore,
keyStorePassword,
trustStoreFile,
trustStorePassword)
trustStorePassword,
crlCheckSoftFail)
}
} else {
null

View File

@ -11,6 +11,7 @@ myLegalName = "Vast Global MegaCorp, Ltd"
emailAddress = "admin@company.com"
keyStorePassword = "cordacadevpass"
trustStorePassword = "trustpass"
crlCheckSoftFail = true
dataSourceProperties = {
dataSourceClassName = org.h2.jdbcx.JdbcDataSource
dataSource.url = "jdbc:h2:file:"${baseDirectory}"/persistence;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=10000;WRITE_DELAY=0;AUTO_SERVER_PORT="${h2port}

View File

@ -103,7 +103,7 @@ class NodeConfigurationImplTest {
adminAddress = NetworkHostAndPort("localhost", 2),
standAloneBroker = false,
useSsl = false,
ssl = SslOptions(baseDirectory / "certificates", keyStorePassword, trustStorePassword))
ssl = SslOptions(baseDirectory / "certificates", keyStorePassword, trustStorePassword, true))
return NodeConfigurationImpl(
baseDirectory = baseDirectory,
myLegalName = ALICE_NAME,
@ -122,7 +122,8 @@ class NodeConfigurationImplTest {
noLocalShell = false,
rpcSettings = rpcSettings,
relay = null,
enterpriseConfiguration = EnterpriseConfiguration((MutualExclusionConfiguration(false, "", 20000, 40000)))
enterpriseConfiguration = EnterpriseConfiguration((MutualExclusionConfiguration(false, "", 20000, 40000))),
crlCheckSoftFail = true
)
}
}

View File

@ -86,9 +86,13 @@ class KeyStores(val keyStore: UnsafeKeyStore, val trustStore: UnsafeKeyStore) {
}
}
}
data class TestSslOptions(override val certificatesDirectory: Path, override val keyStorePassword: String, override val trustStorePassword: String) : SSLConfiguration
private fun sslConfiguration(directory: Path) = TestSslOptions(directory, keyStore.password, trustStore.password)
data class TestSslOptions(override val certificatesDirectory: Path,
override val keyStorePassword: String,
override val trustStorePassword: String,
override val crlCheckSoftFail: Boolean) : SSLConfiguration
private fun sslConfiguration(directory: Path) = TestSslOptions(directory, keyStore.password, trustStore.password, true)
}
interface AutoClosableSSLConfiguration : AutoCloseable {
@ -131,6 +135,7 @@ data class UnsafeKeyStore(private val delegate: KeyStore, val password: String)
class TemporaryFile(fileName: String, val directory: Path) : AutoCloseable {
val file: Path = (directory / fileName).createFile().toAbsolutePath()
init {
file.toFile().deleteOnExit()
}

View File

@ -75,6 +75,7 @@ fun configureTestSSL(legalName: CordaX500Name): SSLConfiguration {
override val certificatesDirectory = Files.createTempDirectory("certs")
override val keyStorePassword: String get() = "cordacadevpass"
override val trustStorePassword: String get() = "trustpass"
override val crlCheckSoftFail: Boolean = true
init {
configureDevKeyAndTrustStores(legalName)
@ -130,22 +131,24 @@ fun createDevNodeCaCertPath(
/** Application of [doAnswer] that gets a value from the given [map] using the arg at [argIndex] as key. */
fun doLookup(map: Map<*, *>, argIndex: Int = 0) = doAnswer { map[it.arguments[argIndex]] }
fun SSLConfiguration.useSslRpcOverrides(): Map<String, String> {
fun SSLConfiguration.useSslRpcOverrides(): Map<String, Any> {
return mapOf(
"rpcSettings.useSsl" to "true",
"rpcSettings.ssl.certificatesDirectory" to certificatesDirectory.toString(),
"rpcSettings.ssl.keyStorePassword" to keyStorePassword,
"rpcSettings.ssl.trustStorePassword" to trustStorePassword
"rpcSettings.ssl.trustStorePassword" to trustStorePassword,
"rpcSettings.ssl.crlCheckSoftFail" to true
)
}
fun SSLConfiguration.noSslRpcOverrides(rpcAdminAddress: NetworkHostAndPort): 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.trustStorePassword" to trustStorePassword,
"rpcSettings.ssl.crlCheckSoftFail" to true
)
}

View File

@ -103,7 +103,7 @@ class InteractiveShellIntegrationTest : IntegrationTest() {
startNode(rpcUsers = listOf(user), customOverrides = nodeSslOptions.useSslRpcOverrides()).getOrThrow().use { node ->
val sslConfiguration = ShellSslOptions(clientSslOptions.sslKeystore, clientSslOptions.keyStorePassword,
clientSslOptions.trustStoreFile, clientSslOptions.trustStorePassword)
clientSslOptions.trustStoreFile, clientSslOptions.trustStorePassword, clientSslOptions.crlCheckSoftFail)
val conf = ShellConfiguration(commandsDirectory = Files.createTempDir().toPath(),
user = user.username, password = user.password,
hostAndPort = node.rpcAddress,
@ -140,7 +140,7 @@ class InteractiveShellIntegrationTest : IntegrationTest() {
startNode(rpcUsers = listOf(user), customOverrides = nodeSslOptions.useSslRpcOverrides()).getOrThrow().use { node ->
val sslConfiguration = ShellSslOptions(clientSslOptions.sslKeystore, clientSslOptions.keyStorePassword,
clientSslOptions.trustStoreFile, clientSslOptions.trustStorePassword)
clientSslOptions.trustStoreFile, clientSslOptions.trustStorePassword, clientSslOptions.crlCheckSoftFail)
val conf = ShellConfiguration(commandsDirectory = Files.createTempDir().toPath(),
user = user.username, password = user.password,
hostAndPort = node.rpcAddress,
@ -222,7 +222,7 @@ class InteractiveShellIntegrationTest : IntegrationTest() {
startNode(rpcUsers = listOf(user), customOverrides = nodeSslOptions.useSslRpcOverrides()).getOrThrow().use { node ->
val sslConfiguration = ShellSslOptions(clientSslOptions.sslKeystore, clientSslOptions.keyStorePassword,
clientSslOptions.trustStoreFile, clientSslOptions.trustStorePassword)
clientSslOptions.trustStoreFile, clientSslOptions.trustStorePassword, clientSslOptions.crlCheckSoftFail)
val conf = ShellConfiguration(commandsDirectory = Files.createTempDir().toPath(),
user = user.username, password = user.password,
hostAndPort = node.rpcAddress,

View File

@ -23,6 +23,12 @@ data class ShellConfiguration(
}
}
data class ShellSslOptions(override val sslKeystore: Path, override val keyStorePassword: String, override val trustStoreFile:Path, override val trustStorePassword: String) : SSLConfiguration {
//TODO: sslKeystore -> it's a path not the keystore itself.
//TODO: trustStoreFile -> it's a path not the file itself.
data class ShellSslOptions(override val sslKeystore: Path,
override val keyStorePassword: String,
override val trustStoreFile: Path,
override val trustStorePassword: String,
override val crlCheckSoftFail: Boolean) : SSLConfiguration {
override val certificatesDirectory: Path get() = Paths.get("")
}

View File

@ -37,10 +37,10 @@ class CommandLineOptionParser {
.accepts("commands-directory", "The directory with additional CrAsH shell commands.")
.withOptionalArg()
private val hostArg = optionParser
.acceptsAll(listOf("h","host"), "The host of the Corda node.")
.acceptsAll(listOf("h", "host"), "The host of the Corda node.")
.withRequiredArg()
private val portArg = optionParser
.acceptsAll(listOf("p","port"), "The port of the Corda node.")
.acceptsAll(listOf("p", "port"), "The port of the Corda node.")
.withRequiredArg()
private val userArg = optionParser
.accepts("user", "The RPC user name.")
@ -219,11 +219,13 @@ private class ShellConfigurationFile {
sslKeystore = Paths.get(it.keystore.path),
keyStorePassword = it.keystore.password,
trustStoreFile = Paths.get(it.truststore.path),
trustStorePassword = it.truststore.password)
trustStorePassword = it.truststore.password,
crlCheckSoftFail = true)
}
return ShellConfiguration(
commandsDirectory = extensions?.commands?.let { Paths.get(it.path) } ?: Paths.get(".") / COMMANDS_DIR,
commandsDirectory = extensions?.commands?.let { Paths.get(it.path) } ?: Paths.get(".")
/ COMMANDS_DIR,
cordappsDirectory = extensions?.cordapps?.let { Paths.get(it.path) },
user = node.user ?: "",
password = node.password ?: "",

View File

@ -112,12 +112,13 @@ class StandaloneShellArgsParserTest {
trustStoreFile = Paths.get("/x/y/truststore.jks"),
keyStoreType = "dummy",
trustStoreType = "dummy"
)
)
val expectedSsl = ShellSslOptions(sslKeystore = Paths.get("/x/y/keystore.jks"),
keyStorePassword = "pass1",
trustStoreFile = Paths.get("/x/y/truststore.jks"),
trustStorePassword = "pass2")
trustStorePassword = "pass2",
crlCheckSoftFail = true)
val expectedConfig = ShellConfiguration(
commandsDirectory = Paths.get("/x/y/commands"),
cordappsDirectory = Paths.get("/x/y/cordapps"),
@ -158,7 +159,8 @@ class StandaloneShellArgsParserTest {
val expectedSsl = ShellSslOptions(sslKeystore = Paths.get("/x/y/keystore.jks"),
keyStorePassword = "pass1",
trustStoreFile = Paths.get("/x/y/truststore.jks"),
trustStorePassword = "pass2")
trustStorePassword = "pass2",
crlCheckSoftFail = true)
val expectedConfig = ShellConfiguration(
commandsDirectory = Paths.get("/x/y/commands"),
cordappsDirectory = Paths.get("/x/y/cordapps"),
@ -197,7 +199,8 @@ class StandaloneShellArgsParserTest {
val expectedSsl = ShellSslOptions(sslKeystore = Paths.get("/x/y/cmd.jks"),
keyStorePassword = "pass1",
trustStoreFile = Paths.get("/x/y/truststore.jks"),
trustStorePassword = "pass2")
trustStorePassword = "pass2",
crlCheckSoftFail = true)
val expectedConfig = ShellConfiguration(
commandsDirectory = Paths.get("/x/y/commands"),
cordappsDirectory = Paths.get("/x/y/cordapps"),

View File

@ -24,6 +24,7 @@ import java.nio.file.Path
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
val useHTTPS: Boolean by config
val myLegalName: String by config
val rpcAddress: NetworkHostAndPort by lazy {