mirror of
https://github.com/corda/corda.git
synced 2025-02-02 01:08:09 +00:00
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:
parent
66515d24b5
commit
89256a7f16
@ -9,13 +9,13 @@ import net.corda.core.internal.createDirectories
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.nodeapi.internal.crypto.*
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.nio.file.Path
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
object ServiceIdentityGenerator {
|
||||
private val log = LoggerFactory.getLogger(javaClass)
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@ -24,25 +24,21 @@ object ServiceIdentityGenerator {
|
||||
* @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 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.
|
||||
* @param customRootCert the certificate to use a Corda root CA. If not specified the one in
|
||||
* certificates/cordadevcakeys.jks is used.
|
||||
*/
|
||||
fun generateToDisk(dirs: List<Path>,
|
||||
serviceName: CordaX500Name,
|
||||
serviceId: String,
|
||||
threshold: Int = 1,
|
||||
rootCertertificate: X509CertificateHolder? = null): Party {
|
||||
customRootCert: X509Certificate? = null): Party {
|
||||
log.trace { "Generating a group identity \"serviceName\" for nodes: ${dirs.joinToString()}" }
|
||||
val keyPairs = (1..dirs.size).map { generateKeyPair() }
|
||||
val notaryKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold)
|
||||
|
||||
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||
val issuer = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
|
||||
val rootCert: Certificate = if (rootCertertificate != null) {
|
||||
rootCertertificate.cert
|
||||
} else {
|
||||
caKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA)
|
||||
}
|
||||
val rootCert = customRootCert ?: caKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA)
|
||||
|
||||
keyPairs.zip(dirs) { keyPair, dir ->
|
||||
val serviceKeyCert = X509Utilities.createCertificate(CertificateType.CLIENT_CA, issuer.certificate, issuer.keyPair, serviceName, keyPair.public)
|
||||
|
@ -15,5 +15,5 @@ interface SSLConfiguration {
|
||||
interface NodeSSLConfiguration : SSLConfiguration {
|
||||
val baseDirectory: Path
|
||||
override val certificatesDirectory: Path get() = baseDirectory / "certificates"
|
||||
val rootCaCertFile: Path get() = certificatesDirectory / "rootcacert.cer"
|
||||
val rootCertFile: Path get() = certificatesDirectory / "rootcert.pem"
|
||||
}
|
||||
|
@ -4,8 +4,8 @@ import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SignatureScheme
|
||||
import net.corda.core.crypto.random63BitValue
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.cert
|
||||
import net.corda.core.internal.read
|
||||
import net.corda.core.internal.write
|
||||
import net.corda.core.internal.x500Name
|
||||
import net.corda.core.utilities.days
|
||||
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.jcajce.JcaPKCS10CertificationRequestBuilder
|
||||
import org.bouncycastle.util.io.pem.PemReader
|
||||
import java.io.FileWriter
|
||||
import java.io.InputStream
|
||||
import java.math.BigInteger
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.security.KeyPair
|
||||
import java.security.PublicKey
|
||||
@ -164,7 +162,7 @@ object X509Utilities {
|
||||
* @param file Target file.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun saveCertificateAsPEMFile(x509Certificate: X509CertificateHolder, file: Path) {
|
||||
fun saveCertificateAsPEMFile(x509Certificate: X509Certificate, file: Path) {
|
||||
JcaPEMWriter(file.toFile().writer()).use {
|
||||
it.writeObject(x509Certificate)
|
||||
}
|
||||
@ -176,14 +174,14 @@ object X509Utilities {
|
||||
* @return The X509Certificate that was encoded in the file.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun loadCertificateFromPEMFile(file: Path): X509CertificateHolder {
|
||||
val cert = file.read {
|
||||
fun loadCertificateFromPEMFile(file: Path): X509Certificate {
|
||||
return file.read {
|
||||
val reader = PemReader(it.reader())
|
||||
val pemObject = reader.readPemObject()
|
||||
X509CertificateHolder(pemObject.content)
|
||||
val certHolder = X509CertificateHolder(pemObject.content)
|
||||
certHolder.isValidOn(Date())
|
||||
certHolder.cert
|
||||
}
|
||||
cert.isValidOn(Date())
|
||||
return cert
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -71,7 +71,7 @@ class X509UtilitiesTest {
|
||||
fun `load and save a PEM file certificate`() {
|
||||
val tmpCertificateFile = tempFile("cacert.pem")
|
||||
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)
|
||||
val readCertificate = X509Utilities.loadCertificateFromPEMFile(tmpCertificateFile)
|
||||
assertEquals(caCert, readCertificate)
|
||||
|
@ -4,85 +4,96 @@ import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.testing.ALICE
|
||||
import net.corda.testing.BOB
|
||||
import net.corda.testing.driver.CompatibilityZoneParams
|
||||
import net.corda.testing.driver.NodeHandle
|
||||
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 org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.net.URL
|
||||
|
||||
// TODO There is a unit test class with the same name. Rename this to something else.
|
||||
class NetworkMapClientTest {
|
||||
private val cacheTimeout = 1.seconds
|
||||
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
|
||||
fun `nodes can see each other using the http network map`() {
|
||||
NetworkMapServer(1.seconds, portAllocation.nextHostAndPort()).use {
|
||||
val (host, port) = it.start()
|
||||
driver(portAllocation = portAllocation, compatibilityZoneURL = URL("http://$host:$port")) {
|
||||
val alice = startNode(providedName = ALICE.name)
|
||||
val bob = startNode(providedName = BOB.name)
|
||||
internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone) {
|
||||
val alice = startNode(providedName = ALICE.name)
|
||||
val bob = startNode(providedName = BOB.name)
|
||||
|
||||
val notaryNode = defaultNotaryNode.get()
|
||||
val aliceNode = alice.get()
|
||||
val bobNode = bob.get()
|
||||
val notaryNode = defaultNotaryNode.get()
|
||||
val aliceNode = alice.get()
|
||||
val bobNode = bob.get()
|
||||
|
||||
notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
}
|
||||
notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `nodes process network map add updates correctly when adding new node to network map`() {
|
||||
NetworkMapServer(1.seconds, portAllocation.nextHostAndPort()).use {
|
||||
val (host, port) = it.start()
|
||||
driver(portAllocation = portAllocation, compatibilityZoneURL = URL("http://$host:$port")) {
|
||||
val alice = startNode(providedName = ALICE.name)
|
||||
val notaryNode = defaultNotaryNode.get()
|
||||
val aliceNode = alice.get()
|
||||
internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone) {
|
||||
val alice = startNode(providedName = ALICE.name)
|
||||
val notaryNode = defaultNotaryNode.get()
|
||||
val aliceNode = alice.get()
|
||||
|
||||
notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo)
|
||||
aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo)
|
||||
notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo)
|
||||
aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo)
|
||||
|
||||
val bob = startNode(providedName = BOB.name)
|
||||
val bobNode = bob.get()
|
||||
val bob = startNode(providedName = BOB.name)
|
||||
val bobNode = bob.get()
|
||||
|
||||
// Wait for network map client to poll for the next update.
|
||||
Thread.sleep(2.seconds.toMillis())
|
||||
// Wait for network map client to poll for the next update.
|
||||
Thread.sleep(cacheTimeout.toMillis() * 2)
|
||||
|
||||
bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
}
|
||||
bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `nodes process network map remove updates correctly`() {
|
||||
NetworkMapServer(1.seconds, portAllocation.nextHostAndPort()).use {
|
||||
val (host, port) = it.start()
|
||||
driver(portAllocation = portAllocation, compatibilityZoneURL = URL("http://$host:$port")) {
|
||||
val alice = startNode(providedName = ALICE.name)
|
||||
val bob = startNode(providedName = BOB.name)
|
||||
internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone) {
|
||||
val alice = startNode(providedName = ALICE.name)
|
||||
val bob = startNode(providedName = BOB.name)
|
||||
|
||||
val notaryNode = defaultNotaryNode.get()
|
||||
val aliceNode = alice.get()
|
||||
val bobNode = bob.get()
|
||||
val notaryNode = defaultNotaryNode.get()
|
||||
val aliceNode = alice.get()
|
||||
val bobNode = bob.get()
|
||||
|
||||
notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
aliceNode.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.
|
||||
Thread.sleep(2.seconds.toMillis())
|
||||
// Wait for network map client to poll for the next update.
|
||||
Thread.sleep(cacheTimeout.toMillis() * 2)
|
||||
|
||||
notaryNode.onlySees(notaryNode.nodeInfo, bobNode.nodeInfo)
|
||||
bobNode.onlySees(notaryNode.nodeInfo, bobNode.nodeInfo)
|
||||
}
|
||||
notaryNode.onlySees(notaryNode.nodeInfo, bobNode.nodeInfo)
|
||||
bobNode.onlySees(notaryNode.nodeInfo, bobNode.nodeInfo)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ 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.getOrThrow
|
||||
import net.corda.core.utilities.minutes
|
||||
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
||||
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_INTERMEDIATE_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.driver
|
||||
import net.corda.testing.driver.internalDriver
|
||||
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
|
||||
@ -30,37 +29,26 @@ 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]
|
||||
*/
|
||||
// TODO Rename this to NodeRegistrationTest
|
||||
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, "")
|
||||
private val portAllocation = PortAllocation.Incremental(13000)
|
||||
private val rootCertAndKeyPair = createSelfKeyAndSelfSignedCertificate()
|
||||
private val registrationHandler = RegistrationHandler(rootCertAndKeyPair)
|
||||
|
||||
private lateinit var server: NetworkMapServer
|
||||
private lateinit var compatibilityZone: CompatibilityZoneParams
|
||||
|
||||
@Before
|
||||
fun startServer() {
|
||||
server = NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), handler)
|
||||
val (host, port) = server.start()
|
||||
this.host = host
|
||||
this.port = port
|
||||
server = NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), registrationHandler)
|
||||
val address = server.start()
|
||||
compatibilityZone = CompatibilityZoneParams(URL("http://$address"), rootCertAndKeyPair.certificate.cert)
|
||||
}
|
||||
|
||||
@After
|
||||
@ -68,115 +56,84 @@ class NetworkRegistrationHelperDriverTest {
|
||||
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
|
||||
fun `node registration correct root cert`() {
|
||||
driver(portAllocation = portAllocation,
|
||||
compatibilityZoneURL = compatibilityZoneUrl,
|
||||
startNodesInProcess = true,
|
||||
rootCertificate = rootCert
|
||||
internalDriver(
|
||||
portAllocation = portAllocation,
|
||||
notarySpecs = emptyList(),
|
||||
compatibilityZone = compatibilityZone
|
||||
) {
|
||||
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)
|
||||
startNode(providedName = CordaX500Name("Alice", "London", "GB")).getOrThrow()
|
||||
assertThat(registrationHandler.idsPolled).contains("Alice")
|
||||
}
|
||||
}
|
||||
|
||||
@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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
class RegistrationHandler(private val rootCertAndKeyPair: CertificateAndKeyPair) {
|
||||
private val certPaths = HashMap<String, CertPath>()
|
||||
val idsPolled = HashSet<String>()
|
||||
|
||||
@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()
|
||||
val certificationRequest = input.use { JcaPKCS10CertificationRequest(it.readBytes()) }
|
||||
val (certPath, name) = createSignedClientCertificate(
|
||||
certificationRequest,
|
||||
rootCertAndKeyPair.keyPair,
|
||||
arrayOf(rootCertAndKeyPair.certificate.cert))
|
||||
certPaths[name.organisation] = certPath
|
||||
return Response.ok(name.organisation).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())
|
||||
@Path("{id}")
|
||||
fun reply(@PathParam("id") id: String): Response {
|
||||
idsPolled += id
|
||||
return buildResponse(certPaths[id]!!.certificates)
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
private fun buildResponse(certificates: List<Certificate>): Response {
|
||||
val baos = ByteArrayOutputStream()
|
||||
ZipOutputStream(baos).use { zip ->
|
||||
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 {
|
||||
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)
|
||||
private fun createSignedClientCertificate(certificationRequest: PKCS10CertificationRequest,
|
||||
caKeyPair: KeyPair,
|
||||
caCertPath: Array<Certificate>): Pair<CertPath, CordaX500Name> {
|
||||
val request = JcaPKCS10CertificationRequest(certificationRequest)
|
||||
val name = CordaX500Name.parse(request.subject.toString())
|
||||
val x509CertificateHolder = X509Utilities.createCertificate(CertificateType.CLIENT_CA,
|
||||
caCertPath.first().toX509CertHolder(),
|
||||
caKeyPair,
|
||||
name,
|
||||
request.publicKey,
|
||||
nameConstraints = null)
|
||||
val certPath = X509CertificateFactory().generateCertPath(x509CertificateHolder.cert, *caCertPath)
|
||||
return Pair(certPath, name)
|
||||
}
|
||||
}
|
||||
|
@ -28,10 +28,18 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
|
||||
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 keystorePassword = config.keyStorePassword
|
||||
// TODO: Use different password for private key.
|
||||
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
|
||||
@ -106,12 +114,11 @@ 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.
|
||||
* @throws WrongRootCertException 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)
|
||||
if (rootCert != returnedRootCa) {
|
||||
throw WrongRootCertException(rootCert, returnedRootCa, config.rootCertFile)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.
|
||||
* This usually means the has been a Man-in-the-middle attack when contacting the doorman.
|
||||
*/
|
||||
class WrongRootCaCertificateException(expected: Certificate,
|
||||
actual: Certificate,
|
||||
expectedFilePath: Path):
|
||||
class WrongRootCertException(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
|
||||
|
@ -1,53 +1,56 @@
|
||||
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.SecureHash
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
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.getX509Certificate
|
||||
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
||||
import net.corda.testing.ALICE
|
||||
import net.corda.testing.rigorousMock
|
||||
import net.corda.testing.testNodeConfiguration
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x500.style.BCStyle
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.security.cert.Certificate
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
val X500Name.commonName: String? get() = getRDNs(BCStyle.CN).firstOrNull()?.first?.value?.toString()
|
||||
|
||||
class NetworkRegistrationHelperTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
val tempFolder = TemporaryFolder()
|
||||
|
||||
@Test
|
||||
fun buildKeyStore() {
|
||||
val id = SecureHash.randomSHA256().toString()
|
||||
private val requestId = SecureHash.randomSHA256().toString()
|
||||
private lateinit var config: NodeConfiguration
|
||||
|
||||
@Before
|
||||
fun init() {
|
||||
config = testNodeConfiguration(baseDirectory = tempFolder.root.toPath(), myLegalName = ALICE.name)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `successful registration`() {
|
||||
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 = rigorousMock<NetworkRegistrationService>().also {
|
||||
doReturn(id).whenever(it).submitRequest(any())
|
||||
doReturn(certs).whenever(it).retrieveCertificates(eq(id))
|
||||
}
|
||||
|
||||
val config = testNodeConfiguration(
|
||||
baseDirectory = tempFolder.root.toPath(),
|
||||
myLegalName = ALICE.name)
|
||||
val certService = mockRegistrationResponse(*certs)
|
||||
|
||||
config.rootCaCertFile.parent.createDirectories()
|
||||
X509Utilities.saveCertificateAsPEMFile(certs.last().toX509CertHolder(), config.rootCaCertFile)
|
||||
config.rootCertFile.parent.createDirectories()
|
||||
X509Utilities.saveCertificateAsPEMFile(certs.last(), config.rootCertFile)
|
||||
|
||||
assertFalse(config.nodeKeystore.exists())
|
||||
assertFalse(config.sslKeystore.exists())
|
||||
@ -92,4 +95,44 @@ class NetworkRegistrationHelperTest {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
@ -76,8 +76,8 @@ class DriverTests {
|
||||
assertThat(baseDirectory / "process-id").exists()
|
||||
}
|
||||
|
||||
val baseDirectory = driver(notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name))) {
|
||||
(this as DriverDSL).baseDirectory(DUMMY_NOTARY.name)
|
||||
val baseDirectory = internalDriver(notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name))) {
|
||||
baseDirectory(DUMMY_NOTARY.name)
|
||||
}
|
||||
assertThat(baseDirectory / "process-id").doesNotExist()
|
||||
}
|
||||
|
@ -48,7 +48,6 @@ import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO
|
||||
import net.corda.testing.node.NotarySpec
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.slf4j.Logger
|
||||
import rx.Observable
|
||||
import rx.observables.ConnectableObservable
|
||||
@ -57,6 +56,7 @@ import java.net.*
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
|
||||
import java.security.cert.X509Certificate
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset.UTC
|
||||
@ -165,8 +165,8 @@ interface DriverDSLExposedInterface : CordformContext {
|
||||
verifierType: VerifierType = defaultParameters.verifierType,
|
||||
customOverrides: Map<String, Any?> = defaultParameters.customOverrides,
|
||||
startInSameProcess: Boolean? = defaultParameters.startInSameProcess,
|
||||
maximumHeapSize: String = defaultParameters.maximumHeapSize,
|
||||
initialRegistration: Boolean = defaultParameters.initialRegistration): CordaFuture<NodeHandle>
|
||||
maximumHeapSize: String = defaultParameters.maximumHeapSize
|
||||
): CordaFuture<NodeHandle>
|
||||
|
||||
|
||||
/**
|
||||
@ -301,8 +301,7 @@ data class NodeParameters(
|
||||
val verifierType: VerifierType = VerifierType.InMemory,
|
||||
val customOverrides: Map<String, Any?> = emptyMap(),
|
||||
val startInSameProcess: Boolean? = null,
|
||||
val maximumHeapSize: String = "200m",
|
||||
val initialRegistration: Boolean = false
|
||||
val maximumHeapSize: String = "200m"
|
||||
) {
|
||||
fun setProvidedName(providedName: CordaX500Name?) = copy(providedName = providedName)
|
||||
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].
|
||||
* @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.
|
||||
* @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.
|
||||
* @return The value returned in the [dsl] closure.
|
||||
*/
|
||||
// TODO: make the registration testing parameters internal.
|
||||
fun <A> driver(
|
||||
defaultParameters: DriverParameters = DriverParameters(),
|
||||
isDebug: Boolean = defaultParameters.isDebug,
|
||||
@ -359,8 +354,6 @@ fun <A> driver(
|
||||
waitForAllNodesToFinish: Boolean = defaultParameters.waitForNodesToFinish,
|
||||
notarySpecs: List<NotarySpec> = defaultParameters.notarySpecs,
|
||||
extraCordappPackagesToScan: List<String> = defaultParameters.extraCordappPackagesToScan,
|
||||
compatibilityZoneURL: URL? = defaultParameters.compatibilityZoneURL,
|
||||
rootCertificate: X509CertificateHolder? = defaultParameters.rootCertificate,
|
||||
dsl: DriverDSLExposedInterface.() -> A
|
||||
): A {
|
||||
return genericDriver(
|
||||
@ -375,8 +368,51 @@ fun <A> driver(
|
||||
waitForNodesToFinish = waitForAllNodesToFinish,
|
||||
notarySpecs = notarySpecs,
|
||||
extraCordappPackagesToScan = extraCordappPackagesToScan,
|
||||
compatibilityZoneURL = compatibilityZoneURL,
|
||||
rootCertificate = rootCertificate
|
||||
compatibilityZone = null
|
||||
),
|
||||
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 },
|
||||
dsl = dsl,
|
||||
@ -411,9 +447,7 @@ data class DriverParameters(
|
||||
val startNodesInProcess: Boolean = false,
|
||||
val waitForNodesToFinish: Boolean = false,
|
||||
val notarySpecs: List<NotarySpec> = listOf(NotarySpec(DUMMY_NOTARY.name)),
|
||||
val extraCordappPackagesToScan: List<String> = emptyList(),
|
||||
val compatibilityZoneURL: URL? = null,
|
||||
val rootCertificate: X509CertificateHolder? = null
|
||||
val extraCordappPackagesToScan: List<String> = emptyList()
|
||||
) {
|
||||
fun setIsDebug(isDebug: Boolean) = copy(isDebug = isDebug)
|
||||
fun setDriverDirectory(driverDirectory: Path) = copy(driverDirectory = driverDirectory)
|
||||
@ -426,8 +460,6 @@ data class DriverParameters(
|
||||
fun setTerminateNodesOnShutdown(terminateNodesOnShutdown: Boolean) = copy(waitForNodesToFinish = terminateNodesOnShutdown)
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -480,10 +512,9 @@ fun <DI : DriverDSLExposedInterface, D : DriverDSLInternalInterface, A> genericD
|
||||
startNodesInProcess: Boolean = defaultParameters.startNodesInProcess,
|
||||
notarySpecs: List<NotarySpec>,
|
||||
extraCordappPackagesToScan: List<String> = defaultParameters.extraCordappPackagesToScan,
|
||||
compatibilityZoneURL: URL? = defaultParameters.compatibilityZoneURL,
|
||||
rootCertificate: X509CertificateHolder? = defaultParameters.rootCertificate,
|
||||
driverDslWrapper: (DriverDSL) -> D,
|
||||
coerce: (D) -> DI, dsl: DI.() -> A
|
||||
coerce: (D) -> DI,
|
||||
dsl: DI.() -> A
|
||||
): A {
|
||||
val serializationEnv = setGlobalSerialization(initialiseSerialization)
|
||||
val driverDsl = driverDslWrapper(
|
||||
@ -498,8 +529,7 @@ fun <DI : DriverDSLExposedInterface, D : DriverDSLInternalInterface, A> genericD
|
||||
waitForNodesToFinish = waitForNodesToFinish,
|
||||
extraCordappPackagesToScan = extraCordappPackagesToScan,
|
||||
notarySpecs = notarySpecs,
|
||||
compatibilityZoneURL = compatibilityZoneURL,
|
||||
rootCertificate = rootCertificate
|
||||
compatibilityZone = null
|
||||
)
|
||||
)
|
||||
val shutdownHook = addShutdownHook(driverDsl::shutdown)
|
||||
@ -606,8 +636,7 @@ class DriverDSL(
|
||||
val waitForNodesToFinish: Boolean,
|
||||
extraCordappPackagesToScan: List<String>,
|
||||
val notarySpecs: List<NotarySpec>,
|
||||
val compatibilityZoneURL: URL?,
|
||||
val rootCertificate: X509CertificateHolder?
|
||||
val compatibilityZone: CompatibilityZoneParams?
|
||||
) : DriverDSLInternalInterface {
|
||||
private var _executorService: ScheduledExecutorService? = null
|
||||
val executorService get() = _executorService!!
|
||||
@ -679,16 +708,14 @@ class DriverDSL(
|
||||
verifierType: VerifierType,
|
||||
customOverrides: Map<String, Any?>,
|
||||
startInSameProcess: Boolean?,
|
||||
maximumHeapSize: String,
|
||||
initialRegistration: Boolean
|
||||
maximumHeapSize: String
|
||||
): CordaFuture<NodeHandle> {
|
||||
val p2pAddress = portAllocation.nextHostAndPort()
|
||||
// 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 registrationFuture = if (initialRegistration) {
|
||||
compatibilityZoneURL ?: throw IllegalArgumentException("Compatibility zone URL must be provided for initial registration.")
|
||||
registerNode(name, compatibilityZoneURL)
|
||||
val registrationFuture = if (compatibilityZone?.rootCert != null) {
|
||||
nodeRegistration(name, compatibilityZone.rootCert, compatibilityZone.url)
|
||||
} else {
|
||||
doneFuture(Unit)
|
||||
}
|
||||
@ -709,8 +736,8 @@ class DriverDSL(
|
||||
val config = ConfigHelper.loadConfig(
|
||||
baseDirectory = baseDirectory(name),
|
||||
allowMissingConfig = true,
|
||||
configOverrides = if (compatibilityZoneURL != null) {
|
||||
configMap + mapOf("compatibilityZoneURL" to compatibilityZoneURL.toString())
|
||||
configOverrides = if (compatibilityZone != null) {
|
||||
configMap + mapOf("compatibilityZoneURL" to compatibilityZone.url.toString())
|
||||
} else {
|
||||
configMap
|
||||
}
|
||||
@ -719,14 +746,10 @@ 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 nodeRegistration(providedName: CordaX500Name, rootCert: X509Certificate, compatibilityZoneURL: URL): CordaFuture<Unit> {
|
||||
val baseDirectory = baseDirectory(providedName).createDirectories()
|
||||
val config = ConfigHelper.loadConfig(
|
||||
baseDirectory = baseDirectory(providedName),
|
||||
baseDirectory = baseDirectory,
|
||||
allowMissingConfig = true,
|
||||
configOverrides = configOf(
|
||||
"p2pAddress" to "localhost:1222", // required argument, not really used
|
||||
@ -734,16 +757,17 @@ class DriverDSL(
|
||||
"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) {
|
||||
|
||||
configuration.rootCertFile.parent.createDirectories()
|
||||
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
|
||||
// when registering.
|
||||
NetworkRegistrationHelper(configuration, HTTPNetworkRegistrationService(compatibilityZoneURL))
|
||||
.buildKeystore()
|
||||
return doneFuture(Unit)
|
||||
NetworkRegistrationHelper(configuration, HTTPNetworkRegistrationService(compatibilityZoneURL)).buildKeystore()
|
||||
doneFuture(Unit)
|
||||
} else {
|
||||
return startNodeForRegistration(config)
|
||||
startOutOfProcessNodeRegistration(config, configuration)
|
||||
}
|
||||
}
|
||||
|
||||
@ -872,18 +896,18 @@ class DriverDSL(
|
||||
ServiceIdentityGenerator.generateToDisk(
|
||||
dirs = listOf(baseDirectory(spec.name)),
|
||||
serviceName = spec.name,
|
||||
rootCertertificate = rootCertificate,
|
||||
serviceId = "identity"
|
||||
serviceId = "identity",
|
||||
customRootCert = compatibilityZone?.rootCert
|
||||
)
|
||||
} else {
|
||||
ServiceIdentityGenerator.generateToDisk(
|
||||
dirs = generateNodeNames(spec).map { baseDirectory(it) },
|
||||
serviceName = spec.name,
|
||||
rootCertertificate = rootCertificate,
|
||||
serviceId = NotaryService.constructId(
|
||||
validating = spec.validating,
|
||||
raft = spec.cluster is ClusterSpec.Raft
|
||||
)
|
||||
),
|
||||
customRootCert = compatibilityZone?.rootCert
|
||||
)
|
||||
}
|
||||
NotaryInfo(identity, spec.validating)
|
||||
@ -1008,16 +1032,12 @@ class DriverDSL(
|
||||
return future
|
||||
}
|
||||
|
||||
private fun startNodeForRegistration(config: Config): CordaFuture<Unit> {
|
||||
val maximumHeapSize = "200m"
|
||||
val configuration = config.parseAsNodeConfiguration()
|
||||
configuration.baseDirectory.createDirectories()
|
||||
|
||||
private fun startOutOfProcessNodeRegistration(config: Config, configuration: NodeConfiguration): CordaFuture<Unit> {
|
||||
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -1030,7 +1050,7 @@ class DriverDSL(
|
||||
val baseDirectory = configuration.baseDirectory.createDirectories()
|
||||
// Distribute node info file using file copier when network map service URL (compatibilityZoneURL) is null.
|
||||
// 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)
|
||||
networkParameters!!.install(baseDirectory)
|
||||
@ -1068,7 +1088,7 @@ class DriverDSL(
|
||||
}
|
||||
val p2pReadyFuture = addressMustBeBoundFuture(executorService, configuration.p2pAddress, process)
|
||||
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
|
||||
}
|
||||
establishRpc(configuration, processDeathFuture).flatMap { rpc ->
|
||||
|
@ -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.spi.core.protocol.RemotingConnection
|
||||
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager3
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import java.lang.reflect.Method
|
||||
import java.net.URL
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
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
|
||||
private val globalPortAllocation = PortAllocation.Incremental(10000)
|
||||
private val globalDebugPortAllocation = PortAllocation.Incremental(5005)
|
||||
|
||||
fun <A> rpcDriver(
|
||||
isDebug: Boolean = false,
|
||||
driverDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()),
|
||||
@ -237,8 +236,6 @@ fun <A> rpcDriver(
|
||||
extraCordappPackagesToScan: List<String> = emptyList(),
|
||||
notarySpecs: List<NotarySpec> = emptyList(),
|
||||
externalTrace: Trace? = null,
|
||||
compatibilityZoneURL: URL? = null,
|
||||
rootCertificate: X509CertificateHolder? = null,
|
||||
dsl: RPCDriverExposedDSLInterface.() -> A
|
||||
) = genericDriver(
|
||||
driverDsl = RPCDriverDSL(
|
||||
@ -253,8 +250,7 @@ fun <A> rpcDriver(
|
||||
waitForNodesToFinish = waitForNodesToFinish,
|
||||
extraCordappPackagesToScan = extraCordappPackagesToScan,
|
||||
notarySpecs = notarySpecs,
|
||||
compatibilityZoneURL = compatibilityZoneURL,
|
||||
rootCertificate = rootCertificate
|
||||
compatibilityZone = null
|
||||
), externalTrace
|
||||
),
|
||||
coerce = { it },
|
||||
|
@ -6,9 +6,8 @@ import net.corda.cordform.CordformDefinition
|
||||
import net.corda.cordform.CordformNode
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.testing.driver.DriverDSL
|
||||
import net.corda.testing.driver.PortAllocation
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.driver.internalDriver
|
||||
|
||||
fun CordformDefinition.clean() {
|
||||
System.err.println("Deleting: $nodesDirectory")
|
||||
@ -39,7 +38,7 @@ private fun CordformDefinition.runNodes(waitForAllNodesToFinish: Boolean, block:
|
||||
.flatMap { listOf(it.p2pAddress, it.rpcAddress, it.webAddress) }
|
||||
.mapNotNull { address -> address?.let { NetworkHostAndPort.parse(it).port } }
|
||||
.max()!!
|
||||
driver(
|
||||
internalDriver(
|
||||
isDebug = true,
|
||||
driverDirectory = nodesDirectory,
|
||||
extraCordappPackagesToScan = cordappPackages,
|
||||
@ -50,7 +49,7 @@ private fun CordformDefinition.runNodes(waitForAllNodesToFinish: Boolean, block:
|
||||
waitForAllNodesToFinish = waitForAllNodesToFinish
|
||||
) {
|
||||
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...")
|
||||
block()
|
||||
}
|
||||
|
@ -19,9 +19,9 @@ import net.corda.node.services.config.configureDevKeyAndTrustStores
|
||||
import net.corda.nodeapi.ArtemisTcpTransport
|
||||
import net.corda.nodeapi.ConnectionDirection
|
||||
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.SSLConfiguration
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
|
||||
import net.corda.testing.driver.*
|
||||
import net.corda.testing.internal.ProcessUtilities
|
||||
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.server.impl.ActiveMQServerImpl
|
||||
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.Paths
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
@ -87,8 +85,6 @@ fun <A> verifierDriver(
|
||||
waitForNodesToFinish: Boolean = false,
|
||||
extraCordappPackagesToScan: List<String> = emptyList(),
|
||||
notarySpecs: List<NotarySpec> = emptyList(),
|
||||
compatibilityZoneURL: URL? = null,
|
||||
rootCertificate: X509CertificateHolder? = null,
|
||||
dsl: VerifierExposedDSLInterface.() -> A
|
||||
) = genericDriver(
|
||||
driverDsl = VerifierDriverDSL(
|
||||
@ -103,8 +99,7 @@ fun <A> verifierDriver(
|
||||
waitForNodesToFinish = waitForNodesToFinish,
|
||||
extraCordappPackagesToScan = extraCordappPackagesToScan,
|
||||
notarySpecs = notarySpecs,
|
||||
compatibilityZoneURL = compatibilityZoneURL,
|
||||
rootCertificate = rootCertificate
|
||||
compatibilityZone = null
|
||||
)
|
||||
),
|
||||
coerce = { it },
|
||||
|
Loading…
x
Reference in New Issue
Block a user