mirror of
https://github.com/corda/corda.git
synced 2025-06-17 06:38:21 +00:00
CORDA-1319 Adding CRL checking for nodes (#2987)
* Adding CRL support for nodes * Addressing review comments
This commit is contained in:
@ -34,6 +34,7 @@ class NodeKeystoreCheckTest {
|
||||
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)
|
||||
|
||||
|
@ -77,7 +77,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()
|
||||
@ -173,6 +173,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
|
||||
@ -210,6 +211,7 @@ class AMQPBridgeTest {
|
||||
serverConfig.loadSslKeyStore().internal,
|
||||
serverConfig.keyStorePassword,
|
||||
serverConfig.loadTrustStore().internal,
|
||||
crlCheckSoftFail = true,
|
||||
trace = true
|
||||
)
|
||||
}
|
||||
|
@ -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, "/*")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -224,6 +224,7 @@ class ProtonWrapperTests {
|
||||
doReturn(NetworkHostAndPort("0.0.0.0", artemisPort)).whenever(it).p2pAddress
|
||||
doReturn(null).whenever(it).jmxMonitoringHttpPort
|
||||
doReturn(emptyList<CertChainPolicyConfig>()).whenever(it).certificateChainCheckPolicies
|
||||
doReturn(true).whenever(it).crlCheckSoftFail
|
||||
}
|
||||
artemisConfig.configureWithDevSSLCertificate()
|
||||
|
||||
@ -240,6 +241,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()
|
||||
|
||||
@ -247,14 +249,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 {
|
||||
@ -263,6 +266,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()
|
||||
|
||||
@ -275,7 +279,9 @@ class ProtonWrapperTests {
|
||||
PEER_USER,
|
||||
clientKeystore,
|
||||
clientConfig.keyStorePassword,
|
||||
clientTruststore, true, sharedEventGroup)
|
||||
clientTruststore,
|
||||
true,
|
||||
sharedThreadPool = sharedEventGroup)
|
||||
}
|
||||
|
||||
private fun createServer(port: Int, name: CordaX500Name = ALICE_NAME): AMQPServer {
|
||||
@ -284,6 +290,7 @@ class ProtonWrapperTests {
|
||||
doReturn(name).whenever(it).myLegalName
|
||||
doReturn("trustpass").whenever(it).trustStorePassword
|
||||
doReturn("cordacadevpass").whenever(it).keyStorePassword
|
||||
doReturn(true).whenever(it).crlCheckSoftFail
|
||||
}
|
||||
serverConfig.configureWithDevSSLCertificate()
|
||||
|
||||
@ -296,6 +303,7 @@ class ProtonWrapperTests {
|
||||
PEER_USER,
|
||||
serverKeystore,
|
||||
serverConfig.keyStorePassword,
|
||||
serverTruststore)
|
||||
serverTruststore,
|
||||
crlCheckSoftFail = true)
|
||||
}
|
||||
}
|
||||
|
@ -88,6 +88,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")
|
||||
|
@ -89,6 +89,7 @@ data class NotaryConfig(val validating: Boolean,
|
||||
"raft, bftSMaRt, and custom configs cannot be specified together"
|
||||
}
|
||||
}
|
||||
|
||||
val isClusterConfig: Boolean get() = raft != null || bftSMaRt != null
|
||||
}
|
||||
|
||||
@ -128,10 +129,11 @@ 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>,
|
||||
override val security : SecurityConfiguration? = null,
|
||||
override val security: SecurityConfiguration? = null,
|
||||
override val verifierType: VerifierType,
|
||||
override val p2pMessagingRetry: P2PMessagingRetryConfiguration,
|
||||
override val p2pAddress: NetworkHostAndPort,
|
||||
@ -155,15 +157,15 @@ data class NodeConfigurationImpl(
|
||||
override val attachmentCacheBound: Long = NodeConfiguration.defaultAttachmentCacheBound,
|
||||
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 = 0,
|
||||
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 {
|
||||
@ -321,8 +323,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()))
|
||||
}
|
||||
|
@ -4,9 +4,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()
|
@ -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
|
||||
|
@ -2,6 +2,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=100;AUTO_SERVER_PORT="${h2port}
|
||||
|
@ -62,7 +62,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,
|
||||
@ -79,7 +79,8 @@ class NodeConfigurationImplTest {
|
||||
certificateChainCheckPolicies = emptyList(),
|
||||
devMode = true,
|
||||
noLocalShell = false,
|
||||
rpcSettings = rpcSettings
|
||||
rpcSettings = rpcSettings,
|
||||
crlCheckSoftFail = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user