ENT-1125 bootstrap root certificate (#2151)

* ENT-1125 make nodes check that the returned signed certificate from Doorman has the expected root
This commit is contained in:
Alberto Arri
2017-12-04 12:53:22 +00:00
committed by GitHub
parent d5e3f28303
commit acd2281b20
11 changed files with 271 additions and 53 deletions

View File

@ -15,4 +15,5 @@ interface SSLConfiguration {
interface NodeSSLConfiguration : SSLConfiguration { interface NodeSSLConfiguration : SSLConfiguration {
val baseDirectory: Path val baseDirectory: Path
override val certificatesDirectory: Path get() = baseDirectory / "certificates" override val certificatesDirectory: Path get() = baseDirectory / "certificates"
val rootCaCertFile: Path get() = certificatesDirectory / "rootcacert.cer"
} }

View File

@ -9,8 +9,10 @@ import net.corda.core.internal.createDirectories
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.core.utilities.trace import net.corda.core.utilities.trace
import net.corda.nodeapi.internal.crypto.* import net.corda.nodeapi.internal.crypto.*
import org.bouncycastle.cert.X509CertificateHolder
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.nio.file.Path import java.nio.file.Path
import java.security.cert.Certificate
object ServiceIdentityGenerator { object ServiceIdentityGenerator {
private val log = LoggerFactory.getLogger(javaClass) private val log = LoggerFactory.getLogger(javaClass)
@ -22,18 +24,25 @@ object ServiceIdentityGenerator {
* @param dirs List of node directories to place the generated identity and key pairs in. * @param dirs List of node directories to place the generated identity and key pairs in.
* @param serviceName The legal name of the distributed service. * @param serviceName The legal name of the distributed service.
* @param threshold The threshold for the generated group [CompositeKey]. * @param threshold The threshold for the generated group [CompositeKey].
* @param rootCertertificate the certificate to use a Corda root CA. If not specified the one in
* net/corda/node/internal/certificates/cordadevcakeys.jks is used.
*/ */
fun generateToDisk(dirs: List<Path>, fun generateToDisk(dirs: List<Path>,
serviceName: CordaX500Name, serviceName: CordaX500Name,
serviceId: String, serviceId: String,
threshold: Int = 1): Party { threshold: Int = 1,
rootCertertificate: X509CertificateHolder? = null): Party {
log.trace { "Generating a group identity \"serviceName\" for nodes: ${dirs.joinToString()}" } log.trace { "Generating a group identity \"serviceName\" for nodes: ${dirs.joinToString()}" }
val keyPairs = (1..dirs.size).map { generateKeyPair() } val keyPairs = (1..dirs.size).map { generateKeyPair() }
val notaryKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold) val notaryKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold)
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass") val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
val issuer = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass") val issuer = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
val rootCert = caKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA) val rootCert: Certificate = if (rootCertertificate != null) {
rootCertertificate.cert
} else {
caKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA)
}
keyPairs.zip(dirs) { keyPair, dir -> keyPairs.zip(dirs) { keyPair, dir ->
val serviceKeyCert = X509Utilities.createCertificate(CertificateType.CLIENT_CA, issuer.certificate, issuer.keyPair, serviceName, keyPair.public) val serviceKeyCert = X509Utilities.createCertificate(CertificateType.CLIENT_CA, issuer.certificate, issuer.keyPair, serviceName, keyPair.public)

View File

@ -313,6 +313,11 @@ class X509CertificateFactory {
fun generateCertificate(input: InputStream): X509Certificate { fun generateCertificate(input: InputStream): X509Certificate {
return delegate.generateCertificate(input) as X509Certificate return delegate.generateCertificate(input) as X509Certificate
} }
// TODO migrate calls to [CertificateFactory#generateCertPath] to call this instead.
fun generateCertPath(vararg certificates: Certificate): CertPath {
return delegate.generateCertPath(certificates.asList())
}
} }
enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurposeId, val isCA: Boolean) { enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurposeId, val isCA: Boolean) {

View File

@ -0,0 +1,182 @@
package net.corda.node.utilities.registration
import net.corda.core.crypto.Crypto
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.cert
import net.corda.core.internal.toX509CertHolder
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.minutes
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
import net.corda.testing.ALICE_NAME
import net.corda.testing.driver.PortAllocation
import net.corda.testing.driver.driver
import net.corda.testing.node.network.NetworkMapServer
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.net.URL
import java.security.KeyPair
import java.security.cert.CertPath
import java.security.cert.Certificate
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import javax.ws.rs.*
import javax.ws.rs.core.MediaType
import javax.ws.rs.core.Response
private const val REQUEST_ID = "requestId"
private val x509CertificateFactory = X509CertificateFactory()
private val portAllocation = PortAllocation.Incremental(13000)
/**
* Driver based tests for [NetworkRegistrationHelper]
*/
class NetworkRegistrationHelperDriverTest {
val rootCertAndKeyPair = createSelfKeyAndSelfSignedCertificate()
val rootCert = rootCertAndKeyPair.certificate
val handler = RegistrationHandler(rootCertAndKeyPair)
lateinit var server: NetworkMapServer
lateinit var host: String
var port: Int = 0
val compatibilityZoneUrl get() = URL("http", host, port, "")
@Before
fun startServer() {
server = NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), handler)
val (host, port) = server.start()
this.host = host
this.port = port
}
@After
fun stopServer() {
server.close()
}
@Test
fun `node registration correct root cert`() {
driver(portAllocation = portAllocation,
compatibilityZoneURL = compatibilityZoneUrl,
startNodesInProcess = true,
rootCertificate = rootCert
) {
startNode(providedName = ALICE_NAME, initialRegistration = true).get()
}
// We're getting:
// a request to sign the certificate then
// at least one poll request to see if the request has been approved.
// all the network map registration and download.
assertThat(handler.requests).startsWith("/certificate", "/certificate/" + REQUEST_ID)
}
@Test
fun `node registration without root cert`() {
driver(portAllocation = portAllocation,
compatibilityZoneURL = compatibilityZoneUrl,
startNodesInProcess = true
) {
assertThatThrownBy {
startNode(providedName = ALICE_NAME, initialRegistration = true).get()
}.isInstanceOf(java.nio.file.NoSuchFileException::class.java)
}
}
@Test
fun `node registration wrong root cert`() {
driver(portAllocation = portAllocation,
compatibilityZoneURL = compatibilityZoneUrl,
startNodesInProcess = true,
rootCertificate = createSelfKeyAndSelfSignedCertificate().certificate
) {
assertThatThrownBy {
startNode(providedName = ALICE_NAME, initialRegistration = true).get()
}.isInstanceOf(WrongRootCaCertificateException::class.java)
}
}
}
/**
* Simple registration handler which can handle a single request, which will be given request id [REQUEST_ID].
*/
@Path("certificate")
class RegistrationHandler(private val certificateAndKeyPair: CertificateAndKeyPair) {
val requests = mutableListOf<String>()
lateinit var certificationRequest: JcaPKCS10CertificationRequest
@POST
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
@Produces(MediaType.TEXT_PLAIN)
fun registration(input: InputStream): Response {
requests += "/certificate"
certificationRequest = input.use { JcaPKCS10CertificationRequest(it.readBytes()) }
return Response.ok(REQUEST_ID).build()
}
@GET
@Path(REQUEST_ID)
fun reply(): Response {
requests += "/certificate/" + REQUEST_ID
val certPath = createSignedClientCertificate(certificationRequest,
certificateAndKeyPair.keyPair, arrayOf(certificateAndKeyPair.certificate.cert))
return buildDoormanReply(certPath.certificates.toTypedArray())
}
}
// TODO this logic is shared with doorman itself, refactor this to be somewhere where both doorman and these tests
// can depend on
private fun createSignedClientCertificate(certificationRequest: PKCS10CertificationRequest,
caKeyPair: KeyPair,
caCertPath: Array<Certificate>): CertPath {
val request = JcaPKCS10CertificationRequest(certificationRequest)
val x509CertificateHolder = X509Utilities.createCertificate(CertificateType.CLIENT_CA,
caCertPath.first().toX509CertHolder(),
caKeyPair,
CordaX500Name.parse(request.subject.toString()).copy(commonName = X509Utilities.CORDA_CLIENT_CA_CN),
request.publicKey,
nameConstraints = null)
return x509CertificateFactory.generateCertPath(x509CertificateHolder.cert, *caCertPath)
}
// TODO this logic is shared with doorman itself, refactor this to be somewhere where both doorman and these tests
// can depend on
private fun buildDoormanReply(certificates: Array<Certificate>): Response {
// Write certificate chain to a zip stream and extract the bit array output.
val baos = ByteArrayOutputStream()
ZipOutputStream(baos).use { zip ->
// Client certificate must come first and root certificate should come last.
listOf(CORDA_CLIENT_CA, CORDA_INTERMEDIATE_CA, CORDA_ROOT_CA).zip(certificates).forEach {
zip.putNextEntry(ZipEntry("${it.first}.cer"))
zip.write(it.second.encoded)
zip.closeEntry()
}
}
return Response.ok(baos.toByteArray())
.type("application/zip")
.header("Content-Disposition", "attachment; filename=\"certificates.zip\"").build()
}
private fun createSelfKeyAndSelfSignedCertificate(): CertificateAndKeyPair {
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val rootCACert = X509Utilities.createSelfSignedCACertificate(
CordaX500Name(commonName = "Integration Test Corda Node Root CA",
organisation = "R3 Ltd", locality = "London",
country = "GB"), rootCAKey)
return CertificateAndKeyPair(rootCACert, rootCAKey)
}

View File

@ -12,6 +12,7 @@ import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
import org.bouncycastle.openssl.jcajce.JcaPEMWriter import org.bouncycastle.openssl.jcajce.JcaPEMWriter
import org.bouncycastle.util.io.pem.PemObject import org.bouncycastle.util.io.pem.PemObject
import java.io.StringWriter import java.io.StringWriter
import java.nio.file.Path
import java.security.KeyPair import java.security.KeyPair
import java.security.KeyStore import java.security.KeyStore
import java.security.cert.Certificate import java.security.cert.Certificate
@ -75,10 +76,15 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
caKeyStore.addOrReplaceKey(CORDA_CLIENT_CA, keyPair.private, privateKeyPassword.toCharArray(), certificates) caKeyStore.addOrReplaceKey(CORDA_CLIENT_CA, keyPair.private, privateKeyPassword.toCharArray(), certificates)
caKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY) caKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY)
caKeyStore.save(config.nodeKeystore, keystorePassword) caKeyStore.save(config.nodeKeystore, keystorePassword)
// Check the root certificate.
val returnedRootCa = certificates.last()
checkReturnedRootCaMatchesExpectedCa(returnedRootCa)
// Save root certificates to trust store. // Save root certificates to trust store.
val trustStore = loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword) val trustStore = loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword)
// Assumes certificate chain always starts with client certificate and end with root certificate. // Assumes certificate chain always starts with client certificate and end with root certificate.
trustStore.addOrReplaceCertificate(CORDA_ROOT_CA, certificates.last()) trustStore.addOrReplaceCertificate(CORDA_ROOT_CA, returnedRootCa)
trustStore.save(config.trustStoreFile, config.trustStorePassword) trustStore.save(config.trustStoreFile, config.trustStorePassword)
println("Node private key and certificate stored in ${config.nodeKeystore}.") println("Node private key and certificate stored in ${config.nodeKeystore}.")
@ -98,6 +104,17 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
} }
} }
/**
* Checks that the passed Certificate is the expected root CA.
* @throws WrongRootCaCertificateException if the certificates don't match.
*/
private fun checkReturnedRootCaMatchesExpectedCa(returnedRootCa: Certificate) {
val expected = X509Utilities.loadCertificateFromPEMFile(config.rootCaCertFile).cert
if (expected != returnedRootCa) {
throw WrongRootCaCertificateException(expected, returnedRootCa, config.rootCaCertFile)
}
}
/** /**
* Poll Certificate Signing Server for approved certificate, * Poll Certificate Signing Server for approved certificate,
* enter a slow polling loop if server return null. * enter a slow polling loop if server return null.
@ -151,3 +168,17 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
} }
} }
} }
/**
* Exception thrown when the doorman root certificate doesn't match the expected (out-of-band) root certificate.
* This usually means the has been a Man-in-the-middle attack when contacting the doorman.
*/
class WrongRootCaCertificateException(expected: Certificate,
actual: Certificate,
expectedFilePath: Path):
Exception("""
The Root CA returned back from the registration process does not match the expected Root CA
expected: $expected
actual: $actual
the expected certificate is stored in: $expectedFilePath
""".trimMargin())

