Moved the CZ URL and node registration logic of the driver to be more internal, not available through the standard driver call, as these are not testing features for an app dev.

Also cleanup up some of the related tests.
This commit is contained in:
Shams Asari 2017-12-05 16:58:35 +00:00
parent 66515d24b5
commit 89256a7f16
13 changed files with 304 additions and 282 deletions

View File

@ -9,13 +9,13 @@ 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 import java.security.cert.X509Certificate
object ServiceIdentityGenerator { object ServiceIdentityGenerator {
private val log = LoggerFactory.getLogger(javaClass) private val log = LoggerFactory.getLogger(javaClass)
/** /**
* Generates signing key pairs and a common distributed service identity for a set of nodes. * Generates signing key pairs and a common distributed service identity for a set of nodes.
* The key pairs and the group identity get serialized to disk in the corresponding node directories. * The key pairs and the group identity get serialized to disk in the corresponding node directories.
@ -24,25 +24,21 @@ 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 * @param customRootCert the certificate to use a Corda root CA. If not specified the one in
* net/corda/node/internal/certificates/cordadevcakeys.jks is used. * 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, threshold: Int = 1,
rootCertertificate: X509CertificateHolder? = null): Party { customRootCert: X509Certificate? = 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: Certificate = if (rootCertertificate != null) { val rootCert = customRootCert ?: caKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA)
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

@ -15,5 +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" val rootCertFile: Path get() = certificatesDirectory / "rootcert.pem"
} }

View File

@ -4,8 +4,8 @@ import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignatureScheme import net.corda.core.crypto.SignatureScheme
import net.corda.core.crypto.random63BitValue import net.corda.core.crypto.random63BitValue
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.cert
import net.corda.core.internal.read import net.corda.core.internal.read
import net.corda.core.internal.write
import net.corda.core.internal.x500Name import net.corda.core.internal.x500Name
import net.corda.core.utilities.days import net.corda.core.utilities.days
import net.corda.core.utilities.millis import net.corda.core.utilities.millis
@ -27,10 +27,8 @@ import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder
import org.bouncycastle.pkcs.PKCS10CertificationRequest import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder
import org.bouncycastle.util.io.pem.PemReader import org.bouncycastle.util.io.pem.PemReader
import java.io.FileWriter
import java.io.InputStream import java.io.InputStream
import java.math.BigInteger import java.math.BigInteger
import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import java.security.KeyPair import java.security.KeyPair
import java.security.PublicKey import java.security.PublicKey
@ -164,7 +162,7 @@ object X509Utilities {
* @param file Target file. * @param file Target file.
*/ */
@JvmStatic @JvmStatic
fun saveCertificateAsPEMFile(x509Certificate: X509CertificateHolder, file: Path) { fun saveCertificateAsPEMFile(x509Certificate: X509Certificate, file: Path) {
JcaPEMWriter(file.toFile().writer()).use { JcaPEMWriter(file.toFile().writer()).use {
it.writeObject(x509Certificate) it.writeObject(x509Certificate)
} }
@ -176,14 +174,14 @@ object X509Utilities {
* @return The X509Certificate that was encoded in the file. * @return The X509Certificate that was encoded in the file.
*/ */
@JvmStatic @JvmStatic
fun loadCertificateFromPEMFile(file: Path): X509CertificateHolder { fun loadCertificateFromPEMFile(file: Path): X509Certificate {
val cert = file.read { return file.read {
val reader = PemReader(it.reader()) val reader = PemReader(it.reader())
val pemObject = reader.readPemObject() val pemObject = reader.readPemObject()
X509CertificateHolder(pemObject.content) val certHolder = X509CertificateHolder(pemObject.content)
certHolder.isValidOn(Date())
certHolder.cert
} }
cert.isValidOn(Date())
return cert
} }
/** /**

View File

@ -71,7 +71,7 @@ class X509UtilitiesTest {
fun `load and save a PEM file certificate`() { fun `load and save a PEM file certificate`() {
val tmpCertificateFile = tempFile("cacert.pem") val tmpCertificateFile = tempFile("cacert.pem")
val caKey = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val caKey = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val caCert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Test Cert", organisation = "R3 Ltd", locality = "London", country = "GB"), caKey) val caCert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Test Cert", organisation = "R3 Ltd", locality = "London", country = "GB"), caKey).cert
X509Utilities.saveCertificateAsPEMFile(caCert, tmpCertificateFile) X509Utilities.saveCertificateAsPEMFile(caCert, tmpCertificateFile)
val readCertificate = X509Utilities.loadCertificateFromPEMFile(tmpCertificateFile) val readCertificate = X509Utilities.loadCertificateFromPEMFile(tmpCertificateFile)
assertEquals(caCert, readCertificate) assertEquals(caCert, readCertificate)

View File

@ -4,85 +4,96 @@ import net.corda.core.node.NodeInfo
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.testing.ALICE import net.corda.testing.ALICE
import net.corda.testing.BOB import net.corda.testing.BOB
import net.corda.testing.driver.CompatibilityZoneParams
import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.PortAllocation import net.corda.testing.driver.PortAllocation
import net.corda.testing.driver.driver import net.corda.testing.driver.internalDriver
import net.corda.testing.node.network.NetworkMapServer import net.corda.testing.node.network.NetworkMapServer
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test import org.junit.Test
import java.net.URL import java.net.URL
// TODO There is a unit test class with the same name. Rename this to something else.
class NetworkMapClientTest { class NetworkMapClientTest {
private val cacheTimeout = 1.seconds
private val portAllocation = PortAllocation.Incremental(10000) private val portAllocation = PortAllocation.Incremental(10000)
private lateinit var networkMapServer: NetworkMapServer
private lateinit var compatibilityZone: CompatibilityZoneParams
@Before
fun start() {
networkMapServer = NetworkMapServer(cacheTimeout, portAllocation.nextHostAndPort())
val address = networkMapServer.start()
compatibilityZone = CompatibilityZoneParams(URL("http://$address"), rootCert = null)
}
@After
fun cleanUp() {
networkMapServer.close()
}
@Test @Test
fun `nodes can see each other using the http network map`() { fun `nodes can see each other using the http network map`() {
NetworkMapServer(1.seconds, portAllocation.nextHostAndPort()).use { internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone) {
val (host, port) = it.start() val alice = startNode(providedName = ALICE.name)
driver(portAllocation = portAllocation, compatibilityZoneURL = URL("http://$host:$port")) { val bob = startNode(providedName = BOB.name)
val alice = startNode(providedName = ALICE.name)
val bob = startNode(providedName = BOB.name)
val notaryNode = defaultNotaryNode.get() val notaryNode = defaultNotaryNode.get()
val aliceNode = alice.get() val aliceNode = alice.get()
val bobNode = bob.get() val bobNode = bob.get()
notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
}
} }
} }
@Test @Test
fun `nodes process network map add updates correctly when adding new node to network map`() { fun `nodes process network map add updates correctly when adding new node to network map`() {
NetworkMapServer(1.seconds, portAllocation.nextHostAndPort()).use { internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone) {
val (host, port) = it.start() val alice = startNode(providedName = ALICE.name)
driver(portAllocation = portAllocation, compatibilityZoneURL = URL("http://$host:$port")) { val notaryNode = defaultNotaryNode.get()
val alice = startNode(providedName = ALICE.name) val aliceNode = alice.get()
val notaryNode = defaultNotaryNode.get()
val aliceNode = alice.get()
notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo) notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo)
aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo) aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo)
val bob = startNode(providedName = BOB.name) val bob = startNode(providedName = BOB.name)
val bobNode = bob.get() val bobNode = bob.get()
// Wait for network map client to poll for the next update. // Wait for network map client to poll for the next update.
Thread.sleep(2.seconds.toMillis()) Thread.sleep(cacheTimeout.toMillis() * 2)
bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
}
} }
} }
@Test @Test
fun `nodes process network map remove updates correctly`() { fun `nodes process network map remove updates correctly`() {
NetworkMapServer(1.seconds, portAllocation.nextHostAndPort()).use { internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone) {
val (host, port) = it.start() val alice = startNode(providedName = ALICE.name)
driver(portAllocation = portAllocation, compatibilityZoneURL = URL("http://$host:$port")) { val bob = startNode(providedName = BOB.name)
val alice = startNode(providedName = ALICE.name)
val bob = startNode(providedName = BOB.name)
val notaryNode = defaultNotaryNode.get() val notaryNode = defaultNotaryNode.get()
val aliceNode = alice.get() val aliceNode = alice.get()
val bobNode = bob.get() val bobNode = bob.get()
notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
it.removeNodeInfo(aliceNode.nodeInfo) networkMapServer.removeNodeInfo(aliceNode.nodeInfo)
// Wait for network map client to poll for the next update. // Wait for network map client to poll for the next update.
Thread.sleep(2.seconds.toMillis()) Thread.sleep(cacheTimeout.toMillis() * 2)
notaryNode.onlySees(notaryNode.nodeInfo, bobNode.nodeInfo) notaryNode.onlySees(notaryNode.nodeInfo, bobNode.nodeInfo)
bobNode.onlySees(notaryNode.nodeInfo, bobNode.nodeInfo) bobNode.onlySees(notaryNode.nodeInfo, bobNode.nodeInfo)
}
} }
} }

View File

@ -4,7 +4,7 @@ import net.corda.core.crypto.Crypto
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.cert import net.corda.core.internal.cert
import net.corda.core.internal.toX509CertHolder import net.corda.core.internal.toX509CertHolder
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.minutes import net.corda.core.utilities.minutes
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.CertificateType
@ -13,12 +13,11 @@ 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_CLIENT_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
import net.corda.testing.ALICE_NAME import net.corda.testing.driver.CompatibilityZoneParams
import net.corda.testing.driver.PortAllocation import net.corda.testing.driver.PortAllocation
import net.corda.testing.driver.driver import net.corda.testing.driver.internalDriver
import net.corda.testing.node.network.NetworkMapServer import net.corda.testing.node.network.NetworkMapServer
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.bouncycastle.pkcs.PKCS10CertificationRequest import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
import org.junit.After import org.junit.After
@ -30,37 +29,26 @@ import java.net.URL
import java.security.KeyPair import java.security.KeyPair
import java.security.cert.CertPath import java.security.cert.CertPath
import java.security.cert.Certificate import java.security.cert.Certificate
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream import java.util.zip.ZipOutputStream
import javax.ws.rs.* import javax.ws.rs.*
import javax.ws.rs.core.MediaType import javax.ws.rs.core.MediaType
import javax.ws.rs.core.Response import javax.ws.rs.core.Response
private const val REQUEST_ID = "requestId" // TODO Rename this to NodeRegistrationTest
private val x509CertificateFactory = X509CertificateFactory()
private val portAllocation = PortAllocation.Incremental(13000)
/**
* Driver based tests for [NetworkRegistrationHelper]
*/
class NetworkRegistrationHelperDriverTest { class NetworkRegistrationHelperDriverTest {
val rootCertAndKeyPair = createSelfKeyAndSelfSignedCertificate() private val portAllocation = PortAllocation.Incremental(13000)
val rootCert = rootCertAndKeyPair.certificate private val rootCertAndKeyPair = createSelfKeyAndSelfSignedCertificate()
val handler = RegistrationHandler(rootCertAndKeyPair) private val registrationHandler = RegistrationHandler(rootCertAndKeyPair)
lateinit var server: NetworkMapServer
lateinit var host: String private lateinit var server: NetworkMapServer
var port: Int = 0 private lateinit var compatibilityZone: CompatibilityZoneParams
val compatibilityZoneUrl get() = URL("http", host, port, "")
@Before @Before
fun startServer() { fun startServer() {
server = NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), handler) server = NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), registrationHandler)
val (host, port) = server.start() val address = server.start()
this.host = host compatibilityZone = CompatibilityZoneParams(URL("http://$address"), rootCertAndKeyPair.certificate.cert)
this.port = port
} }
@After @After
@ -68,115 +56,84 @@ class NetworkRegistrationHelperDriverTest {
server.close() server.close()
} }
// TODO Ideally this test should be checking that two nodes that register are able to transact with each other. However
// starting a second node hangs so that needs to be fixed.
@Test @Test
fun `node registration correct root cert`() { fun `node registration correct root cert`() {
driver(portAllocation = portAllocation, internalDriver(
compatibilityZoneURL = compatibilityZoneUrl, portAllocation = portAllocation,
startNodesInProcess = true, notarySpecs = emptyList(),
rootCertificate = rootCert compatibilityZone = compatibilityZone
) { ) {
startNode(providedName = ALICE_NAME, initialRegistration = true).get() startNode(providedName = CordaX500Name("Alice", "London", "GB")).getOrThrow()
} assertThat(registrationHandler.idsPolled).contains("Alice")
// 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 private fun createSelfKeyAndSelfSignedCertificate(): CertificateAndKeyPair {
fun `node registration wrong root cert`() { val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
driver(portAllocation = portAllocation, val rootCACert = X509Utilities.createSelfSignedCACertificate(
compatibilityZoneURL = compatibilityZoneUrl, CordaX500Name(
startNodesInProcess = true, commonName = "Integration Test Corda Node Root CA",
rootCertificate = createSelfKeyAndSelfSignedCertificate().certificate organisation = "R3 Ltd",
) { locality = "London",
assertThatThrownBy { country = "GB"),
startNode(providedName = ALICE_NAME, initialRegistration = true).get() rootCAKey)
}.isInstanceOf(WrongRootCaCertificateException::class.java) return CertificateAndKeyPair(rootCACert, rootCAKey)
}
} }
} }
/**
* Simple registration handler which can handle a single request, which will be given request id [REQUEST_ID].
*/
@Path("certificate") @Path("certificate")
class RegistrationHandler(private val certificateAndKeyPair: CertificateAndKeyPair) { class RegistrationHandler(private val rootCertAndKeyPair: CertificateAndKeyPair) {
val requests = mutableListOf<String>() private val certPaths = HashMap<String, CertPath>()
lateinit var certificationRequest: JcaPKCS10CertificationRequest val idsPolled = HashSet<String>()
@POST @POST
@Consumes(MediaType.APPLICATION_OCTET_STREAM) @Consumes(MediaType.APPLICATION_OCTET_STREAM)
@Produces(MediaType.TEXT_PLAIN) @Produces(MediaType.TEXT_PLAIN)
fun registration(input: InputStream): Response { fun registration(input: InputStream): Response {
requests += "/certificate" val certificationRequest = input.use { JcaPKCS10CertificationRequest(it.readBytes()) }
certificationRequest = input.use { JcaPKCS10CertificationRequest(it.readBytes()) } val (certPath, name) = createSignedClientCertificate(
return Response.ok(REQUEST_ID).build() certificationRequest,
rootCertAndKeyPair.keyPair,
arrayOf(rootCertAndKeyPair.certificate.cert))
certPaths[name.organisation] = certPath
return Response.ok(name.organisation).build()
} }
@GET @GET
@Path(REQUEST_ID) @Path("{id}")
fun reply(): Response { fun reply(@PathParam("id") id: String): Response {
requests += "/certificate/" + REQUEST_ID idsPolled += id
val certPath = createSignedClientCertificate(certificationRequest, return buildResponse(certPaths[id]!!.certificates)
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 private fun buildResponse(certificates: List<Certificate>): Response {
// can depend on val baos = ByteArrayOutputStream()
private fun createSignedClientCertificate(certificationRequest: PKCS10CertificationRequest, ZipOutputStream(baos).use { zip ->
caKeyPair: KeyPair, listOf(CORDA_CLIENT_CA, CORDA_INTERMEDIATE_CA, CORDA_ROOT_CA).zip(certificates).forEach {
caCertPath: Array<Certificate>): CertPath { zip.putNextEntry(ZipEntry("${it.first}.cer"))
val request = JcaPKCS10CertificationRequest(certificationRequest) zip.write(it.second.encoded)
val x509CertificateHolder = X509Utilities.createCertificate(CertificateType.CLIENT_CA, zip.closeEntry()
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()
} }
return Response.ok(baos.toByteArray())
.type("application/zip")
.header("Content-Disposition", "attachment; filename=\"certificates.zip\"").build()
}
private fun createSelfKeyAndSelfSignedCertificate(): CertificateAndKeyPair { private fun createSignedClientCertificate(certificationRequest: PKCS10CertificationRequest,
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) caKeyPair: KeyPair,
val rootCACert = X509Utilities.createSelfSignedCACertificate( caCertPath: Array<Certificate>): Pair<CertPath, CordaX500Name> {
CordaX500Name(commonName = "Integration Test Corda Node Root CA", val request = JcaPKCS10CertificationRequest(certificationRequest)
organisation = "R3 Ltd", locality = "London", val name = CordaX500Name.parse(request.subject.toString())
country = "GB"), rootCAKey) val x509CertificateHolder = X509Utilities.createCertificate(CertificateType.CLIENT_CA,
return CertificateAndKeyPair(rootCACert, rootCAKey) caCertPath.first().toX509CertHolder(),
caKeyPair,
name,
request.publicKey,
nameConstraints = null)
val certPath = X509CertificateFactory().generateCertPath(x509CertificateHolder.cert, *caCertPath)
return Pair(certPath, name)
}
} }

View File

@ -28,10 +28,18 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
val SELF_SIGNED_PRIVATE_KEY = "Self Signed Private Key" val SELF_SIGNED_PRIVATE_KEY = "Self Signed Private Key"
} }
init {
require(config.rootCertFile.exists()) {
"${config.rootCertFile} does not exist. This file must contain the root CA cert of your compatibility zone. " +
"Please contact your CZ operator."
}
}
private val requestIdStore = config.certificatesDirectory / "certificate-request-id.txt" private val requestIdStore = config.certificatesDirectory / "certificate-request-id.txt"
private val keystorePassword = config.keyStorePassword private val keystorePassword = config.keyStorePassword
// TODO: Use different password for private key. // TODO: Use different password for private key.
private val privateKeyPassword = config.keyStorePassword private val privateKeyPassword = config.keyStorePassword
private val rootCert = X509Utilities.loadCertificateFromPEMFile(config.rootCertFile)
/** /**
* Ensure the initial keystore for a node is set up; note that this function may cause the process to exit under * Ensure the initial keystore for a node is set up; note that this function may cause the process to exit under
@ -106,12 +114,11 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
/** /**
* Checks that the passed Certificate is the expected root CA. * Checks that the passed Certificate is the expected root CA.
* @throws WrongRootCaCertificateException if the certificates don't match. * @throws WrongRootCertException if the certificates don't match.
*/ */
private fun checkReturnedRootCaMatchesExpectedCa(returnedRootCa: Certificate) { private fun checkReturnedRootCaMatchesExpectedCa(returnedRootCa: Certificate) {
val expected = X509Utilities.loadCertificateFromPEMFile(config.rootCaCertFile).cert if (rootCert != returnedRootCa) {
if (expected != returnedRootCa) { throw WrongRootCertException(rootCert, returnedRootCa, config.rootCertFile)
throw WrongRootCaCertificateException(expected, returnedRootCa, config.rootCaCertFile)
} }
} }
@ -173,9 +180,9 @@ 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. * 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. * This usually means the has been a Man-in-the-middle attack when contacting the doorman.
*/ */
class WrongRootCaCertificateException(expected: Certificate, class WrongRootCertException(expected: Certificate,
actual: Certificate, actual: Certificate,
expectedFilePath: Path): expectedFilePath: Path):
Exception(""" Exception("""
The Root CA returned back from the registration process does not match the expected Root CA The Root CA returned back from the registration process does not match the expected Root CA
expected: $expected expected: $expected

View File

@ -1,53 +1,56 @@
package net.corda.node.utilities.registration package net.corda.node.utilities.registration
import com.nhaarman.mockito_kotlin.* import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.eq
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.* import net.corda.core.internal.*
import net.corda.node.services.config.NodeConfiguration
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.getX509Certificate import net.corda.nodeapi.internal.crypto.getX509Certificate
import net.corda.nodeapi.internal.crypto.loadKeyStore import net.corda.nodeapi.internal.crypto.loadKeyStore
import net.corda.testing.ALICE import net.corda.testing.ALICE
import net.corda.testing.rigorousMock import net.corda.testing.rigorousMock
import net.corda.testing.testNodeConfiguration import net.corda.testing.testNodeConfiguration
import org.bouncycastle.asn1.x500.X500Name import org.assertj.core.api.Assertions.assertThatThrownBy
import org.bouncycastle.asn1.x500.style.BCStyle import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.TemporaryFolder import org.junit.rules.TemporaryFolder
import java.security.cert.Certificate
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFalse import kotlin.test.assertFalse
import kotlin.test.assertTrue import kotlin.test.assertTrue
val X500Name.commonName: String? get() = getRDNs(BCStyle.CN).firstOrNull()?.first?.value?.toString()
class NetworkRegistrationHelperTest { class NetworkRegistrationHelperTest {
@Rule @Rule
@JvmField @JvmField
val tempFolder = TemporaryFolder() val tempFolder = TemporaryFolder()
@Test private val requestId = SecureHash.randomSHA256().toString()
fun buildKeyStore() { private lateinit var config: NodeConfiguration
val id = SecureHash.randomSHA256().toString()
@Before
fun init() {
config = testNodeConfiguration(baseDirectory = tempFolder.root.toPath(), myLegalName = ALICE.name)
}
@Test
fun `successful registration`() {
val identities = listOf("CORDA_CLIENT_CA", val identities = listOf("CORDA_CLIENT_CA",
"CORDA_INTERMEDIATE_CA", "CORDA_INTERMEDIATE_CA",
"CORDA_ROOT_CA") "CORDA_ROOT_CA")
.map { CordaX500Name(commonName = it, organisation = "R3 Ltd", locality = "London", country = "GB") } .map { CordaX500Name(commonName = it, organisation = "R3 Ltd", locality = "London", country = "GB") }
val certs = identities.stream().map { X509Utilities.createSelfSignedCACertificate(it, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) } val certs = identities.stream().map { X509Utilities.createSelfSignedCACertificate(it, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) }
.map { it.cert }.toTypedArray() .map { it.cert }.toTypedArray()
val certService = rigorousMock<NetworkRegistrationService>().also {
doReturn(id).whenever(it).submitRequest(any())
doReturn(certs).whenever(it).retrieveCertificates(eq(id))
}
val config = testNodeConfiguration( val certService = mockRegistrationResponse(*certs)
baseDirectory = tempFolder.root.toPath(),
myLegalName = ALICE.name)
config.rootCaCertFile.parent.createDirectories() config.rootCertFile.parent.createDirectories()
X509Utilities.saveCertificateAsPEMFile(certs.last().toX509CertHolder(), config.rootCaCertFile) X509Utilities.saveCertificateAsPEMFile(certs.last(), config.rootCertFile)
assertFalse(config.nodeKeystore.exists()) assertFalse(config.nodeKeystore.exists())
assertFalse(config.sslKeystore.exists()) assertFalse(config.sslKeystore.exists())
@ -92,4 +95,44 @@ class NetworkRegistrationHelperTest {
assertTrue(containsAlias(X509Utilities.CORDA_ROOT_CA)) assertTrue(containsAlias(X509Utilities.CORDA_ROOT_CA))
} }
} }
@Test
fun `rootCertFile doesn't exist`() {
val certService = rigorousMock<NetworkRegistrationService>()
assertThatThrownBy {
NetworkRegistrationHelper(config, certService)
}.hasMessageContaining(config.rootCertFile.toString())
}
@Test
fun `root cert in response doesn't match expected`() {
val identities = listOf("CORDA_CLIENT_CA",
"CORDA_INTERMEDIATE_CA",
"CORDA_ROOT_CA")
.map { CordaX500Name(commonName = it, organisation = "R3 Ltd", locality = "London", country = "GB") }
val certs = identities.stream().map { X509Utilities.createSelfSignedCACertificate(it, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) }
.map { it.cert }.toTypedArray()
val certService = mockRegistrationResponse(*certs)
config.rootCertFile.parent.createDirectories()
X509Utilities.saveCertificateAsPEMFile(
X509Utilities.createSelfSignedCACertificate(
CordaX500Name("CORDA_ROOT_CA", "R3 Ltd", "London", "GB"),
Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)).cert,
config.rootCertFile
)
assertThatThrownBy {
NetworkRegistrationHelper(config, certService).buildKeystore()
}.isInstanceOf(WrongRootCertException::class.java)
}
private fun mockRegistrationResponse(vararg response: Certificate): NetworkRegistrationService {
return rigorousMock<NetworkRegistrationService>().also {
doReturn(requestId).whenever(it).submitRequest(any())
doReturn(response).whenever(it).retrieveCertificates(eq(requestId))
}
}
} }

View File

@ -76,8 +76,8 @@ class DriverTests {
assertThat(baseDirectory / "process-id").exists() assertThat(baseDirectory / "process-id").exists()
} }
val baseDirectory = driver(notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name))) { val baseDirectory = internalDriver(notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name))) {
(this as DriverDSL).baseDirectory(DUMMY_NOTARY.name) baseDirectory(DUMMY_NOTARY.name)
} }
assertThat(baseDirectory / "process-id").doesNotExist() assertThat(baseDirectory / "process-id").doesNotExist()
} }