View File

@ -46,6 +46,9 @@ class NetworkRegistrationHelperTest {
baseDirectory = tempFolder.root.toPath(), baseDirectory = tempFolder.root.toPath(),
myLegalName = ALICE.name) myLegalName = ALICE.name)
config.rootCaCertFile.parent.createDirectories()
X509Utilities.saveCertificateAsPEMFile(certs.last().toX509CertHolder(), config.rootCaCertFile)
assertFalse(config.nodeKeystore.exists()) assertFalse(config.nodeKeystore.exists())
assertFalse(config.sslKeystore.exists()) assertFalse(config.sslKeystore.exists())
assertFalse(config.trustStoreFile.exists()) assertFalse(config.trustStoreFile.exists())

View File

@ -5,25 +5,17 @@ import net.corda.core.internal.div
import net.corda.core.internal.list import net.corda.core.internal.list
import net.corda.core.internal.readLines import net.corda.core.internal.readLines
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.minutes
import net.corda.core.utilities.seconds
import net.corda.node.internal.NodeStartup import net.corda.node.internal.NodeStartup
import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_BANK_A
import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.DUMMY_REGULATOR import net.corda.testing.DUMMY_REGULATOR
import net.corda.testing.common.internal.ProjectStructure.projectRootDir import net.corda.testing.common.internal.ProjectStructure.projectRootDir
import net.corda.testing.node.NotarySpec import net.corda.testing.node.NotarySpec
import net.corda.testing.node.network.NetworkMapServer
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Test import org.junit.Test
import java.net.URL
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.ScheduledExecutorService
import javax.ws.rs.GET
import javax.ws.rs.POST
import javax.ws.rs.Path
import javax.ws.rs.core.Response
import javax.ws.rs.core.Response.ok
class DriverTests { class DriverTests {
companion object { companion object {
@ -61,22 +53,6 @@ class DriverTests {
nodeMustBeDown(nodeHandle) nodeMustBeDown(nodeHandle)
} }
@Test
fun `node registration`() {
val handler = RegistrationHandler()
NetworkMapServer(1.seconds, portAllocation.nextHostAndPort(), handler).use {
val (host, port) = it.start()
driver(portAllocation = portAllocation, compatibilityZoneURL = URL("http://$host:$port")) {
// Wait for the node to have started.
startNode(initialRegistration = true).get()
}
}
// We're getting:
// a request to sign the certificate then
// at least one poll request to see if the request has been approved.
// all the network map registration and download.
assertThat(handler.requests).startsWith("/certificate", "/certificate/reply")
}
@Test @Test
fun `debug mode enables debug logging level`() { fun `debug mode enables debug logging level`() {
@ -106,20 +82,3 @@ class DriverTests {
assertThat(baseDirectory / "process-id").doesNotExist() assertThat(baseDirectory / "process-id").doesNotExist()
} }
} }
@Path("certificate")
class RegistrationHandler {
val requests = mutableListOf<String>()
@POST
fun registration(): Response {
requests += "/certificate"
return ok("reply").build()
}
@GET
@Path("reply")
fun reply(): Response {
requests += "/certificate/reply"
return ok().build()
}
}

View File

@ -78,6 +78,7 @@ fun testNodeConfiguration(
doCallRealMethod().whenever(it).trustStoreFile doCallRealMethod().whenever(it).trustStoreFile
doCallRealMethod().whenever(it).sslKeystore doCallRealMethod().whenever(it).sslKeystore
doCallRealMethod().whenever(it).nodeKeystore doCallRealMethod().whenever(it).nodeKeystore
doCallRealMethod().whenever(it).rootCaCertFile
} }
} }