View File

@ -48,7 +48,6 @@ 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
@ -57,6 +56,7 @@ import java.net.*
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
import java.nio.file.StandardCopyOption.REPLACE_EXISTING import java.nio.file.StandardCopyOption.REPLACE_EXISTING
import java.security.cert.X509Certificate
import java.time.Duration import java.time.Duration
import java.time.Instant import java.time.Instant
import java.time.ZoneOffset.UTC import java.time.ZoneOffset.UTC
@ -165,8 +165,8 @@ interface DriverDSLExposedInterface : CordformContext {
verifierType: VerifierType = defaultParameters.verifierType, verifierType: VerifierType = defaultParameters.verifierType,
customOverrides: Map<String, Any?> = defaultParameters.customOverrides, customOverrides: Map<String, Any?> = defaultParameters.customOverrides,
startInSameProcess: Boolean? = defaultParameters.startInSameProcess, startInSameProcess: Boolean? = defaultParameters.startInSameProcess,
maximumHeapSize: String = defaultParameters.maximumHeapSize, maximumHeapSize: String = defaultParameters.maximumHeapSize
initialRegistration: Boolean = defaultParameters.initialRegistration): CordaFuture<NodeHandle> ): CordaFuture<NodeHandle>
/** /**
@ -301,8 +301,7 @@ data class NodeParameters(
val verifierType: VerifierType = VerifierType.InMemory, val verifierType: VerifierType = VerifierType.InMemory,
val customOverrides: Map<String, Any?> = emptyMap(), val customOverrides: Map<String, Any?> = emptyMap(),
val startInSameProcess: Boolean? = null, val startInSameProcess: Boolean? = null,
val maximumHeapSize: String = "200m", val maximumHeapSize: String = "200m"
val initialRegistration: Boolean = false
) { ) {
fun setProvidedName(providedName: CordaX500Name?) = copy(providedName = providedName) fun setProvidedName(providedName: CordaX500Name?) = copy(providedName = providedName)
fun setRpcUsers(rpcUsers: List<User>) = copy(rpcUsers = rpcUsers) fun setRpcUsers(rpcUsers: List<User>) = copy(rpcUsers = rpcUsers)
@ -339,13 +338,9 @@ data class NodeParameters(
* not. Note that this may be overridden in [DriverDSLExposedInterface.startNode]. * not. Note that this may be overridden in [DriverDSLExposedInterface.startNode].
* @param notarySpecs The notaries advertised for this network. These nodes will be started automatically and will be * @param notarySpecs The notaries advertised for this network. These nodes will be started automatically and will be
* 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),
* 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,
@ -359,8 +354,6 @@ fun <A> driver(
waitForAllNodesToFinish: Boolean = defaultParameters.waitForNodesToFinish, waitForAllNodesToFinish: Boolean = defaultParameters.waitForNodesToFinish,
notarySpecs: List<NotarySpec> = defaultParameters.notarySpecs, notarySpecs: List<NotarySpec> = defaultParameters.notarySpecs,
extraCordappPackagesToScan: List<String> = defaultParameters.extraCordappPackagesToScan, extraCordappPackagesToScan: List<String> = defaultParameters.extraCordappPackagesToScan,
compatibilityZoneURL: URL? = defaultParameters.compatibilityZoneURL,
rootCertificate: X509CertificateHolder? = defaultParameters.rootCertificate,
dsl: DriverDSLExposedInterface.() -> A dsl: DriverDSLExposedInterface.() -> A
): A { ): A {
return genericDriver( return genericDriver(
@ -375,8 +368,51 @@ fun <A> driver(
waitForNodesToFinish = waitForAllNodesToFinish, waitForNodesToFinish = waitForAllNodesToFinish,
notarySpecs = notarySpecs, notarySpecs = notarySpecs,
extraCordappPackagesToScan = extraCordappPackagesToScan, extraCordappPackagesToScan = extraCordappPackagesToScan,
compatibilityZoneURL = compatibilityZoneURL, compatibilityZone = null
rootCertificate = rootCertificate ),
coerce = { it },
dsl = dsl,
initialiseSerialization = initialiseSerialization
)
}
// TODO Move CompatibilityZoneParams and internalDriver into internal package
/**
* @property url The base CZ URL for registration and network map updates
* @property rootCert If specified then the node will register itself using [url] and expect the registration response
* to be rooted at this cert.
*/
data class CompatibilityZoneParams(val url: URL, val rootCert: X509Certificate?)
fun <A> internalDriver(
isDebug: Boolean = DriverParameters().isDebug,
driverDirectory: Path = DriverParameters().driverDirectory,
portAllocation: PortAllocation = DriverParameters().portAllocation,
debugPortAllocation: PortAllocation = DriverParameters().debugPortAllocation,
systemProperties: Map<String, String> = DriverParameters().systemProperties,
useTestClock: Boolean = DriverParameters().useTestClock,
initialiseSerialization: Boolean = DriverParameters().initialiseSerialization,
startNodesInProcess: Boolean = DriverParameters().startNodesInProcess,
waitForAllNodesToFinish: Boolean = DriverParameters().waitForNodesToFinish,
notarySpecs: List<NotarySpec> = DriverParameters().notarySpecs,
extraCordappPackagesToScan: List<String> = DriverParameters().extraCordappPackagesToScan,
compatibilityZone: CompatibilityZoneParams? = null,
dsl: DriverDSL.() -> A
): A {
return genericDriver(
driverDsl = DriverDSL(
portAllocation = portAllocation,
debugPortAllocation = debugPortAllocation,
systemProperties = systemProperties,
driverDirectory = driverDirectory.toAbsolutePath(),
useTestClock = useTestClock,
isDebug = isDebug,
startNodesInProcess = startNodesInProcess,
waitForNodesToFinish = waitForAllNodesToFinish,
notarySpecs = notarySpecs,
extraCordappPackagesToScan = extraCordappPackagesToScan,
compatibilityZone = compatibilityZone
), ),
coerce = { it }, coerce = { it },
dsl = dsl, dsl = dsl,
@ -411,9 +447,7 @@ data class DriverParameters(
val startNodesInProcess: Boolean = false, val startNodesInProcess: Boolean = false,
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 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)
@ -426,8 +460,6 @@ data class DriverParameters(
fun setTerminateNodesOnShutdown(terminateNodesOnShutdown: Boolean) = copy(waitForNodesToFinish = terminateNodesOnShutdown) fun setTerminateNodesOnShutdown(terminateNodesOnShutdown: Boolean) = copy(waitForNodesToFinish = terminateNodesOnShutdown)
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 setExtraCordappPackagesToScan(extraCordappPackagesToScan: List<String>) = copy(extraCordappPackagesToScan = extraCordappPackagesToScan)
fun setCompatibilityZoneURL(compatibilityZoneURL: URL?) = copy(compatibilityZoneURL = compatibilityZoneURL)
fun setRootCertificate(rootCertificate: X509CertificateHolder?) = copy(rootCertificate = rootCertificate)
} }
/** /**
@ -480,10 +512,9 @@ fun <DI : DriverDSLExposedInterface, D : DriverDSLInternalInterface, A> genericD
startNodesInProcess: Boolean = defaultParameters.startNodesInProcess, startNodesInProcess: Boolean = defaultParameters.startNodesInProcess,
notarySpecs: List<NotarySpec>, notarySpecs: List<NotarySpec>,
extraCordappPackagesToScan: List<String> = defaultParameters.extraCordappPackagesToScan, extraCordappPackagesToScan: List<String> = defaultParameters.extraCordappPackagesToScan,
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 {
val serializationEnv = setGlobalSerialization(initialiseSerialization) val serializationEnv = setGlobalSerialization(initialiseSerialization)
val driverDsl = driverDslWrapper( val driverDsl = driverDslWrapper(
@ -498,8 +529,7 @@ fun <DI : DriverDSLExposedInterface, D : DriverDSLInternalInterface, A> genericD
waitForNodesToFinish = waitForNodesToFinish, waitForNodesToFinish = waitForNodesToFinish,
extraCordappPackagesToScan = extraCordappPackagesToScan, extraCordappPackagesToScan = extraCordappPackagesToScan,
notarySpecs = notarySpecs, notarySpecs = notarySpecs,
compatibilityZoneURL = compatibilityZoneURL, compatibilityZone = null
rootCertificate = rootCertificate
) )
) )
val shutdownHook = addShutdownHook(driverDsl::shutdown) val shutdownHook = addShutdownHook(driverDsl::shutdown)
@ -606,8 +636,7 @@ 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 compatibilityZone: CompatibilityZoneParams?
val rootCertificate: X509CertificateHolder?
) : DriverDSLInternalInterface { ) : DriverDSLInternalInterface {
private var _executorService: ScheduledExecutorService? = null private var _executorService: ScheduledExecutorService? = null
val executorService get() = _executorService!! val executorService get() = _executorService!!
@ -679,16 +708,14 @@ class DriverDSL(
verifierType: VerifierType, verifierType: VerifierType,
customOverrides: Map<String, Any?>, customOverrides: Map<String, Any?>,
startInSameProcess: Boolean?, startInSameProcess: Boolean?,
maximumHeapSize: String, maximumHeapSize: String
initialRegistration: Boolean
): CordaFuture<NodeHandle> { ): CordaFuture<NodeHandle> {
val p2pAddress = portAllocation.nextHostAndPort() val p2pAddress = portAllocation.nextHostAndPort()
// TODO: Derive name from the full picked name, don't just wrap the common name // TODO: Derive name from the full picked name, don't just wrap the common name
val name = providedName ?: CordaX500Name(organisation = "${oneOf(names).organisation}-${p2pAddress.port}", locality = "London", country = "GB") val name = providedName ?: CordaX500Name(organisation = "${oneOf(names).organisation}-${p2pAddress.port}", locality = "London", country = "GB")
val registrationFuture = if (initialRegistration) { val registrationFuture = if (compatibilityZone?.rootCert != null) {
compatibilityZoneURL ?: throw IllegalArgumentException("Compatibility zone URL must be provided for initial registration.") nodeRegistration(name, compatibilityZone.rootCert, compatibilityZone.url)
registerNode(name, compatibilityZoneURL)
} else { } else {
doneFuture(Unit) doneFuture(Unit)
} }
@ -709,8 +736,8 @@ class DriverDSL(
val config = ConfigHelper.loadConfig( val config = ConfigHelper.loadConfig(
baseDirectory = baseDirectory(name), baseDirectory = baseDirectory(name),
allowMissingConfig = true, allowMissingConfig = true,
configOverrides = if (compatibilityZoneURL != null) { configOverrides = if (compatibilityZone != null) {
configMap + mapOf("compatibilityZoneURL" to compatibilityZoneURL.toString()) configMap + mapOf("compatibilityZoneURL" to compatibilityZone.url.toString())
} else { } else {
configMap configMap
} }
@ -719,14 +746,10 @@ class DriverDSL(
} }
} }
private fun writeRootCaCertificateForNode(path: Path, caRootCertificate: X509CertificateHolder) { private fun nodeRegistration(providedName: CordaX500Name, rootCert: X509Certificate, compatibilityZoneURL: URL): CordaFuture<Unit> {
path.parent.createDirectories() val baseDirectory = baseDirectory(providedName).createDirectories()
X509Utilities.saveCertificateAsPEMFile(caRootCertificate, path)
}
private fun registerNode(providedName: CordaX500Name, compatibilityZoneURL: URL): CordaFuture<Unit> {
val config = ConfigHelper.loadConfig( val config = ConfigHelper.loadConfig(
baseDirectory = baseDirectory(providedName), baseDirectory = baseDirectory,
allowMissingConfig = true, allowMissingConfig = true,
configOverrides = configOf( configOverrides = configOf(
"p2pAddress" to "localhost:1222", // required argument, not really used "p2pAddress" to "localhost:1222", // required argument, not really used
@ -734,16 +757,17 @@ class DriverDSL(
"myLegalName" to providedName.toString()) "myLegalName" to providedName.toString())
) )
val configuration = config.parseAsNodeConfiguration() val configuration = config.parseAsNodeConfiguration()
// If a rootCertificate is specified, put that in the node expected path.
rootCertificate?.let { writeRootCaCertificateForNode(configuration.rootCaCertFile, it) } configuration.rootCertFile.parent.createDirectories()
if (startNodesInProcess) { X509Utilities.saveCertificateAsPEMFile(rootCert, configuration.rootCertFile)
return 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.
NetworkRegistrationHelper(configuration, HTTPNetworkRegistrationService(compatibilityZoneURL)) NetworkRegistrationHelper(configuration, HTTPNetworkRegistrationService(compatibilityZoneURL)).buildKeystore()
.buildKeystore() doneFuture(Unit)
return doneFuture(Unit)
} else { } else {
return startNodeForRegistration(config) startOutOfProcessNodeRegistration(config, configuration)
} }
} }
@ -872,18 +896,18 @@ 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" customRootCert = compatibilityZone?.rootCert
) )
} 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
) ),
customRootCert = compatibilityZone?.rootCert
) )
} }
NotaryInfo(identity, spec.validating) NotaryInfo(identity, spec.validating)
@ -1008,16 +1032,12 @@ class DriverDSL(
return future return future
} }
private fun startNodeForRegistration(config: Config): CordaFuture<Unit> { private fun startOutOfProcessNodeRegistration(config: Config, configuration: NodeConfiguration): CordaFuture<Unit> {
val maximumHeapSize = "200m"
val configuration = config.parseAsNodeConfiguration()
configuration.baseDirectory.createDirectories()
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
val process = startOutOfProcessNode(configuration, config, quasarJarPath, debugPort, val process = startOutOfProcessNode(configuration, config, quasarJarPath, debugPort,
systemProperties, cordappPackages, maximumHeapSize, initialRegistration = true) systemProperties, cordappPackages, "200m", initialRegistration = true)
return poll(executorService, "process exit") { return poll(executorService, "node registration (${configuration.myLegalName})") {
if (process.isAlive) null else Unit if (process.isAlive) null else Unit
} }
} }
@ -1030,7 +1050,7 @@ class DriverDSL(
val baseDirectory = configuration.baseDirectory.createDirectories() val baseDirectory = configuration.baseDirectory.createDirectories()
// Distribute node info file using file copier when network map service URL (compatibilityZoneURL) is null. // Distribute node info file using file copier when network map service URL (compatibilityZoneURL) is null.
// TODO: need to implement the same in cordformation? // TODO: need to implement the same in cordformation?
val nodeInfoFilesCopier = if (compatibilityZoneURL == null) nodeInfoFilesCopier else null val nodeInfoFilesCopier = if (compatibilityZone == null) nodeInfoFilesCopier else null
nodeInfoFilesCopier?.addConfig(baseDirectory) nodeInfoFilesCopier?.addConfig(baseDirectory)
networkParameters!!.install(baseDirectory) networkParameters!!.install(baseDirectory)
@ -1068,7 +1088,7 @@ class DriverDSL(
} }
val p2pReadyFuture = addressMustBeBoundFuture(executorService, configuration.p2pAddress, process) val p2pReadyFuture = addressMustBeBoundFuture(executorService, configuration.p2pAddress, process)
return p2pReadyFuture.flatMap { return p2pReadyFuture.flatMap {
val processDeathFuture = poll(executorService, "process death") { val processDeathFuture = poll(executorService, "process death while waiting for RPC (${configuration.myLegalName})") {
if (process.isAlive) null else process if (process.isAlive) null else process
} }
establishRpc(configuration, processDeathFuture).flatMap { rpc -> establishRpc(configuration, processDeathFuture).flatMap { rpc ->

View File

@ -47,9 +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.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
import java.util.* import java.util.*
@ -225,6 +223,7 @@ val fakeNodeLegalName = CordaX500Name(organisation = "Not:a:real:name", locality
// Use a global pool so that we can run RPC tests in parallel // Use a global pool so that we can run RPC tests in parallel
private val globalPortAllocation = PortAllocation.Incremental(10000) private val globalPortAllocation = PortAllocation.Incremental(10000)
private val globalDebugPortAllocation = PortAllocation.Incremental(5005) private val globalDebugPortAllocation = PortAllocation.Incremental(5005)
fun <A> rpcDriver( fun <A> rpcDriver(
isDebug: Boolean = false, isDebug: Boolean = false,
driverDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()), driverDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()),
@ -237,8 +236,6 @@ fun <A> rpcDriver(
extraCordappPackagesToScan: List<String> = emptyList(), extraCordappPackagesToScan: List<String> = emptyList(),
notarySpecs: List<NotarySpec> = emptyList(), notarySpecs: List<NotarySpec> = emptyList(),
externalTrace: Trace? = null, externalTrace: Trace? = null,
compatibilityZoneURL: URL? = null,
rootCertificate: X509CertificateHolder? = null,
dsl: RPCDriverExposedDSLInterface.() -> A dsl: RPCDriverExposedDSLInterface.() -> A
) = genericDriver( ) = genericDriver(
driverDsl = RPCDriverDSL( driverDsl = RPCDriverDSL(
@ -253,8 +250,7 @@ fun <A> rpcDriver(
waitForNodesToFinish = waitForNodesToFinish, waitForNodesToFinish = waitForNodesToFinish,
extraCordappPackagesToScan = extraCordappPackagesToScan, extraCordappPackagesToScan = extraCordappPackagesToScan,
notarySpecs = notarySpecs, notarySpecs = notarySpecs,
compatibilityZoneURL = compatibilityZoneURL, compatibilityZone = null
rootCertificate = rootCertificate
), externalTrace ), externalTrace
), ),
coerce = { it }, coerce = { it },

View File

@ -6,9 +6,8 @@ import net.corda.cordform.CordformDefinition
import net.corda.cordform.CordformNode import net.corda.cordform.CordformNode
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.testing.driver.DriverDSL
import net.corda.testing.driver.PortAllocation import net.corda.testing.driver.PortAllocation
import net.corda.testing.driver.driver import net.corda.testing.driver.internalDriver
fun CordformDefinition.clean() { fun CordformDefinition.clean() {
System.err.println("Deleting: $nodesDirectory") System.err.println("Deleting: $nodesDirectory")
@ -39,7 +38,7 @@ private fun CordformDefinition.runNodes(waitForAllNodesToFinish: Boolean, block:
.flatMap { listOf(it.p2pAddress, it.rpcAddress, it.webAddress) } .flatMap { listOf(it.p2pAddress, it.rpcAddress, it.webAddress) }
.mapNotNull { address -> address?.let { NetworkHostAndPort.parse(it).port } } .mapNotNull { address -> address?.let { NetworkHostAndPort.parse(it).port } }
.max()!! .max()!!
driver( internalDriver(
isDebug = true, isDebug = true,
driverDirectory = nodesDirectory, driverDirectory = nodesDirectory,
extraCordappPackagesToScan = cordappPackages, extraCordappPackagesToScan = cordappPackages,
@ -50,7 +49,7 @@ private fun CordformDefinition.runNodes(waitForAllNodesToFinish: Boolean, block:
waitForAllNodesToFinish = waitForAllNodesToFinish waitForAllNodesToFinish = waitForAllNodesToFinish
) { ) {
setup(this) setup(this)
(this as DriverDSL).startCordformNodes(nodes).getOrThrow() // Only proceed once everything is up and running startCordformNodes(nodes).getOrThrow() // Only proceed once everything is up and running
println("All nodes and webservers are ready...") println("All nodes and webservers are ready...")
block() block()
} }

View File

@ -19,9 +19,9 @@ import net.corda.node.services.config.configureDevKeyAndTrustStores
import net.corda.nodeapi.ArtemisTcpTransport import net.corda.nodeapi.ArtemisTcpTransport
import net.corda.nodeapi.ConnectionDirection import net.corda.nodeapi.ConnectionDirection
import net.corda.nodeapi.VerifierApi import net.corda.nodeapi.VerifierApi
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
import net.corda.nodeapi.internal.config.NodeSSLConfiguration import net.corda.nodeapi.internal.config.NodeSSLConfiguration
import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
import net.corda.testing.driver.* import net.corda.testing.driver.*
import net.corda.testing.internal.ProcessUtilities import net.corda.testing.internal.ProcessUtilities
import net.corda.testing.node.NotarySpec import net.corda.testing.node.NotarySpec
@ -37,8 +37,6 @@ 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.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
@ -87,8 +85,6 @@ fun <A> verifierDriver(
waitForNodesToFinish: Boolean = false, waitForNodesToFinish: Boolean = false,
extraCordappPackagesToScan: List<String> = emptyList(), extraCordappPackagesToScan: List<String> = emptyList(),
notarySpecs: List<NotarySpec> = emptyList(), notarySpecs: List<NotarySpec> = emptyList(),
compatibilityZoneURL: URL? = null,
rootCertificate: X509CertificateHolder? = null,
dsl: VerifierExposedDSLInterface.() -> A dsl: VerifierExposedDSLInterface.() -> A
) = genericDriver( ) = genericDriver(
driverDsl = VerifierDriverDSL( driverDsl = VerifierDriverDSL(
@ -103,8 +99,7 @@ fun <A> verifierDriver(
waitForNodesToFinish = waitForNodesToFinish, waitForNodesToFinish = waitForNodesToFinish,
extraCordappPackagesToScan = extraCordappPackagesToScan, extraCordappPackagesToScan = extraCordappPackagesToScan,
notarySpecs = notarySpecs, notarySpecs = notarySpecs,
compatibilityZoneURL = compatibilityZoneURL, compatibilityZone = null
rootCertificate = rootCertificate
) )
), ),
coerce = { it }, coerce = { it },