View File

@ -41,6 +41,7 @@ import net.corda.nodeapi.config.parseAs
import net.corda.nodeapi.config.toConfig import net.corda.nodeapi.config.toConfig
import net.corda.nodeapi.internal.NotaryInfo import net.corda.nodeapi.internal.NotaryInfo
import net.corda.nodeapi.internal.addShutdownHook import net.corda.nodeapi.internal.addShutdownHook
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.testing.* import net.corda.testing.*
import net.corda.nodeapi.internal.NetworkParametersCopier import net.corda.nodeapi.internal.NetworkParametersCopier
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
@ -51,6 +52,7 @@ import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO
import net.corda.testing.node.NotarySpec import net.corda.testing.node.NotarySpec
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import org.bouncycastle.cert.X509CertificateHolder
import org.slf4j.Logger import org.slf4j.Logger
import rx.Observable import rx.Observable
import rx.observables.ConnectableObservable import rx.observables.ConnectableObservable
@ -343,9 +345,11 @@ data class NodeParameters(
* available from [DriverDSLExposedInterface.notaryHandles]. Defaults to a simple validating notary. * available from [DriverDSLExposedInterface.notaryHandles]. Defaults to a simple validating notary.
* @param compatibilityZoneURL if not null each node is started once in registration mode (which makes the node register and quit), * @param compatibilityZoneURL if not null each node is started once in registration mode (which makes the node register and quit),
* and then re-starts the node with the given parameters. * and then re-starts the node with the given parameters.
* @param rootCertificate if not null every time a node is started for registration that certificate is written on disk
* @param dsl The dsl itself. * @param dsl The dsl itself.
* @return The value returned in the [dsl] closure. * @return The value returned in the [dsl] closure.
*/ */
// TODO: make the registration testing parameters internal.
fun <A> driver( fun <A> driver(
defaultParameters: DriverParameters = DriverParameters(), defaultParameters: DriverParameters = DriverParameters(),
isDebug: Boolean = defaultParameters.isDebug, isDebug: Boolean = defaultParameters.isDebug,
@ -360,6 +364,7 @@ fun <A> driver(
notarySpecs: List<NotarySpec> = defaultParameters.notarySpecs, notarySpecs: List<NotarySpec> = defaultParameters.notarySpecs,
extraCordappPackagesToScan: List<String> = defaultParameters.extraCordappPackagesToScan, extraCordappPackagesToScan: List<String> = defaultParameters.extraCordappPackagesToScan,
compatibilityZoneURL: URL? = defaultParameters.compatibilityZoneURL, compatibilityZoneURL: URL? = defaultParameters.compatibilityZoneURL,
rootCertificate: X509CertificateHolder? = defaultParameters.rootCertificate,
dsl: DriverDSLExposedInterface.() -> A dsl: DriverDSLExposedInterface.() -> A
): A { ): A {
return genericDriver( return genericDriver(
@ -374,7 +379,8 @@ fun <A> driver(
waitForNodesToFinish = waitForAllNodesToFinish, waitForNodesToFinish = waitForAllNodesToFinish,
notarySpecs = notarySpecs, notarySpecs = notarySpecs,
extraCordappPackagesToScan = extraCordappPackagesToScan, extraCordappPackagesToScan = extraCordappPackagesToScan,
compatibilityZoneURL = compatibilityZoneURL compatibilityZoneURL = compatibilityZoneURL,
rootCertificate = rootCertificate
), ),
coerce = { it }, coerce = { it },
dsl = dsl, dsl = dsl,
@ -410,7 +416,8 @@ data class DriverParameters(
val waitForNodesToFinish: Boolean = false, val waitForNodesToFinish: Boolean = false,
val notarySpecs: List<NotarySpec> = listOf(NotarySpec(DUMMY_NOTARY.name)), val notarySpecs: List<NotarySpec> = listOf(NotarySpec(DUMMY_NOTARY.name)),
val extraCordappPackagesToScan: List<String> = emptyList(), val extraCordappPackagesToScan: List<String> = emptyList(),
val compatibilityZoneURL: URL? = null val compatibilityZoneURL: URL? = null,
val rootCertificate: X509CertificateHolder? = null
) { ) {
fun setIsDebug(isDebug: Boolean) = copy(isDebug = isDebug) fun setIsDebug(isDebug: Boolean) = copy(isDebug = isDebug)
fun setDriverDirectory(driverDirectory: Path) = copy(driverDirectory = driverDirectory) fun setDriverDirectory(driverDirectory: Path) = copy(driverDirectory = driverDirectory)
@ -421,8 +428,10 @@ data class DriverParameters(
fun setInitialiseSerialization(initialiseSerialization: Boolean) = copy(initialiseSerialization = initialiseSerialization) fun setInitialiseSerialization(initialiseSerialization: Boolean) = copy(initialiseSerialization = initialiseSerialization)
fun setStartNodesInProcess(startNodesInProcess: Boolean) = copy(startNodesInProcess = startNodesInProcess) fun setStartNodesInProcess(startNodesInProcess: Boolean) = copy(startNodesInProcess = startNodesInProcess)
fun setTerminateNodesOnShutdown(terminateNodesOnShutdown: Boolean) = copy(waitForNodesToFinish = terminateNodesOnShutdown) fun setTerminateNodesOnShutdown(terminateNodesOnShutdown: Boolean) = copy(waitForNodesToFinish = terminateNodesOnShutdown)
fun setExtraCordappPackagesToScan(extraCordappPackagesToScan: List<String>) = copy(extraCordappPackagesToScan = extraCordappPackagesToScan)
fun setNotarySpecs(notarySpecs: List<NotarySpec>) = copy(notarySpecs = notarySpecs) fun setNotarySpecs(notarySpecs: List<NotarySpec>) = copy(notarySpecs = notarySpecs)
fun setExtraCordappPackagesToScan(extraCordappPackagesToScan: List<String>) = copy(extraCordappPackagesToScan = extraCordappPackagesToScan)
fun setCompatibilityZoneURL(compatibilityZoneURL: URL?) = copy(compatibilityZoneURL = compatibilityZoneURL)
fun setRootCertificate(rootCertificate: X509CertificateHolder?) = copy(rootCertificate = rootCertificate)
} }
/** /**
@ -476,6 +485,7 @@ fun <DI : DriverDSLExposedInterface, D : DriverDSLInternalInterface, A> genericD
notarySpecs: List<NotarySpec>, notarySpecs: List<NotarySpec>,
extraCordappPackagesToScan: List<String> = defaultParameters.extraCordappPackagesToScan, extraCordappPackagesToScan: List<String> = defaultParameters.extraCordappPackagesToScan,
compatibilityZoneURL: URL? = defaultParameters.compatibilityZoneURL, compatibilityZoneURL: URL? = defaultParameters.compatibilityZoneURL,
rootCertificate: X509CertificateHolder? = defaultParameters.rootCertificate,
driverDslWrapper: (DriverDSL) -> D, driverDslWrapper: (DriverDSL) -> D,
coerce: (D) -> DI, dsl: DI.() -> A coerce: (D) -> DI, dsl: DI.() -> A
): A { ): A {
@ -492,7 +502,8 @@ fun <DI : DriverDSLExposedInterface, D : DriverDSLInternalInterface, A> genericD
waitForNodesToFinish = waitForNodesToFinish, waitForNodesToFinish = waitForNodesToFinish,
extraCordappPackagesToScan = extraCordappPackagesToScan, extraCordappPackagesToScan = extraCordappPackagesToScan,
notarySpecs = notarySpecs, notarySpecs = notarySpecs,
compatibilityZoneURL = compatibilityZoneURL compatibilityZoneURL = compatibilityZoneURL,
rootCertificate = rootCertificate
) )
) )
val shutdownHook = addShutdownHook(driverDsl::shutdown) val shutdownHook = addShutdownHook(driverDsl::shutdown)
@ -599,7 +610,8 @@ class DriverDSL(
val waitForNodesToFinish: Boolean, val waitForNodesToFinish: Boolean,
extraCordappPackagesToScan: List<String>, extraCordappPackagesToScan: List<String>,
val notarySpecs: List<NotarySpec>, val notarySpecs: List<NotarySpec>,
val compatibilityZoneURL: URL? val compatibilityZoneURL: URL?,
val rootCertificate: X509CertificateHolder?
) : DriverDSLInternalInterface { ) : DriverDSLInternalInterface {
private var _executorService: ScheduledExecutorService? = null private var _executorService: ScheduledExecutorService? = null
val executorService get() = _executorService!! val executorService get() = _executorService!!
@ -711,6 +723,11 @@ class DriverDSL(
} }
} }
private fun writeRootCaCertificateForNode(path: Path, caRootCertificate: X509CertificateHolder) {
path.parent.createDirectories()
X509Utilities.saveCertificateAsPEMFile(caRootCertificate, path)
}
private fun registerNode(providedName: CordaX500Name, compatibilityZoneURL: URL): CordaFuture<Unit> { private fun registerNode(providedName: CordaX500Name, compatibilityZoneURL: URL): CordaFuture<Unit> {
val config = ConfigHelper.loadConfig( val config = ConfigHelper.loadConfig(
baseDirectory = baseDirectory(providedName), baseDirectory = baseDirectory(providedName),
@ -720,10 +737,12 @@ class DriverDSL(
"compatibilityZoneURL" to compatibilityZoneURL.toString(), "compatibilityZoneURL" to compatibilityZoneURL.toString(),
"myLegalName" to providedName.toString()) "myLegalName" to providedName.toString())
) )
val configuration = config.parseAsNodeConfiguration()
// If a rootCertificate is specified, put that in the node expected path.
rootCertificate?.let { writeRootCaCertificateForNode(configuration.rootCaCertFile, it) }
if (startNodesInProcess) { if (startNodesInProcess) {
// This is a bit cheating, we're not starting a full node, we're just calling the code nodes call // This is a bit cheating, we're not starting a full node, we're just calling the code nodes call
// when registering. // when registering.
val configuration = config.parseAsNodeConfiguration()
NetworkRegistrationHelper(configuration, HTTPNetworkRegistrationService(compatibilityZoneURL)) NetworkRegistrationHelper(configuration, HTTPNetworkRegistrationService(compatibilityZoneURL))
.buildKeystore() .buildKeystore()
return doneFuture(Unit) return doneFuture(Unit)
@ -857,12 +876,14 @@ class DriverDSL(
ServiceIdentityGenerator.generateToDisk( ServiceIdentityGenerator.generateToDisk(
dirs = listOf(baseDirectory(spec.name)), dirs = listOf(baseDirectory(spec.name)),
serviceName = spec.name, serviceName = spec.name,
rootCertertificate = rootCertificate,
serviceId = "identity" serviceId = "identity"
) )
} else { } else {
ServiceIdentityGenerator.generateToDisk( ServiceIdentityGenerator.generateToDisk(
dirs = generateNodeNames(spec).map { baseDirectory(it) }, dirs = generateNodeNames(spec).map { baseDirectory(it) },
serviceName = spec.name, serviceName = spec.name,
rootCertertificate = rootCertificate,
serviceId = NotaryService.constructId( serviceId = NotaryService.constructId(
validating = spec.validating, validating = spec.validating,
raft = spec.cluster is ClusterSpec.Raft raft = spec.cluster is ClusterSpec.Raft

View File

@ -47,6 +47,7 @@ import org.apache.activemq.artemis.core.settings.impl.AddressFullMessagePolicy
import org.apache.activemq.artemis.core.settings.impl.AddressSettings import org.apache.activemq.artemis.core.settings.impl.AddressSettings
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager3 import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager3
import org.bouncycastle.cert.X509CertificateHolder
import java.lang.reflect.Method import java.lang.reflect.Method
import java.net.URL import java.net.URL
import java.nio.file.Path import java.nio.file.Path
@ -237,6 +238,7 @@ fun <A> rpcDriver(
notarySpecs: List<NotarySpec> = emptyList(), notarySpecs: List<NotarySpec> = emptyList(),
externalTrace: Trace? = null, externalTrace: Trace? = null,
compatibilityZoneURL: URL? = null, compatibilityZoneURL: URL? = null,
rootCertificate: X509CertificateHolder? = null,
dsl: RPCDriverExposedDSLInterface.() -> A dsl: RPCDriverExposedDSLInterface.() -> A
) = genericDriver( ) = genericDriver(
driverDsl = RPCDriverDSL( driverDsl = RPCDriverDSL(
@ -251,7 +253,8 @@ fun <A> rpcDriver(
waitForNodesToFinish = waitForNodesToFinish, waitForNodesToFinish = waitForNodesToFinish,
extraCordappPackagesToScan = extraCordappPackagesToScan, extraCordappPackagesToScan = extraCordappPackagesToScan,
notarySpecs = notarySpecs, notarySpecs = notarySpecs,
compatibilityZoneURL = compatibilityZoneURL compatibilityZoneURL = compatibilityZoneURL,
rootCertificate = rootCertificate
), externalTrace ), externalTrace
), ),
coerce = { it }, coerce = { it },

View File

@ -37,6 +37,7 @@ import org.apache.activemq.artemis.core.security.CheckType
import org.apache.activemq.artemis.core.security.Role import org.apache.activemq.artemis.core.security.Role
import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager
import org.bouncycastle.cert.X509CertificateHolder
import java.net.URL import java.net.URL
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
@ -87,6 +88,7 @@ fun <A> verifierDriver(
extraCordappPackagesToScan: List<String> = emptyList(), extraCordappPackagesToScan: List<String> = emptyList(),
notarySpecs: List<NotarySpec> = emptyList(), notarySpecs: List<NotarySpec> = emptyList(),
compatibilityZoneURL: URL? = null, compatibilityZoneURL: URL? = null,
rootCertificate: X509CertificateHolder? = null,
dsl: VerifierExposedDSLInterface.() -> A dsl: VerifierExposedDSLInterface.() -> A
) = genericDriver( ) = genericDriver(
driverDsl = VerifierDriverDSL( driverDsl = VerifierDriverDSL(
@ -101,7 +103,8 @@ fun <A> verifierDriver(
waitForNodesToFinish = waitForNodesToFinish, waitForNodesToFinish = waitForNodesToFinish,
extraCordappPackagesToScan = extraCordappPackagesToScan, extraCordappPackagesToScan = extraCordappPackagesToScan,
notarySpecs = notarySpecs, notarySpecs = notarySpecs,
compatibilityZoneURL = compatibilityZoneURL compatibilityZoneURL = compatibilityZoneURL,
rootCertificate = rootCertificate
) )
), ),
coerce = { it }, coerce = { it },