Merge pull request #277 from corda/shams-os-merge-020118

Shams os merge 020118
This commit is contained in:
Shams Asari 2018-01-03 10:46:13 +00:00 committed by GitHub
commit 62dbf3651d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 1402 additions and 908 deletions

View File

@ -110,7 +110,11 @@ object Crypto {
"ECDSA signature scheme using the secp256r1 (NIST P-256) curve." "ECDSA signature scheme using the secp256r1 (NIST P-256) curve."
) )
/** EdDSA signature scheme using the ed25519 twisted Edwards curve and SHA512 for message hashing. */ /**
* EdDSA signature scheme using the ed25519 twisted Edwards curve and SHA512 for message hashing.
* The actual algorithm is PureEdDSA Ed25519 as defined in https://tools.ietf.org/html/rfc8032
* Not to be confused with the EdDSA variants, Ed25519ctx and Ed25519ph.
*/
@JvmField @JvmField
val EDDSA_ED25519_SHA512 = SignatureScheme( val EDDSA_ED25519_SHA512 = SignatureScheme(
4, 4,

View File

@ -11,6 +11,8 @@ import net.corda.core.serialization.CordaSerializable
@DoNotImplement @DoNotImplement
interface FlowLogicRefFactory { interface FlowLogicRefFactory {
fun create(flowClass: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef fun create(flowClass: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef
fun createForRPC(flowClass: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef
fun toFlowLogic(ref: FlowLogicRef): FlowLogic<*>
} }
@CordaSerializable @CordaSerializable

View File

@ -27,8 +27,8 @@ import kotlin.test.*
*/ */
class CryptoUtilsTest { class CryptoUtilsTest {
val testString = "Hello World" private val testString = "Hello World"
val testBytes = testString.toByteArray() private val testBytes = testString.toByteArray()
// key generation test // key generation test
@Test @Test

View File

@ -0,0 +1,190 @@
package net.corda.core.crypto
import net.corda.core.utilities.hexToByteArray
import net.corda.core.utilities.toHex
import net.i2p.crypto.eddsa.EdDSAPrivateKey
import net.i2p.crypto.eddsa.EdDSASecurityProvider
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec
import org.junit.Test
import java.security.PrivateKey
import java.security.Signature
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
/**
* Testing PureEdDSA Ed25519 using test vectors from https://tools.ietf.org/html/rfc8032#section-7.1
*/
class EdDSATests {
@Test
fun `PureEdDSA Ed25519 test vectors`() {
val edParams = Crypto.EDDSA_ED25519_SHA512.algSpec as EdDSANamedCurveSpec
// MESSAGE (length 0 bytes).
val testVector1 = SignatureTestVector(
"9d61b19deffd5a60ba844af492ec2cc4" +
"4449c5697b326919703bac031cae7f60",
"d75a980182b10ab7d54bfed3c964073a" +
"0ee172f3daa62325af021a68f707511a",
"",
"e5564300c360ac729086e2cc806e828a" +
"84877f1eb8e5d974d873e06522490155" +
"5fb8821590a33bacc61e39701cf9b46b" +
"d25bf5f0595bbe24655141438e7a100b"
)
// MESSAGE (length 1 byte).
val testVector2 = SignatureTestVector(
"4ccd089b28ff96da9db6c346ec114e0f" +
"5b8a319f35aba624da8cf6ed4fb8a6fb",
"3d4017c3e843895a92b70aa74d1b7ebc" +
"9c982ccf2ec4968cc0cd55f12af4660c",
"72",
"92a009a9f0d4cab8720e820b5f642540" +
"a2b27b5416503f8fb3762223ebdb69da" +
"085ac1e43e15996e458f3613d0f11d8c" +
"387b2eaeb4302aeeb00d291612bb0c00"
)
// MESSAGE (length 2 bytes).
val testVector3 = SignatureTestVector(
"c5aa8df43f9f837bedb7442f31dcb7b1" +
"66d38535076f094b85ce3a2e0b4458f7",
"fc51cd8e6218a1a38da47ed00230f058" +
"0816ed13ba3303ac5deb911548908025",
"af82",
"6291d657deec24024827e69c3abe01a3" +
"0ce548a284743a445e3680d7db5ac3ac" +
"18ff9b538d16f290ae67f760984dc659" +
"4a7c15e9716ed28dc027beceea1ec40a"
)
// MESSAGE (length 1023 bytes).
val testVector1024 = SignatureTestVector(
"f5e5767cf153319517630f226876b86c" +
"8160cc583bc013744c6bf255f5cc0ee5",
"278117fc144c72340f67d0f2316e8386" +
"ceffbf2b2428c9c51fef7c597f1d426e",
"08b8b2b733424243760fe426a4b54908" +
"632110a66c2f6591eabd3345e3e4eb98" +
"fa6e264bf09efe12ee50f8f54e9f77b1" +
"e355f6c50544e23fb1433ddf73be84d8" +
"79de7c0046dc4996d9e773f4bc9efe57" +
"38829adb26c81b37c93a1b270b20329d" +
"658675fc6ea534e0810a4432826bf58c" +
"941efb65d57a338bbd2e26640f89ffbc" +
"1a858efcb8550ee3a5e1998bd177e93a" +
"7363c344fe6b199ee5d02e82d522c4fe" +
"ba15452f80288a821a579116ec6dad2b" +
"3b310da903401aa62100ab5d1a36553e" +
"06203b33890cc9b832f79ef80560ccb9" +
"a39ce767967ed628c6ad573cb116dbef" +
"efd75499da96bd68a8a97b928a8bbc10" +
"3b6621fcde2beca1231d206be6cd9ec7" +
"aff6f6c94fcd7204ed3455c68c83f4a4" +
"1da4af2b74ef5c53f1d8ac70bdcb7ed1" +
"85ce81bd84359d44254d95629e9855a9" +
"4a7c1958d1f8ada5d0532ed8a5aa3fb2" +
"d17ba70eb6248e594e1a2297acbbb39d" +
"502f1a8c6eb6f1ce22b3de1a1f40cc24" +
"554119a831a9aad6079cad88425de6bd" +
"e1a9187ebb6092cf67bf2b13fd65f270" +
"88d78b7e883c8759d2c4f5c65adb7553" +
"878ad575f9fad878e80a0c9ba63bcbcc" +
"2732e69485bbc9c90bfbd62481d9089b" +
"eccf80cfe2df16a2cf65bd92dd597b07" +
"07e0917af48bbb75fed413d238f5555a" +
"7a569d80c3414a8d0859dc65a46128ba" +
"b27af87a71314f318c782b23ebfe808b" +
"82b0ce26401d2e22f04d83d1255dc51a" +
"ddd3b75a2b1ae0784504df543af8969b" +
"e3ea7082ff7fc9888c144da2af58429e" +
"c96031dbcad3dad9af0dcbaaaf268cb8" +
"fcffead94f3c7ca495e056a9b47acdb7" +
"51fb73e666c6c655ade8297297d07ad1" +
"ba5e43f1bca32301651339e22904cc8c" +
"42f58c30c04aafdb038dda0847dd988d" +
"cda6f3bfd15c4b4c4525004aa06eeff8" +
"ca61783aacec57fb3d1f92b0fe2fd1a8" +
"5f6724517b65e614ad6808d6f6ee34df" +
"f7310fdc82aebfd904b01e1dc54b2927" +
"094b2db68d6f903b68401adebf5a7e08" +
"d78ff4ef5d63653a65040cf9bfd4aca7" +
"984a74d37145986780fc0b16ac451649" +
"de6188a7dbdf191f64b5fc5e2ab47b57" +
"f7f7276cd419c17a3ca8e1b939ae49e4" +
"88acba6b965610b5480109c8b17b80e1" +
"b7b750dfc7598d5d5011fd2dcc5600a3" +
"2ef5b52a1ecc820e308aa342721aac09" +
"43bf6686b64b2579376504ccc493d97e" +
"6aed3fb0f9cd71a43dd497f01f17c0e2" +
"cb3797aa2a2f256656168e6c496afc5f" +
"b93246f6b1116398a346f1a641f3b041" +
"e989f7914f90cc2c7fff357876e506b5" +
"0d334ba77c225bc307ba537152f3f161" +
"0e4eafe595f6d9d90d11faa933a15ef1" +
"369546868a7f3a45a96768d40fd9d034" +
"12c091c6315cf4fde7cb68606937380d" +
"b2eaaa707b4c4185c32eddcdd306705e" +
"4dc1ffc872eeee475a64dfac86aba41c" +
"0618983f8741c5ef68d3a101e8a3b8ca" +
"c60c905c15fc910840b94c00a0b9d0",
"0aab4c900501b3e24d7cdf4663326a3a" +
"87df5e4843b2cbdb67cbf6e460fec350" +
"aa5371b1508f9f4528ecea23c436d94b" +
"5e8fcd4f681e30a6ac00a9704a188a03"
)
// MESSAGE (length 64 bytes). TEST SHA(abc).
val testVectorSHAabc = SignatureTestVector(
"833fe62409237b9d62ec77587520911e" +
"9a759cec1d19755b7da901b96dca3d42",
"ec172b93ad5e563bf4932c70e1245034" +
"c35467ef2efd4d64ebf819683467e2bf",
"ddaf35a193617abacc417349ae204131" +
"12e6fa4e89a97ea20a9eeee64b55d39a" +
"2192992a274fc1a836ba3c23a3feebbd" +
"454d4423643ce80e2a9ac94fa54ca49f",
"dc2a4459e7369633a52b1bf277839a00" +
"201009a3efbf3ecb69bea2186c26b589" +
"09351fc9ac90b3ecfdfbc7c66431e030" +
"3dca179c138ac17ad9bef1177331a704"
)
val testVectors = listOf(testVector1, testVector2, testVector3, testVector1024, testVectorSHAabc)
testVectors.forEach {
val privateKey = EdDSAPrivateKey(EdDSAPrivateKeySpec(it.privateKeyHex.hexToByteArray(), edParams))
assertEquals(it.signatureOutputHex, doSign(privateKey, it.messageToSignHex.hexToByteArray()).toHex().toLowerCase())
}
// Test vector for the variant Ed25519ctx, expected to fail.
val testVectorEd25519ctx = SignatureTestVector(
"0305334e381af78f141cb666f6199f57" +
"bc3495335a256a95bd2a55bf546663f6",
"dfc9425e4f968f7f0c29f0259cf5f9ae" +
"d6851c2bb4ad8bfb860cfee0ab248292",
"f726936d19c800494e3fdaff20b276a8",
"fc60d5872fc46b3aa69f8b5b4351d580" +
"8f92bcc044606db097abab6dbcb1aee3" +
"216c48e8b3b66431b5b186d1d28f8ee1" +
"5a5ca2df6668346291c2043d4eb3e90d"
)
val privateKey = EdDSAPrivateKey(EdDSAPrivateKeySpec(testVectorEd25519ctx.privateKeyHex.hexToByteArray(), edParams))
assertNotEquals(testVectorEd25519ctx.signatureOutputHex, doSign(privateKey, testVectorEd25519ctx.messageToSignHex.hexToByteArray()).toHex().toLowerCase())
}
/** A test vector object for digital signature schemes. */
private data class SignatureTestVector(val privateKeyHex: String,
val publicKeyHex: String,
val messageToSignHex: String,
val signatureOutputHex: String)
// Required to implement a custom doSign function, because Corda's Crypto.doSign does not allow empty messages (testVector1).
private fun doSign(privateKey: PrivateKey, clearData: ByteArray): ByteArray {
val signature = Signature.getInstance(Crypto.EDDSA_ED25519_SHA512.signatureName, EdDSASecurityProvider())
signature.initSign(privateKey)
signature.update(clearData)
return signature.sign()
}
}

View File

@ -1,9 +1,9 @@
package net.corda.core.crypto package net.corda.core.crypto
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.toTypedArray
import net.corda.core.internal.cert import net.corda.core.internal.cert
import net.corda.nodeapi.internal.crypto.* import net.corda.nodeapi.internal.crypto.*
import net.corda.testing.internal.createDevIntermediateCaCertPath
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.GeneralName import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralSubtree import org.bouncycastle.asn1.x509.GeneralSubtree
@ -11,35 +11,40 @@ import org.bouncycastle.asn1.x509.NameConstraints
import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.junit.Test import org.junit.Test
import java.security.KeyStore import java.security.KeyStore
import java.security.cert.* import java.security.cert.CertPathValidator
import java.util.stream.Stream import java.security.cert.CertPathValidatorException
import java.security.cert.PKIXParameters
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
import kotlin.test.assertTrue import kotlin.test.assertTrue
class X509NameConstraintsTest { class X509NameConstraintsTest {
private fun makeKeyStores(subjectName: X500Name, nameConstraints: NameConstraints): Pair<KeyStore, KeyStore> { private fun makeKeyStores(subjectName: X500Name, nameConstraints: NameConstraints): Pair<KeyStore, KeyStore> {
val rootKeys = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Corda Root CA", organisation = "R3 Ltd", locality = "London", country = "GB"), rootKeys) val nodeCaKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val nodeCaCert = X509Utilities.createCertificate(
val intermediateCAKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) CertificateType.NODE_CA,
val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootKeys, CordaX500Name(commonName = "Corda Intermediate CA", organisation = "R3 Ltd", locality = "London", country = "GB"), intermediateCAKeyPair.public) intermediateCa.certificate,
intermediateCa.keyPair,
val clientCAKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) CordaX500Name("Corda Client CA", "R3 Ltd", "London", "GB"),
val clientCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCACert, intermediateCAKeyPair, CordaX500Name(commonName = "Corda Client CA", organisation = "R3 Ltd", locality = "London", country = "GB"), clientCAKeyPair.public, nameConstraints = nameConstraints) nodeCaKeyPair.public,
nameConstraints = nameConstraints)
val keyPass = "password" val keyPass = "password"
val trustStore = KeyStore.getInstance(KEYSTORE_TYPE) val trustStore = KeyStore.getInstance(KEYSTORE_TYPE)
trustStore.load(null, keyPass.toCharArray()) trustStore.load(null, keyPass.toCharArray())
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCACert.cert) trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCa.certificate.cert)
val tlsKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val tlsKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, clientCACert, clientCAKeyPair, subjectName, tlsKey.public) val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, nodeCaCert, nodeCaKeyPair, subjectName, tlsKeyPair.public)
val keyStore = KeyStore.getInstance(KEYSTORE_TYPE) val keyStore = KeyStore.getInstance(KEYSTORE_TYPE)
keyStore.load(null, keyPass.toCharArray()) keyStore.load(null, keyPass.toCharArray())
keyStore.addOrReplaceKey(X509Utilities.CORDA_CLIENT_TLS, tlsKey.private, keyPass.toCharArray(), keyStore.addOrReplaceKey(
Stream.of(tlsCert, clientCACert, intermediateCACert, rootCACert).map { it.cert }.toTypedArray<Certificate>()) X509Utilities.CORDA_CLIENT_TLS,
tlsKeyPair.private,
keyPass.toCharArray(),
arrayOf(tlsCert, nodeCaCert, intermediateCa.certificate, rootCa.certificate))
return Pair(keyStore, trustStore) return Pair(keyStore, trustStore)
} }

View File

@ -28,18 +28,16 @@ Setting your dependencies
Choosing your Corda version Choosing your Corda version
^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
The following two lines of the ``build.gradle`` file define the Corda version used to build your CorDapp: ``ext.corda_release_version`` and ``ext.corda_gradle_plugins_version`` are used in the ``build.gradle`` to define the
versions of Corda and the Corda Gradle Plugins that are used to build your CorDapp.
For example, to use version 1.0 of Corda and version 1.0 of the Corda gradle plugins, you'd write:
.. sourcecode:: groovy .. sourcecode:: groovy
ext.corda_release_version = '1.0.0' ext.corda_release_version = '1.0.0'
ext.corda_gradle_plugins_version = '1.0.0' ext.corda_gradle_plugins_version = '1.0.0'
In this case, our CorDapp will use:
* Version 1.0 of Corda
* Version 1.0 of the Corda gradle plugins
You can find the latest published version of both here: https://bintray.com/r3/corda. You can find the latest published version of both here: https://bintray.com/r3/corda.
``corda_gradle_plugins_versions`` are given in the form ``major.minor.patch``. You should use the same ``major`` and ``corda_gradle_plugins_versions`` are given in the form ``major.minor.patch``. You should use the same ``major`` and
@ -50,25 +48,32 @@ In certain cases, you may also wish to build against the unstable Master branch.
Corda dependencies Corda dependencies
^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
The ``cordformation`` plugin adds: The ``cordformation`` plugin adds two new gradle configurations:
* ``cordaCompile`` as a new configuration that ``compile`` extends from * ``cordaCompile``, which extends ``compile``
* ``cordaRuntime`` which ``runtime`` extends from * ``cordaRuntime``, which extends ``runtime``
To build against Corda you must add the following to your ``build.gradle`` file; To build against Corda, you must add the following to your ``build.gradle`` file:
* The ``net.corda:corda:<version>`` JAR as a ``cordaRuntime`` dependency * ``net.corda:corda:$corda_release_version`` as a ``cordaRuntime`` dependency
* Each compile dependency (eg ``corda-core``) as a ``cordaCompile`` dependency * Each Corda compile dependency (eg ``net.corda:corda-core:$corda_release_version``) as a ``cordaCompile`` dependency
To use Corda's test facilities you must add ``net.corda:corda-test-utils:<version>`` as a ``testCompile`` dependency You may also want to add:
(i.e. a default Java/Kotlin test compile task).
* ``net.corda:corda-test-utils:$corda_release_version`` as a ``testCompile`` dependency, in order to use Corda's test
frameworks
* ``net.corda:corda-webserver:$corda_release_version`` as a ``cordaRuntime`` dependency, in order to use Corda's
built-in development webserver
.. warning:: Never include ``corda-test-utils`` as a ``compile`` or ``cordaCompile`` dependency. .. warning:: Never include ``corda-test-utils`` as a ``compile`` or ``cordaCompile`` dependency.
Dependencies on other CorDapps Dependencies on other CorDapps
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Sometimes, a CorDapp you build will depend on states, contracts or flows defined in another CorDapp. You must include You CorDapp may also depend on classes defined in another CorDapp, such as states, contracts and flows. There are two
the CorDapp your CorDapp depends upon as a ``cordapp`` dependency in your ``build.gradle`` file. ways to add another CorDapp as a dependency in your CorDapp's ``build.gradle`` file:
* ``cordapp project(":another-cordapp")`` (use this if the other CorDapp is defined in a module in the same project)
* ``cordapp "net.corda:another-cordapp:1.0"`` (use this otherwise)
Other dependencies Other dependencies
^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^

View File

@ -10,7 +10,6 @@ import net.corda.core.messaging.CordaRPCOps
import net.corda.core.serialization.SerializationWhitelist import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.webserver.services.WebServerPluginRegistry
import java.util.function.Function import java.util.function.Function
import javax.ws.rs.GET import javax.ws.rs.GET
import javax.ws.rs.Path import javax.ws.rs.Path

View File

@ -67,10 +67,21 @@ The bootstrapper tool can be built with the command:
The resulting jar can be found in ``tools/bootstrapper/build/libs/``. The resulting jar can be found in ``tools/bootstrapper/build/libs/``.
To use it, run the following command, specifying the root directory which hosts all the node directories as the argument: To use it, create a directory containing a ``node.conf`` file for each node you want to create. Then run the following command:
``java -jar network-bootstrapper.jar <nodes-root-dir>`` ``java -jar network-bootstrapper.jar <nodes-root-dir>``
For example running the command on a directory containing these files :
.. sourcecode:: none
.
├── notary.conf // The notary's node.conf file
├── partya.conf // Party A's node.conf file
└── partyb.conf // Party B's node.conf file
Would generate directories containing three nodes: notary, partya and partyb.
Starting the nodes Starting the nodes
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~

View File

@ -7,25 +7,32 @@
Shell Shell
===== =====
The Corda shell is an embedded command line that allows an administrator to control and monitor the node. .. contents::
Some of its features include:
* Invoking any of the RPCs the node exposes to applications. The Corda shell is an embedded command line that allows an administrator to control and monitor a node. It is based on
* Starting flows. the `CRaSH`_ shell and supports many of the same features. These features include:
* View a dashboard of threads, heap usage, VM properties.
* Uploading and downloading zips from the attachment store.
* Issue SQL queries to the underlying database.
* View JMX metrics and monitoring exports.
* UNIX style pipes for both text and objects, an ``egrep`` command and a command for working with columnular data.
It is based on the popular `CRaSH`_ shell used in various other projects and supports many of the same features. * Invoking any of the node's RPC methods
* Viewing a dashboard of threads, heap usage, VM properties
* Uploading and downloading attachments
* Issuing SQL queries to the underlying database
* Viewing JMX metrics and monitoring exports
* UNIX style pipes for both text and objects, an ``egrep`` command and a command for working with columnular data
Local terminal shell runs only in development mode. It may be disabled by passing the ``--no-local-shell`` flag to the node. The shell via the local terminal
--------------------------------
SSH server In development mode, the shell will display in the node's terminal window. It may be disabled by passing the
---------- ``--no-local-shell`` flag when running the node.
Shell can also be accessible via SSH. By default SSH server is *disabled*. To enable it port must be configured - in ``node.conf`` file The shell via SSH
-----------------
The shell is also accessible via SSH.
Enabling SSH access
*******************
By default, the SSH server is *disabled*. To enable it, a port must be configured in the node's ``node.conf`` file:
.. code:: bash .. code:: bash
@ -33,80 +40,159 @@ Shell can also be accessible via SSH. By default SSH server is *disabled*. To en
port = 2222 port = 2222
} }
Authentication and authorization Authentication
-------------------------------- **************
SSH requires users to login first - using the same users as RPC system. In fact, the shell serves as a proxy to RPC and communicates Users log in to shell via SSH using the same credentials as for RPC. This is because the shell actually communicates
with the node using RPC calls. This also means that RPC permissions are enforced. No permissions are required to allow the connection with the node using RPC calls. No RPC permissions are required to allow the connection and log in.
and log in.
Watching flows (``flow watch``) requires ``InvokeRpc.stateMachinesFeed`` while starting flows requires
``InvokeRpc.startTrackedFlowDynamic`` and ``InvokeRpc.registeredFlows`` in addition to a permission for a particular flow.
Host key The host key is loaded from the ``<node root directory>/sshkey/hostkey.pem`` file. If this file does not exist, it is
-------- generated automatically. In development mode, the seed may be specified to give the same results on the same computer
in order to avoid host-checking errors.
The host key is loaded from ``sshkey/hostkey.pem`` file. If the file does not exist, it will be generated randomly, however Connecting to the shell
in the development mode seed may be tuned to give the same results on the same computer - in order to avoid host checking ***********************
errors.
Connecting Linux and MacOS
---------- ^^^^^^^^^^^^^^^
Linux and MacOS computers usually come with SSH client preinstalled. On Windows it usually requires extra download. Run the following command from the terminal:
Usual connection syntax is ``ssh user@host -p 2222`` - where ``user`` is a RPC username, and ``-p`` specifies a port parameters -
it's the same as setup in ``node.conf`` file. ``host`` should point to a node hostname, usually ``localhost`` if connecting and
running node on the same computer. Password will be asked after establishing connection.
:note: While developing, checking multiple samples or simply restarting a node frequently host key may be regenerated. SSH usually .. code:: bash
saved once trusted hosts and will refuse to connect in case of a change. Then check may be disabled with extra options
``ssh -o StrictHostKeyChecking=no user@host -p2222``. This option should never be used in production environment!
Getting help ssh -p [portNumber] [host] -l [user]
------------
You can run ``help`` to list the available commands. Where:
The shell has a ``man`` command that can be used to get interactive help on many commands. You can also use the * ``[portNumber]`` is the port number specified in the ``node.conf`` file
``--help`` or ``-h`` flags to a command to get info about what switches it supports. * ``[host]`` is the node's host (e.g. ``localhost`` if running the node locally)
* ``[user]`` is the RPC username
Commands may have subcommands, in the same style as ``git``. In that case running the command by itself will The RPC password will be requested after a connection is established.
list the supported subcommands.
Starting flows and performing remote method calls :note: In development mode, restarting a node frequently may cause the host key to be regenerated. SSH usually saves
------------------------------------------------- trusted hosts and will refuse to connect in case of a change. This check can be disabled using the
``-o StrictHostKeyChecking=no`` flag. This option should never be used in production environment!
**Flows** are the way the ledger is changed. If you aren't familiar with them, please review ":doc:`flow-state-machines`" Windows
first. The ``flow list`` command can be used to list the flows understood by the node, ``flow watch`` shows all the flows ^^^^^^^
currently running on the node with the result (or error) information in a user friendly way, ``flow start`` can be
used to start flows. The ``flow start`` command takes the class name of a flow, or *any unambiguous substring* and
then the data to be passed to the flow constructor. The unambiguous substring feature is helpful for reducing
the needed typing. If the match is ambiguous the possible matches will be printed out. If a flow has multiple
constructors then the names and types of the arguments will be used to try and determine which to use automatically.
If the match against available constructors is unclear, the reasons each available constructor failed to match
will be printed out. In the case of an ambiguous match, the first applicable will be used.
**RPCs** (remote procedure calls) are commands that can be sent to the node to query it, control it and manage it. Windows does not provide a built-in SSH tool. An alternative such as PuTTY should be used.
RPCs don't typically do anything that changes the global ledger, but they may change node-specific data in the
database. Each RPC is one method on the ``CordaRPCOps`` interface, and may return a stream of events that will
be shown on screen until you press Ctrl-C. You perform an RPC by using ``run`` followed by the name.
.. raw:: html Permissions
***********
<center><b><a href="api/kotlin/corda/net.corda.core.messaging/-corda-r-p-c-ops/index.html">Documentation of available RPCs</a></b><p></center> When accessing the shell via SSH, some additional RPC permissions are required:
Whichever form of change is used, there is a need to provide *parameters* to either the RPC or the flow * Watching flows (``flow watch``) requires ``InvokeRpc.stateMachinesFeed``
constructor. Because parameters can be any arbitrary Java object graph, we need a convenient syntax to express * Starting flows requires ``InvokeRpc.startTrackedFlowDynamic`` and ``InvokeRpc.registeredFlows``, as well as a
this sort of data. The shell uses a syntax called `Yaml`_ to do this. permission for the flow being started
Data syntax Interacting with the node via the shell
----------- ---------------------------------------
Yaml (yet another markup language) is a simple JSON-like way to describe object graphs. It has several features The shell interacts with the node by issuing RPCs (remote procedure calls). You make an RPC from the shell by typing
that make it helpful for our use case, like a lightweight syntax and support for "bare words" which mean you can ``run`` followed by the name of the desired RPC method. For example, you'd see a list of the registered flows on your
often skip the quotes around strings. Here is an example of how this syntax is used: node by running:
``run registeredFlows``
Some RPCs return a stream of events that will be shown on screen until you press Ctrl-C.
You can find a list of the available RPC methods
`here <https://docs.corda.net/api/kotlin/corda/net.corda.core.messaging/-corda-r-p-c-ops/index.html>`_.
Flow commands
*************
The shell also has special commands for working with flows:
* ``flow list`` lists the flows available on the node
* ``flow watch`` shows all the flows currently running on the node with result (or error) information
* ``flow start`` starts a flow. The ``flow start`` command takes the name of a flow class, or
*any unambiguous substring* thereof, as well as the data to be passed to the flow constructor. If there are several
matches for a given substring, the possible matches will be printed out. If a flow has multiple constructors then the
names and types of the arguments will be used to try and automatically determine which one to use. If the match
against available constructors is unclear, the reasons each available constructor failed to match will be printed
out. In the case of an ambiguous match, the first applicable constructor will be used
Parameter syntax
****************
Parameters are passed to RPC or flow commands using a syntax called `Yaml`_ (yet another markup language), a
simple JSON-like language. The key features of Yaml are:
* Parameters are separated by commas
* Each parameter is specified as a ``key: value`` pair
* There **MUST** to be a space after the colon, otherwise you'll get a syntax error
* Strings do not need to be surrounded by quotes unless they contain commas, colons or embedded quotes
* Class names must be fully-qualified (e.g. ``java.lang.String``)
.. note:: If your CorDapp is written in Java, named arguments won't work unless you compiled the node using the
``-parameters`` argument to javac. See :doc:`generating-a-node` for how to specify it via Gradle.
Creating an instance of a class
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Class instances are created using curly-bracket syntax. For example, if we have a ``Campaign`` class with the following
constructor:
``data class Campaign(val name: String, val target: Int)``
Then we could create an instance of this class to pass as a parameter as follows:
``newCampaign: { name: Roger, target: 1000 }``
Where ``newCampaign`` is a parameter of type ``Campaign``.
Mappings from strings to types
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Several parameter types can automatically be mapped from strings. See the `defined parsers`_ for more information. We
cover the most common types here.
Amount
~~~~~~
A parameter of type ``Amount<Currency>`` can be written as either:
* A dollar ($), pound (£) or euro (€) symbol followed by the amount as a decimal
* The amount as a decimal followed by the ISO currency code (e.g. "100.12 CHF")
OpaqueBytes
~~~~~~~~~~~
A parameter of type ``OpaqueBytes`` can be provided as a string, which will be automatically converted to
``OpaqueBytes``.
Party
~~~~~
A parameter of type ``Party`` can be written in several ways:
* By using the node's full name: ``"O=Monogram Bank,L=Sao Paulo,C=GB"``
* By specifying the organisation name only: ``"Monogram Bank"``
* By specifying any other non-ambiguous part of the name: ``"Sao Paulo"`` (if only one network node is located in Sao
Paulo)
Instant
~~~~~~~
A parameter of type ``Instant`` can be written as follows: ``"2017-12-22T00:00:00Z"``.
Examples
^^^^^^^^
Starting a flow
~~~~~~~~~~~~~~~
We would start the ``CashIssue`` flow as follows:
``flow start CashIssue amount: $1000, issueRef: 1234, recipient: "O=Bank A,L=London,C=GB", notary: "O=Notary Service,OU=corda,L=London,C=GB"`` ``flow start CashIssue amount: $1000, issueRef: 1234, recipient: "O=Bank A,L=London,C=GB", notary: "O=Notary Service,OU=corda,L=London,C=GB"``
This invokes a constructor of a flow with the following prototype in the code: This breaks down as follows:
* ``flow start`` is a shell command for starting a flow
* ``CashIssue`` is the flow we want to start
* Each ``name: value`` pair after that is a flow constructor argument
This command invokes the following ``CashIssue`` constructor:
.. container:: codeset .. container:: codeset
@ -117,50 +203,44 @@ This invokes a constructor of a flow with the following prototype in the code:
val recipient: Party, val recipient: Party,
val notary: Party) : AbstractCashFlow(progressTracker) val notary: Party) : AbstractCashFlow(progressTracker)
Here, everything after ``CashIssue`` is specifying the arguments to the constructor of a flow. In Yaml, an object Querying the vault
is specified as a set of ``key: value`` pairs and in our form, we separate them by commas. There are a few things ~~~~~~~~~~~~~~~~~~
to note about this syntax:
* When a parameter is of type ``Amount<Currency>`` you can write it as either one of the dollar symbol ($), We would query the vault for ``IOUState`` states as follows:
pound (£), euro (€) followed by the amount as a decimal, or as the value followed by the ISO currency code
e.g. "100.12 CHF"
* ``OpaqueBytes`` is filled with the contents of whatever is provided as a string.
* ``Party`` objects are looked up by name.
* Strings do not need to be surrounded by quotes unless they contain a comma or embedded quotes. This makes it
a lot more convenient to type such strings.
Other types also have sensible mappings from strings. See `the defined parsers`_ for more information. ``run vaultQuery contractStateType: com.template.IOUState``
Nested objects can be created using curly braces, as in ``{ a: 1, b: 2}``. This is helpful when no particular This breaks down as follows:
parser is defined for the type you need, for instance, if an API requires a ``Pair<String, Int>``
which could be represented as ``{ first: foo, second: 123 }``.
.. note:: If your CorDapp is written in Java, * ``run`` is a shell command for making an RPC call
named arguments won't work unless you compiled using the ``-parameters`` argument to javac. * ``vaultQuery`` is the RPC call we want to make
See :doc:`generating-a-node` for how to specify it via Gradle. * ``contractStateType: com.template.IOUState`` is the fully-qualified name of the state type we are querying for
The same syntax is also used to specify the parameters for RPCs, accessed via the ``run`` command, like this:
``run registeredFlows``
Attachments Attachments
----------- ***********
The shell can be used to upload and download attachments from the node interactively. To learn more, see The shell can be used to upload and download attachments from the node. To learn more, see the tutorial
the tutorial ":doc:`tutorial-attachments`". ":doc:`tutorial-attachments`".
Getting help
************
You can type ``help`` in the shell to list the available commands, and ``man`` to get interactive help on many
commands. You can also pass the ``--help`` or ``-h`` flags to a command to get info about what switches it supports.
Commands may have subcommands, in the same style as ``git``. In that case, running the command by itself will
list the supported subcommands.
Extending the shell Extending the shell
------------------- -------------------
The shell can be extended using commands written in either Java or `Groovy`_ (Groovy is a scripting language that The shell can be extended using commands written in either Java or `Groovy`_ (a Java-compatible scripting language).
is Java compatible). Such commands have full access to the node internal APIs and thus can be used to achieve These commands have full access to the node's internal APIs and thus can be used to achieve almost anything.
almost anything.
A full tutorial on how to write such commands is out of scope for this documentation, to learn more please A full tutorial on how to write such commands is out of scope for this documentation. To learn more, please refer to
refer to the `CRaSH`_ documentation. New commands can be placed in the ``shell-commands`` subdirectory in the the `CRaSH`_ documentation. New commands are placed in the ``shell-commands`` subdirectory in the node directory. Edits
node directory. Edits to existing commands will be used automatically, but at this time commands added after the to existing commands will be used automatically, but currently commands added after the node has started won't be
node has started won't be automatically detected. Commands should be named in all lower case with either a automatically detected. Commands must have names all in lower-case with either a ``.java`` or ``.groovy`` extension.
``.java`` or ``.groovy`` extension.
.. warning:: Commands written in Groovy ignore Java security checks, so have unrestricted access to node and JVM .. warning:: Commands written in Groovy ignore Java security checks, so have unrestricted access to node and JVM
internals regardless of any sandboxing that may be in place. Don't allow untrusted users to edit files in the internals regardless of any sandboxing that may be in place. Don't allow untrusted users to edit files in the
@ -171,14 +251,13 @@ Limitations
The shell will be enhanced over time. The currently known limitations include: The shell will be enhanced over time. The currently known limitations include:
* SSH access is currently not available. * There is no command completion for flows or RPCs
* There is no command completion for flows or RPCs. * Command history is not preserved across restarts
* Command history is not preserved across restarts. * The ``jdbc`` command requires you to explicitly log into the database first
* The ``jdbc`` command requires you to explicitly log into the database first. * Commands placed in the ``shell-commands`` directory are only noticed after the node is restarted
* Commands placed in the ``shell-commands`` directory are only noticed after the node is restarted. * The ``jul`` command advertises access to logs, but it doesn't work with the logging framework we're using
* The ``jul`` command advertises access to logs, but it doesn't work with the logging framework we're using.
.. _Yaml: http://www.yaml.org/spec/1.2/spec.html .. _Yaml: http://www.yaml.org/spec/1.2/spec.html
.. _the defined parsers: api/kotlin/corda/net.corda.client.jackson/-jackson-support/index.html .. _defined parsers: api/kotlin/corda/net.corda.client.jackson/-jackson-support/index.html
.. _Groovy: http://groovy-lang.org/ .. _Groovy: http://groovy-lang.org/
.. _CRaSH: http://www.crashub.org/ .. _CRaSH: http://www.crashub.org/

View File

@ -35,8 +35,14 @@ the transaction to the regulator. There are two important aspects to note here:
If the states define a relational mapping (see :doc:`api-persistence`) then the regulator will be able to query the If the states define a relational mapping (see :doc:`api-persistence`) then the regulator will be able to query the
reports from their database and observe new transactions coming in via RPC. reports from their database and observe new transactions coming in via RPC.
.. warning:: Nodes which act as both observers and which directly take part in the ledger are not supported at this Caveats
time. In particular, coin selection may return states which you do not have the private keys to be able to sign -------
for. Future versions of Corda may address this issue, but for now, if you wish to both participate in the ledger
and also observe transactions that you can't sign for you will need to run two nodes and have two separate * Nodes which act as both observers and direct participants in the ledger are not supported at this time. In
identities. particular, coin selection may return states which you do not have the private keys to be able to sign for. Future
versions of Corda may address this issue, but for now, if you wish to both participate in the ledger and also observe
transactions that you can't sign for you will need to run two nodes and have two separate identities
* Nodes only record each transaction once. If a node has already recorded a transaction in non-observer mode, it cannot
later re-record the same transaction as an observer. This issue is tracked here:
https://r3-cev.atlassian.net/browse/CORDA-883

View File

@ -26,7 +26,7 @@ additional classes are used when the jar is invoked directly. To do this we'll u
./gradlew experimental:quasar-hook:jar ./gradlew experimental:quasar-hook:jar
./gradlew samples:irs-demo:deployNodes ./gradlew samples:irs-demo:deployNodes
cd samples/irs-demo/build/nodes/NotaryService cd samples/irs-demo/build/nodes/NotaryService
java -javaagent:../../../../../experimental/quasar-hook/build/libs/quasar-hook.jar=expand=com,de,org,co,io;truncate=net.corda;alwaysExcluded=com.opengamma,io.atomix -jar corda.jar java -javaagent:../../../../../experimental/quasar-hook/build/libs/quasar-hook.jar=expand=com,de,org,co,io;truncate=net.corda;alwaysExcluded=com.opengamma,io.atomix,org.jolokia -jar corda.jar
``` ```
Once the node is started just exit the node. Once the node is started just exit the node.

View File

@ -90,8 +90,12 @@ private abstract class JavaCommand(
add(getJavaPath()) add(getJavaPath())
addAll(jvmArgs) addAll(jvmArgs)
add("-Dname=$nodeName") add("-Dname=$nodeName")
null != debugPort && add("-Dcapsule.jvm.args=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=$debugPort") val jvmArgs: MutableList<String> = mutableListOf()
null != monitoringPort && add("-Dcapsule.jvm.args=-javaagent:drivers/$jolokiaJar=port=$monitoringPort") null != debugPort && jvmArgs.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=$debugPort")
null != monitoringPort && jvmArgs.add("-javaagent:drivers/$jolokiaJar=port=$monitoringPort")
if (jvmArgs.isNotEmpty()) {
add("-Dcapsule.jvm.args=${jvmArgs.joinToString(separator = " ")}")
}
add("-jar") add("-jar")
add(jarName) add(jarName)
init() init()

View File

@ -3,14 +3,12 @@ package com.r3.corda.networkmanage.hsm
import com.nhaarman.mockito_kotlin.* import com.nhaarman.mockito_kotlin.*
import com.r3.corda.networkmanage.common.persistence.configureDatabase import com.r3.corda.networkmanage.common.persistence.configureDatabase
import com.r3.corda.networkmanage.common.utils.buildCertPath import com.r3.corda.networkmanage.common.utils.buildCertPath
import com.r3.corda.networkmanage.common.utils.toX509Certificate
import com.r3.corda.networkmanage.doorman.DoormanConfig import com.r3.corda.networkmanage.doorman.DoormanConfig
import com.r3.corda.networkmanage.doorman.NetworkManagementServer import com.r3.corda.networkmanage.doorman.NetworkManagementServer
import com.r3.corda.networkmanage.hsm.persistence.ApprovedCertificateRequestData import com.r3.corda.networkmanage.hsm.persistence.ApprovedCertificateRequestData
import com.r3.corda.networkmanage.hsm.persistence.DBSignedCertificateRequestStorage import com.r3.corda.networkmanage.hsm.persistence.DBSignedCertificateRequestStorage
import com.r3.corda.networkmanage.hsm.persistence.SignedCertificateRequestStorage import com.r3.corda.networkmanage.hsm.persistence.SignedCertificateRequestStorage
import com.r3.corda.networkmanage.hsm.signer.HsmCsrSigner import com.r3.corda.networkmanage.hsm.signer.HsmCsrSigner
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.createDirectories import net.corda.core.internal.createDirectories
@ -21,12 +19,14 @@ import net.corda.core.utilities.seconds
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
import net.corda.node.utilities.registration.NetworkRegistrationHelper import net.corda.node.utilities.registration.NetworkRegistrationHelper
import net.corda.nodeapi.internal.createDevNodeCa
import net.corda.nodeapi.internal.crypto.* import net.corda.nodeapi.internal.crypto.*
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.ALICE_NAME import net.corda.testing.ALICE_NAME
import net.corda.testing.BOB_NAME import net.corda.testing.BOB_NAME
import net.corda.testing.CHARLIE_NAME import net.corda.testing.CHARLIE_NAME
import net.corda.testing.SerializationEnvironmentRule import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.internal.createDevIntermediateCaCertPath
import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.rigorousMock
import org.bouncycastle.cert.X509CertificateHolder import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
@ -34,7 +34,6 @@ import org.h2.tools.Server
import org.junit.* import org.junit.*
import org.junit.rules.TemporaryFolder import org.junit.rules.TemporaryFolder
import java.net.URL import java.net.URL
import java.security.KeyPair
import java.util.* import java.util.*
import javax.persistence.PersistenceException import javax.persistence.PersistenceException
import kotlin.concurrent.scheduleAtFixedRate import kotlin.concurrent.scheduleAtFixedRate
@ -56,15 +55,15 @@ class SigningServiceIntegrationTest {
val testSerialization = SerializationEnvironmentRule(true) val testSerialization = SerializationEnvironmentRule(true)
private lateinit var timer: Timer private lateinit var timer: Timer
private lateinit var rootCAKey: KeyPair private lateinit var rootCaCert: X509CertificateHolder
private lateinit var rootCACert: X509CertificateHolder private lateinit var intermediateCa: CertificateAndKeyPair
@Before @Before
fun setUp() { fun setUp() {
timer = Timer() timer = Timer()
rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Integration Test Corda Node Root CA", rootCaCert = rootCa.certificate
organisation = "R3 Ltd", locality = "London", country = "GB"), rootCAKey) this.intermediateCa = intermediateCa
} }
@After @After
@ -73,22 +72,17 @@ class SigningServiceIntegrationTest {
} }
private fun givenSignerSigningAllRequests(storage: SignedCertificateRequestStorage): HsmCsrSigner { private fun givenSignerSigningAllRequests(storage: SignedCertificateRequestStorage): HsmCsrSigner {
// Create all certificates
val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey,
CordaX500Name(commonName = "Integration Test Corda Node Intermediate CA", locality = "London", country = "GB",
organisation = "R3 Ltd"), intermediateCAKey.public)
// Mock signing logic but keep certificate persistence // Mock signing logic but keep certificate persistence
return mock { return mock {
on { sign(any()) }.then { on { sign(any()) }.then {
val toSign: List<ApprovedCertificateRequestData> = uncheckedCast(it.arguments[0]) val approvedRequests: List<ApprovedCertificateRequestData> = uncheckedCast(it.arguments[0])
toSign.forEach { for (approvedRequest in approvedRequests) {
JcaPKCS10CertificationRequest(it.request).run { JcaPKCS10CertificationRequest(approvedRequest.request).run {
val certificate = X509Utilities.createCertificate(CertificateType.TLS, intermediateCACert, intermediateCAKey, subject, publicKey).toX509Certificate() val nodeCa = createDevNodeCa(intermediateCa, CordaX500Name.parse(subject.toString()))
it.certPath = buildCertPath(certificate, rootCACert.toX509Certificate()) approvedRequest.certPath = buildCertPath(nodeCa.certificate.cert, intermediateCa.certificate.cert, rootCaCert.cert)
} }
} }
storage.store(toSign, listOf("TEST")) storage.store(approvedRequests, listOf("TEST"))
} }
} }
} }
@ -128,9 +122,9 @@ class SigningServiceIntegrationTest {
// [org.hibernate.tool.schema.spi.SchemaManagementException] being thrown as the schema is missing. // [org.hibernate.tool.schema.spi.SchemaManagementException] being thrown as the schema is missing.
} }
} }
config.trustStoreFile.parent.createDirectories() config.certificatesDirectory.createDirectories()
loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword).also { loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword).also {
it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCACert.cert) it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCaCert.cert)
it.save(config.trustStoreFile, config.trustStorePassword) it.save(config.trustStoreFile, config.trustStorePassword)
} }
NetworkRegistrationHelper(config, HTTPNetworkRegistrationService(config.compatibilityZoneURL!!)).buildKeystore() NetworkRegistrationHelper(config, HTTPNetworkRegistrationService(config.compatibilityZoneURL!!)).buildKeystore()
@ -172,9 +166,9 @@ class SigningServiceIntegrationTest {
}).whenever(it).myLegalName }).whenever(it).myLegalName
doReturn(URL("http://$HOST:${server.hostAndPort.port}")).whenever(it).compatibilityZoneURL doReturn(URL("http://$HOST:${server.hostAndPort.port}")).whenever(it).compatibilityZoneURL
} }
config.trustStoreFile.parent.createDirectories() config.certificatesDirectory.createDirectories()
loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword).also { loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword).also {
it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCACert.cert) it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCaCert.cert)
it.save(config.trustStoreFile, config.trustStorePassword) it.save(config.trustStoreFile, config.trustStorePassword)
} }
NetworkRegistrationHelper(config, HTTPNetworkRegistrationService(config.compatibilityZoneURL!!)).buildKeystore() NetworkRegistrationHelper(config, HTTPNetworkRegistrationService(config.compatibilityZoneURL!!)).buildKeystore()
@ -183,7 +177,7 @@ class SigningServiceIntegrationTest {
} }
} }
fun createConfig(): NodeConfiguration { private fun createConfig(): NodeConfiguration {
return rigorousMock<NodeConfiguration>().also { return rigorousMock<NodeConfiguration>().also {
doReturn(tempFolder.root.toPath()).whenever(it).baseDirectory doReturn(tempFolder.root.toPath()).whenever(it).baseDirectory
doReturn(it.baseDirectory / "certificates").whenever(it).certificatesDirectory doReturn(it.baseDirectory / "certificates").whenever(it).certificatesDirectory
@ -195,15 +189,15 @@ class SigningServiceIntegrationTest {
doReturn("iTest@R3.com").whenever(it).emailAddress doReturn("iTest@R3.com").whenever(it).emailAddress
} }
} }
private fun makeTestDataSourceProperties(): Properties {
val props = Properties()
props.setProperty("dataSourceClassName", "org.h2.jdbcx.JdbcDataSource")
props.setProperty("dataSource.url", "jdbc:h2:mem:${SigningServiceIntegrationTest.DB_NAME};DB_CLOSE_DELAY=-1")
props.setProperty("dataSource.user", "sa")
props.setProperty("dataSource.password", "")
return props
}
} }
private fun makeTestDataSourceProperties(): Properties { internal fun makeNotInitialisingTestDatabaseProperties() = DatabaseConfig(runMigration = false)
val props = Properties()
props.setProperty("dataSourceClassName", "org.h2.jdbcx.JdbcDataSource")
props.setProperty("dataSource.url", "jdbc:h2:mem:${SigningServiceIntegrationTest.DB_NAME};DB_CLOSE_DELAY=-1")
props.setProperty("dataSource.user", "sa")
props.setProperty("dataSource.password", "")
return props
}
internal fun makeNotInitialisingTestDatabaseProperties() = DatabaseConfig(runMigration = false)

View File

@ -0,0 +1,80 @@
package net.corda.nodeapi.internal
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.cert
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.internal.toX509CertHolder
import net.corda.core.utilities.trace
import net.corda.nodeapi.internal.config.NodeSSLConfiguration
import net.corda.nodeapi.internal.crypto.*
import org.slf4j.LoggerFactory
import java.nio.file.Path
/**
* Contains utility methods for generating identities for a node.
*
* WARNING: This is not application for production use.
*/
object DevIdentityGenerator {
private val log = LoggerFactory.getLogger(javaClass)
// TODO These don't need to be prefixes but can be the full aliases
// TODO Move these constants out of here as the node needs access to them
const val NODE_IDENTITY_ALIAS_PREFIX = "identity"
const val DISTRIBUTED_NOTARY_ALIAS_PREFIX = "distributed-notary"
/** Install a node key store for the given node directory using the given legal name. */
fun installKeyStoreWithNodeIdentity(nodeDir: Path, legalName: CordaX500Name): Party {
val nodeSslConfig = object : NodeSSLConfiguration {
override val baseDirectory = nodeDir
override val keyStorePassword: String = "cordacadevpass"
override val trustStorePassword get() = throw NotImplementedError("Not expected to be called")
}
// TODO The passwords for the dev key stores are spread everywhere and should be constants in a single location
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
val intermediateCa = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
val rootCert = caKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA)
nodeSslConfig.certificatesDirectory.createDirectories()
nodeSslConfig.createDevKeyStores(rootCert.toX509CertHolder(), intermediateCa, legalName)
val keyStoreWrapper = KeyStoreWrapper(nodeSslConfig.nodeKeystore, nodeSslConfig.keyStorePassword)
val identity = keyStoreWrapper.storeLegalIdentity(legalName, "$NODE_IDENTITY_ALIAS_PREFIX-private-key", Crypto.generateKeyPair())
return identity.party
}
fun generateDistributedNotaryIdentity(dirs: List<Path>, notaryName: CordaX500Name, threshold: Int = 1): Party {
require(dirs.isNotEmpty())
log.trace { "Generating identity \"$notaryName\" for nodes: ${dirs.joinToString()}" }
val keyPairs = (1..dirs.size).map { generateKeyPair() }
val compositeKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold)
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
val intermediateCa = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
val rootCert = caKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA)
keyPairs.zip(dirs) { keyPair, nodeDir ->
val (serviceKeyCert, compositeKeyCert) = listOf(keyPair.public, compositeKey).map { publicKey ->
X509Utilities.createCertificate(CertificateType.SERVICE_IDENTITY, intermediateCa.certificate, intermediateCa.keyPair, notaryName, publicKey)
}
val distServKeyStoreFile = (nodeDir / "certificates").createDirectories() / "distributedService.jks"
val keystore = loadOrCreateKeyStore(distServKeyStoreFile, "cordacadevpass")
keystore.setCertificateEntry("$DISTRIBUTED_NOTARY_ALIAS_PREFIX-composite-key", compositeKeyCert.cert)
keystore.setKeyEntry(
"$DISTRIBUTED_NOTARY_ALIAS_PREFIX-private-key",
keyPair.private,
"cordacadevkeypass".toCharArray(),
arrayOf(serviceKeyCert.cert, intermediateCa.certificate.cert, rootCert))
keystore.save(distServKeyStoreFile, "cordacadevpass")
}
return Party(notaryName, compositeKey)
}
}

View File

@ -1,66 +0,0 @@
package net.corda.nodeapi.internal
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.cert
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.slf4j.LoggerFactory
import java.nio.file.Path
import java.security.cert.X509Certificate
object IdentityGenerator {
private val log = LoggerFactory.getLogger(javaClass)
const val NODE_IDENTITY_ALIAS_PREFIX = "identity"
const val DISTRIBUTED_NOTARY_ALIAS_PREFIX = "distributed-notary"
fun generateNodeIdentity(dir: Path, legalName: CordaX500Name, customRootCert: X509Certificate? = null): Party {
return generateToDisk(listOf(dir), legalName, NODE_IDENTITY_ALIAS_PREFIX, threshold = 1, customRootCert = customRootCert)
}
fun generateDistributedNotaryIdentity(dirs: List<Path>, notaryName: CordaX500Name, threshold: Int = 1, customRootCert: X509Certificate? = null): Party {
return generateToDisk(dirs, notaryName, DISTRIBUTED_NOTARY_ALIAS_PREFIX, threshold, customRootCert)
}
/**
* 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.
* This method should be called *before* any of the nodes are started.
*
* @param dirs List of node directories to place the generated identity and key pairs in.
* @param name The name of the identity.
* @param threshold The threshold for the generated group [CompositeKey].
* @param customRootCert the certificate to use as the Corda root CA. If not specified the one in
* internal/certificates/cordadevcakeys.jks is used.
*/
private fun generateToDisk(dirs: List<Path>,
name: CordaX500Name,
aliasPrefix: String,
threshold: Int,
customRootCert: X509Certificate?): Party {
log.trace { "Generating identity \"$name\" for nodes: ${dirs.joinToString()}" }
val keyPairs = (1..dirs.size).map { generateKeyPair() }
val key = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold)
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
val intermediateCa = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
val rootCert = customRootCert ?: caKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA)
keyPairs.zip(dirs) { keyPair, dir ->
val serviceKeyCert = X509Utilities.createCertificate(CertificateType.SERVICE_IDENTITY, intermediateCa.certificate, intermediateCa.keyPair, name, keyPair.public)
val compositeKeyCert = X509Utilities.createCertificate(CertificateType.SERVICE_IDENTITY, intermediateCa.certificate, intermediateCa.keyPair, name, key)
val certPath = (dir / "certificates").createDirectories() / "distributedService.jks"
val keystore = loadOrCreateKeyStore(certPath, "cordacadevpass")
keystore.setCertificateEntry("$aliasPrefix-composite-key", compositeKeyCert.cert)
keystore.setKeyEntry("$aliasPrefix-private-key", keyPair.private, "cordacadevkeypass".toCharArray(), arrayOf(serviceKeyCert.cert, intermediateCa.certificate.cert, rootCert))
keystore.save(certPath, "cordacadevpass")
}
return Party(name, key)
}
}

View File

@ -0,0 +1,57 @@
package net.corda.nodeapi.internal
import net.corda.core.crypto.Crypto
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.x500Name
import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.crypto.*
import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralSubtree
import org.bouncycastle.asn1.x509.NameConstraints
import org.bouncycastle.cert.X509CertificateHolder
/**
* Create the node and SSL key stores needed by a node. The node key store will be populated with a node CA cert (using
* the given legal name), and the SSL key store will store the TLS cert which is a sub-cert of the node CA.
*/
fun SSLConfiguration.createDevKeyStores(rootCert: X509CertificateHolder, intermediateCa: CertificateAndKeyPair, legalName: CordaX500Name) {
val (nodeCaCert, nodeCaKeyPair) = createDevNodeCa(intermediateCa, legalName)
loadOrCreateKeyStore(nodeKeystore, keyStorePassword).apply {
addOrReplaceKey(
X509Utilities.CORDA_CLIENT_CA,
nodeCaKeyPair.private,
keyStorePassword.toCharArray(),
arrayOf(nodeCaCert, intermediateCa.certificate, rootCert))
save(nodeKeystore, keyStorePassword)
}
val tlsKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, nodeCaCert, nodeCaKeyPair, legalName, tlsKeyPair.public)
loadOrCreateKeyStore(sslKeystore, keyStorePassword).apply {
addOrReplaceKey(
X509Utilities.CORDA_CLIENT_TLS,
tlsKeyPair.private,
keyStorePassword.toCharArray(),
arrayOf(tlsCert, nodeCaCert, intermediateCa.certificate, rootCert))
save(sslKeystore, keyStorePassword)
}
}
/**
* Create a dev node CA cert, as a sub-cert of the given [intermediateCa], and matching key pair using the given
* [CordaX500Name] as the cert subject.
*/
fun createDevNodeCa(intermediateCa: CertificateAndKeyPair, legalName: CordaX500Name): CertificateAndKeyPair {
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName.x500Name))), arrayOf())
val cert = X509Utilities.createCertificate(
CertificateType.NODE_CA,
intermediateCa.certificate,
intermediateCa.keyPair,
legalName,
keyPair.public,
nameConstraints = nameConstraints)
return CertificateAndKeyPair(cert, keyPair)
}

View File

@ -8,6 +8,7 @@ interface SSLConfiguration {
val trustStorePassword: String val trustStorePassword: String
val certificatesDirectory: Path val certificatesDirectory: Path
val sslKeystore: Path get() = certificatesDirectory / "sslkeystore.jks" val sslKeystore: Path get() = certificatesDirectory / "sslkeystore.jks"
// TODO This looks like it should be in NodeSSLConfiguration
val nodeKeystore: Path get() = certificatesDirectory / "nodekeystore.jks" val nodeKeystore: Path get() = certificatesDirectory / "nodekeystore.jks"
val trustStoreFile: Path get() = certificatesDirectory / "truststore.jks" val trustStoreFile: Path get() = certificatesDirectory / "truststore.jks"
} }

View File

@ -1,41 +1,26 @@
package net.corda.nodeapi.internal.crypto package net.corda.nodeapi.internal.crypto
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.cert import net.corda.core.internal.cert
import net.corda.core.internal.read import net.corda.core.internal.read
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.cert.CertPath
import java.security.cert.Certificate import java.security.cert.Certificate
class KeyStoreWrapper(private val storePath: Path, private val storePassword: String) { class KeyStoreWrapper(private val storePath: Path, private val storePassword: String) {
private val keyStore = storePath.read { loadKeyStore(it, storePassword) } private val keyStore = storePath.read { loadKeyStore(it, storePassword) }
private fun createCertificate(serviceName: CordaX500Name, pubKey: PublicKey): CertPath { // TODO This method seems misplaced in this class.
val clientCertPath = keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA) fun storeLegalIdentity(legalName: CordaX500Name, alias: String, keyPair: KeyPair): PartyAndCertificate {
val nodeCaCertChain = keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
val nodeCa = getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
val identityCert = X509Utilities.createCertificate(CertificateType.LEGAL_IDENTITY, nodeCa.certificate, nodeCa.keyPair, legalName, keyPair.public)
val identityCertPath = X509CertificateFactory().generateCertPath(identityCert.cert, *nodeCaCertChain)
// Assume key password = store password. // Assume key password = store password.
val clientCA = certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA) keyStore.addOrReplaceKey(alias, keyPair.private, storePassword.toCharArray(), identityCertPath.certificates.toTypedArray())
// Create new keys and store in keystore.
val cert = X509Utilities.createCertificate(CertificateType.LEGAL_IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, pubKey)
val certPath = X509CertificateFactory().generateCertPath(cert.cert, *clientCertPath)
require(certPath.certificates.isNotEmpty()) { "Certificate path cannot be empty" }
// TODO: X509Utilities.validateCertificateChain()
return certPath
}
fun signAndSaveNewKeyPair(serviceName: CordaX500Name, privateKeyAlias: String, keyPair: KeyPair) {
val certPath = createCertificate(serviceName, keyPair.public)
// Assume key password = store password.
keyStore.addOrReplaceKey(privateKeyAlias, keyPair.private, storePassword.toCharArray(), certPath.certificates.toTypedArray())
keyStore.save(storePath, storePassword)
}
fun savePublicKey(serviceName: CordaX500Name, pubKeyAlias: String, pubKey: PublicKey) {
val certPath = createCertificate(serviceName, pubKey)
// Assume key password = store password.
keyStore.addOrReplaceCertificate(pubKeyAlias, certPath.certificates.first())
keyStore.save(storePath, storePassword) keyStore.save(storePath, storePassword)
return PartyAndCertificate(identityCertPath)
} }
// Delegate methods to keystore. Sadly keystore doesn't have an interface. // Delegate methods to keystore. Sadly keystore doesn't have an interface.
@ -47,5 +32,5 @@ class KeyStoreWrapper(private val storePath: Path, private val storePassword: St
fun getCertificate(alias: String): Certificate = keyStore.getCertificate(alias) fun getCertificate(alias: String): Certificate = keyStore.getCertificate(alias)
fun certificateAndKeyPair(alias: String): CertificateAndKeyPair = keyStore.getCertificateAndKeyPair(alias, storePassword) fun getCertificateAndKeyPair(alias: String): CertificateAndKeyPair = keyStore.getCertificateAndKeyPair(alias, storePassword)
} }

View File

@ -4,12 +4,8 @@ import net.corda.core.CordaOID
import net.corda.core.crypto.Crypto 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.internal.CertRole
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.cert import net.corda.core.internal.*
import net.corda.core.internal.reader
import net.corda.core.internal.writer
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
import org.bouncycastle.asn1.* import org.bouncycastle.asn1.*
@ -43,6 +39,7 @@ object X509Utilities {
val DEFAULT_IDENTITY_SIGNATURE_SCHEME = Crypto.EDDSA_ED25519_SHA512 val DEFAULT_IDENTITY_SIGNATURE_SCHEME = Crypto.EDDSA_ED25519_SHA512
val DEFAULT_TLS_SIGNATURE_SCHEME = Crypto.ECDSA_SECP256R1_SHA256 val DEFAULT_TLS_SIGNATURE_SCHEME = Crypto.ECDSA_SECP256R1_SHA256
// TODO This class is more of a general purpose utility class and as such these constants belong elsewhere
// Aliases for private keys and certificates. // Aliases for private keys and certificates.
const val CORDA_ROOT_CA = "cordarootca" const val CORDA_ROOT_CA = "cordarootca"
const val CORDA_INTERMEDIATE_CA = "cordaintermediateca" const val CORDA_INTERMEDIATE_CA = "cordaintermediateca"

View File

@ -4,23 +4,28 @@ import com.typesafe.config.ConfigFactory
import net.corda.cordform.CordformNode import net.corda.cordform.CordformNode
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.* import net.corda.core.internal.*
import net.corda.core.internal.concurrent.fork
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.serialization.internal.SerializationEnvironmentImpl import net.corda.core.serialization.internal.SerializationEnvironmentImpl
import net.corda.core.serialization.internal._contextSerializationEnv import net.corda.core.serialization.internal._contextSerializationEnv
import net.corda.core.utilities.ByteSequence import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds
import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
import java.nio.file.Files
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 import java.nio.file.StandardCopyOption
import java.time.Instant import java.time.Instant
import java.util.concurrent.TimeUnit.SECONDS import java.util.concurrent.Executors
import java.util.concurrent.TimeoutException
import kotlin.streams.toList import kotlin.streams.toList
/** /**
@ -48,13 +53,14 @@ class NetworkBootstrapper {
fun bootstrap(directory: Path) { fun bootstrap(directory: Path) {
directory.createDirectories() directory.createDirectories()
println("Bootstrapping local network in $directory") println("Bootstrapping local network in $directory")
generateDirectoriesIfNeeded(directory)
val nodeDirs = directory.list { paths -> paths.filter { (it / "corda.jar").exists() }.toList() } val nodeDirs = directory.list { paths -> paths.filter { (it / "corda.jar").exists() }.toList() }
require(nodeDirs.isNotEmpty()) { "No nodes found" } require(nodeDirs.isNotEmpty()) { "No nodes found" }
println("Nodes found in the following sub-directories: ${nodeDirs.map { it.fileName }}") println("Nodes found in the following sub-directories: ${nodeDirs.map { it.fileName }}")
val processes = startNodeInfoGeneration(nodeDirs) val processes = startNodeInfoGeneration(nodeDirs)
initialiseSerialization() initialiseSerialization()
try { try {
println("Waiting for all nodes to generate their node-info files") println("Waiting for all nodes to generate their node-info files...")
val nodeInfoFiles = gatherNodeInfoFiles(processes, nodeDirs) val nodeInfoFiles = gatherNodeInfoFiles(processes, nodeDirs)
println("Distributing all node info-files to all nodes") println("Distributing all node info-files to all nodes")
distributeNodeInfos(nodeDirs, nodeInfoFiles) distributeNodeInfos(nodeDirs, nodeInfoFiles)
@ -69,6 +75,27 @@ class NetworkBootstrapper {
} }
} }
private fun generateDirectoriesIfNeeded(directory: Path) {
val confFiles = directory.list { it.filter { it.toString().endsWith(".conf") }.toList() }
if (confFiles.isEmpty()) return
println("Node config files found in the root directory - generating node directories")
val cordaJar = extractCordaJarTo(directory)
for (confFile in confFiles) {
val nodeName = confFile.fileName.toString().removeSuffix(".conf")
println("Generating directory for $nodeName")
val nodeDir = (directory / nodeName).createDirectory()
confFile.moveTo(nodeDir / "node.conf")
Files.copy(cordaJar, (nodeDir / "corda.jar"))
}
Files.delete(cordaJar)
}
private fun extractCordaJarTo(directory: Path): Path {
val cordaJarPath = (directory / "corda.jar")
Thread.currentThread().contextClassLoader.getResourceAsStream("corda.jar").copyTo(cordaJarPath)
return cordaJarPath
}
private fun startNodeInfoGeneration(nodeDirs: List<Path>): List<Process> { private fun startNodeInfoGeneration(nodeDirs: List<Path>): List<Process> {
return nodeDirs.map { nodeDir -> return nodeDirs.map { nodeDir ->
val logsDir = (nodeDir / LOGS_DIR_NAME).createDirectories() val logsDir = (nodeDir / LOGS_DIR_NAME).createDirectories()
@ -82,15 +109,22 @@ class NetworkBootstrapper {
} }
private fun gatherNodeInfoFiles(processes: List<Process>, nodeDirs: List<Path>): List<Path> { private fun gatherNodeInfoFiles(processes: List<Process>, nodeDirs: List<Path>): List<Path> {
val timeOutInSeconds = 60L val executor = Executors.newSingleThreadExecutor()
return processes.zip(nodeDirs).map { (process, nodeDir) ->
check(process.waitFor(timeOutInSeconds, SECONDS)) { val future = executor.fork {
"Node in ${nodeDir.fileName} took longer than ${timeOutInSeconds}s to generate its node-info - see logs in ${nodeDir / LOGS_DIR_NAME}" processes.zip(nodeDirs).map { (process, nodeDir) ->
check(process.waitFor() == 0) {
"Node in ${nodeDir.fileName} exited with ${process.exitValue()} when generating its node-info - see logs in ${nodeDir / LOGS_DIR_NAME}"
}
nodeDir.list { paths -> paths.filter { it.fileName.toString().startsWith("nodeInfo-") }.findFirst().get() }
} }
check(process.exitValue() == 0) { }
"Node in ${nodeDir.fileName} exited with ${process.exitValue()} when generating its node-info - see logs in ${nodeDir / LOGS_DIR_NAME}"
} return try {
nodeDir.list { paths -> paths.filter { it.fileName.toString().startsWith("nodeInfo-") }.findFirst().get() } future.getOrThrow(60.seconds)
} catch (e: TimeoutException) {
println("...still waiting. If this is taking longer than usual, check the node logs.")
future.getOrThrow()
} }
} }
@ -136,10 +170,10 @@ class NetworkBootstrapper {
private fun NodeInfo.notaryIdentity(): Party { private fun NodeInfo.notaryIdentity(): Party {
return when (legalIdentities.size) { return when (legalIdentities.size) {
// Single node notaries have just one identity like all other nodes. This identity is the notary identity // Single node notaries have just one identity like all other nodes. This identity is the notary identity
1 -> legalIdentities[0] 1 -> legalIdentities[0]
// Nodes which are part of a distributed notary have a second identity which is the composite identity of the // Nodes which are part of a distributed notary have a second identity which is the composite identity of the
// cluster and is shared by all the other members. This is the notary identity. // cluster and is shared by all the other members. This is the notary identity.
2 -> legalIdentities[1] 2 -> legalIdentities[1]
else -> throw IllegalArgumentException("Not sure how to get the notary identity in this scenerio: $this") else -> throw IllegalArgumentException("Not sure how to get the notary identity in this scenerio: $this")
} }
@ -161,6 +195,7 @@ class NetworkBootstrapper {
override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean { override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean {
return byteSequence == KryoHeaderV0_1 && target == SerializationContext.UseCase.P2P return byteSequence == KryoHeaderV0_1 && target == SerializationContext.UseCase.P2P
} }
override fun rpcClientKryoPool(context: SerializationContext) = throw UnsupportedOperationException() override fun rpcClientKryoPool(context: SerializationContext) = throw UnsupportedOperationException()
override fun rpcServerKryoPool(context: SerializationContext) = throw UnsupportedOperationException() override fun rpcServerKryoPool(context: SerializationContext) = throw UnsupportedOperationException()
} }

View File

@ -12,12 +12,17 @@ import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.node.serialization.KryoServerSerializationScheme import net.corda.node.serialization.KryoServerSerializationScheme
import net.corda.node.services.config.createKeystoreForCordaNode import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.createDevKeyStores
import net.corda.nodeapi.internal.serialization.AllWhitelist import net.corda.nodeapi.internal.serialization.AllWhitelist
import net.corda.nodeapi.internal.serialization.SerializationContextImpl import net.corda.nodeapi.internal.serialization.SerializationContextImpl
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
import net.corda.testing.* import net.corda.testing.ALICE_NAME
import net.corda.testing.BOB_NAME
import net.corda.testing.TestIdentity
import net.corda.testing.internal.createDevIntermediateCaCertPath
import org.assertj.core.api.Assertions.assertThat
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.BasicConstraints import org.bouncycastle.asn1.x509.BasicConstraints
import org.bouncycastle.asn1.x509.Extension import org.bouncycastle.asn1.x509.Extension
@ -33,11 +38,8 @@ import java.io.IOException
import java.net.InetAddress import java.net.InetAddress
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.nio.file.Path import java.nio.file.Path
import java.security.KeyStore
import java.security.PrivateKey
import java.security.SecureRandom import java.security.SecureRandom
import java.security.cert.CertPath import java.security.cert.CertPath
import java.security.cert.Certificate
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import java.util.* import java.util.*
import java.util.stream.Stream import java.util.stream.Stream
@ -52,6 +54,11 @@ class X509UtilitiesTest {
val MEGA_CORP = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")).party val MEGA_CORP = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")).party
val BOB get() = bob.party val BOB get() = bob.party
val BOB_PUBKEY get() = bob.publicKey val BOB_PUBKEY get() = bob.publicKey
val CIPHER_SUITES = arrayOf(
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256"
)
} }
@Rule @Rule
@ -155,107 +162,62 @@ class X509UtilitiesTest {
assertEquals(edDSAKeypair.private, privateKey) assertEquals(edDSAKeypair.private, privateKey)
} }
@Test
fun `create full CA keystore`() {
val tmpKeyStore = tempFile("keystore.jks")
val tmpTrustStore = tempFile("truststore.jks")
// Generate Root and Intermediate CA cert and put both into key store and root ca cert into trust store
createCAKeyStoreAndTrustStore(tmpKeyStore, "keystorepass", "keypass", tmpTrustStore, "trustpass")
// Load back generated root CA Cert and private key from keystore and check against copy in truststore
val keyStore = loadKeyStore(tmpKeyStore, "keystorepass")
val trustStore = loadKeyStore(tmpTrustStore, "trustpass")
val rootCaCert = keyStore.getCertificate(X509Utilities.CORDA_ROOT_CA) as X509Certificate
val rootCaPrivateKey = keyStore.getKey(X509Utilities.CORDA_ROOT_CA, "keypass".toCharArray()) as PrivateKey
val rootCaFromTrustStore = trustStore.getCertificate(X509Utilities.CORDA_ROOT_CA) as X509Certificate
assertEquals(rootCaCert, rootCaFromTrustStore)
rootCaCert.checkValidity(Date())
rootCaCert.verify(rootCaCert.publicKey)
// Now sign something with private key and verify against certificate public key
val testData = "12345".toByteArray()
val caSignature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, rootCaPrivateKey, testData)
assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, rootCaCert.publicKey, caSignature, testData) }
// Load back generated intermediate CA Cert and private key
val intermediateCaCert = keyStore.getCertificate(X509Utilities.CORDA_INTERMEDIATE_CA) as X509Certificate
val intermediateCaCertPrivateKey = keyStore.getKey(X509Utilities.CORDA_INTERMEDIATE_CA, "keypass".toCharArray()) as PrivateKey
intermediateCaCert.checkValidity(Date())
intermediateCaCert.verify(rootCaCert.publicKey)
// Now sign something with private key and verify against certificate public key
val intermediateSignature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCertPrivateKey, testData)
assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCert.publicKey, intermediateSignature, testData) }
}
@Test @Test
fun `create server certificate in keystore for SSL`() { fun `create server certificate in keystore for SSL`() {
val tmpCAKeyStore = tempFile("keystore.jks") val sslConfig = object : SSLConfiguration {
val tmpTrustStore = tempFile("truststore.jks") override val certificatesDirectory = tempFolder.root.toPath()
val tmpSSLKeyStore = tempFile("sslkeystore.jks") override val keyStorePassword = "serverstorepass"
val tmpServerKeyStore = tempFile("serverkeystore.jks") override val trustStorePassword = "trustpass"
}
// Generate Root and Intermediate CA cert and put both into key store and root ca cert into trust store val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
createCAKeyStoreAndTrustStore(tmpCAKeyStore,
"cakeystorepass",
"cakeypass",
tmpTrustStore,
"trustpass")
// Load signing intermediate CA cert
val caKeyStore = loadKeyStore(tmpCAKeyStore, "cakeystorepass")
val caCertAndKey = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cakeypass")
// Generate server cert and private key and populate another keystore suitable for SSL // Generate server cert and private key and populate another keystore suitable for SSL
createKeystoreForCordaNode(tmpSSLKeyStore, tmpServerKeyStore, "serverstorepass", "serverkeypass", caKeyStore, "cakeypass", MEGA_CORP.name) sslConfig.createDevKeyStores(rootCa.certificate, intermediateCa, MEGA_CORP.name)
// Load back server certificate // Load back server certificate
val serverKeyStore = loadKeyStore(tmpServerKeyStore, "serverstorepass") val serverKeyStore = loadKeyStore(sslConfig.nodeKeystore, sslConfig.keyStorePassword)
val serverCertAndKey = serverKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, "serverkeypass") val (serverCert, serverKeyPair) = serverKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, sslConfig.keyStorePassword)
serverCertAndKey.certificate.isValidOn(Date()) serverCert.cert.checkValidity()
serverCertAndKey.certificate.isSignatureValid(JcaContentVerifierProviderBuilder().build(caCertAndKey.certificate.subjectPublicKeyInfo)) serverCert.cert.verify(intermediateCa.certificate.cert.publicKey)
assertThat(CordaX500Name.parse(serverCert.subject.toString())).isEqualTo(MEGA_CORP.name)
assertTrue { serverCertAndKey.certificate.subject.toString().contains(MEGA_CORP.name.organisation) } // Load back SSL certificate
val sslKeyStore = loadKeyStore(sslConfig.sslKeystore, sslConfig.keyStorePassword)
val (sslCert) = sslKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_TLS, sslConfig.keyStorePassword)
// Load back server certificate sslCert.cert.checkValidity()
val sslKeyStore = loadKeyStore(tmpSSLKeyStore, "serverstorepass") sslCert.cert.verify(serverCert.cert.publicKey)
val sslCertAndKey = sslKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_TLS, "serverkeypass") assertThat(CordaX500Name.parse(sslCert.subject.toString())).isEqualTo(MEGA_CORP.name)
sslCertAndKey.certificate.isValidOn(Date())
sslCertAndKey.certificate.isSignatureValid(JcaContentVerifierProviderBuilder().build(serverCertAndKey.certificate.subjectPublicKeyInfo))
assertTrue { sslCertAndKey.certificate.subject.toString().contains(MEGA_CORP.name.organisation) }
// Now sign something with private key and verify against certificate public key // Now sign something with private key and verify against certificate public key
val testData = "123456".toByteArray() val testData = "123456".toByteArray()
val signature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.keyPair.private, testData) val signature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, serverKeyPair.private, testData)
val publicKey = Crypto.toSupportedPublicKey(serverCertAndKey.certificate.subjectPublicKeyInfo) val publicKey = Crypto.toSupportedPublicKey(serverCert.subjectPublicKeyInfo)
assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, publicKey, signature, testData) } assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, publicKey, signature, testData) }
} }
@Test @Test
fun `create server cert and use in SSL socket`() { fun `create server cert and use in SSL socket`() {
val tmpCAKeyStore = tempFile("keystore.jks") val sslConfig = object : SSLConfiguration {
val tmpTrustStore = tempFile("truststore.jks") override val certificatesDirectory = tempFolder.root.toPath()
val tmpSSLKeyStore = tempFile("sslkeystore.jks") override val keyStorePassword = "serverstorepass"
val tmpServerKeyStore = tempFile("serverkeystore.jks") override val trustStorePassword = "trustpass"
}
// Generate Root and Intermediate CA cert and put both into key store and root ca cert into trust store val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
val caKeyStore = createCAKeyStoreAndTrustStore(tmpCAKeyStore,
"cakeystorepass",
"cakeypass",
tmpTrustStore,
"trustpass")
// Generate server cert and private key and populate another keystore suitable for SSL // Generate server cert and private key and populate another keystore suitable for SSL
createKeystoreForCordaNode(tmpSSLKeyStore, tmpServerKeyStore, "serverstorepass", "serverstorepass", caKeyStore, "cakeypass", MEGA_CORP.name) sslConfig.createDevKeyStores(rootCa.certificate, intermediateCa, MEGA_CORP.name)
val keyStore = loadKeyStore(tmpSSLKeyStore, "serverstorepass") sslConfig.createTrustStore(rootCa.certificate.cert)
val trustStore = loadKeyStore(tmpTrustStore, "trustpass")
val keyStore = loadKeyStore(sslConfig.sslKeystore, sslConfig.keyStorePassword)
val trustStore = loadKeyStore(sslConfig.trustStoreFile, sslConfig.trustStorePassword)
val context = SSLContext.getInstance("TLS") val context = SSLContext.getInstance("TLS")
val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
keyManagerFactory.init(keyStore, "serverstorepass".toCharArray()) keyManagerFactory.init(keyStore, sslConfig.keyStorePassword.toCharArray())
val keyManagers = keyManagerFactory.keyManagers val keyManagers = keyManagerFactory.keyManagers
val trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) val trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
trustMgrFactory.init(trustStore) trustMgrFactory.init(trustStore)
@ -266,13 +228,7 @@ class X509UtilitiesTest {
val clientSocketFactory = context.socketFactory val clientSocketFactory = context.socketFactory
val serverSocket = serverSocketFactory.createServerSocket(0) as SSLServerSocket // use 0 to get first free socket val serverSocket = serverSocketFactory.createServerSocket(0) as SSLServerSocket // use 0 to get first free socket
val serverParams = SSLParameters(arrayOf("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", val serverParams = SSLParameters(CIPHER_SUITES, arrayOf("TLSv1.2"))
"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256"),
arrayOf("TLSv1.2"))
serverParams.wantClientAuth = true serverParams.wantClientAuth = true
serverParams.needClientAuth = true serverParams.needClientAuth = true
serverParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator. serverParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator.
@ -280,13 +236,7 @@ class X509UtilitiesTest {
serverSocket.useClientMode = false serverSocket.useClientMode = false
val clientSocket = clientSocketFactory.createSocket() as SSLSocket val clientSocket = clientSocketFactory.createSocket() as SSLSocket
val clientParams = SSLParameters(arrayOf("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", val clientParams = SSLParameters(CIPHER_SUITES, arrayOf("TLSv1.2"))
"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256"),
arrayOf("TLSv1.2"))
clientParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator. clientParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator.
clientSocket.sslParameters = clientParams clientSocket.sslParameters = clientParams
clientSocket.useClientMode = true clientSocket.useClientMode = true
@ -344,60 +294,14 @@ class X509UtilitiesTest {
private fun tempFile(name: String): Path = tempFolder.root.toPath() / name private fun tempFile(name: String): Path = tempFolder.root.toPath() / name
/** private fun SSLConfiguration.createTrustStore(rootCert: X509Certificate) {
* All in one wrapper to manufacture a root CA cert and an Intermediate CA cert. val trustStore = loadOrCreateKeyStore(trustStoreFile, trustStorePassword)
* Normally this would be run once and then the outputs would be re-used repeatedly to manufacture the server certs trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCert)
* @param keyStoreFilePath The output KeyStore path to publish the private keys of the CA root and intermediate certs into. trustStore.save(trustStoreFile, trustStorePassword)
* @param storePassword The storage password to protect access to the generated KeyStore and public certificates
* @param keyPassword The password that protects the CA private keys.
* Unlike the SSL libraries that tend to assume the password is the same as the keystore password.
* These CA private keys should be protected more effectively with a distinct password.
* @param trustStoreFilePath The output KeyStore to place the Root CA public certificate, which can be used as an SSL truststore
* @param trustStorePassword The password to protect the truststore
* @return The KeyStore object that was saved to file
*/
private fun createCAKeyStoreAndTrustStore(keyStoreFilePath: Path,
storePassword: String,
keyPassword: String,
trustStoreFilePath: Path,
trustStorePassword: String
): KeyStore {
val rootCAKey = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val baseName = CordaX500Name(organisation = "R3CEV", locality = "London", country = "GB")
val rootCACert = X509Utilities.createSelfSignedCACertificate(baseName.copy(commonName = "Corda Node Root CA"), rootCAKey)
val intermediateCAKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val intermediateCACert = X509Utilities.createCertificate(
CertificateType.INTERMEDIATE_CA,
rootCACert,
rootCAKey,
baseName.copy(commonName = "Corda Node Intermediate CA"),
intermediateCAKeyPair.public)
val keyPass = keyPassword.toCharArray()
val keyStore = loadOrCreateKeyStore(keyStoreFilePath, storePassword)
keyStore.addOrReplaceKey(X509Utilities.CORDA_ROOT_CA, rootCAKey.private, keyPass, arrayOf<Certificate>(rootCACert.cert))
keyStore.addOrReplaceKey(X509Utilities.CORDA_INTERMEDIATE_CA,
intermediateCAKeyPair.private,
keyPass,
Stream.of(intermediateCACert, rootCACert).map { it.cert }.toTypedArray<Certificate>())
keyStore.save(keyStoreFilePath, storePassword)
val trustStore = loadOrCreateKeyStore(trustStoreFilePath, trustStorePassword)
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCACert.cert)
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_INTERMEDIATE_CA, intermediateCACert.cert)
trustStore.save(trustStoreFilePath, trustStorePassword)
return keyStore
} }
@Test @Test
fun `Get correct private key type from Keystore`() { fun `get correct private key type from Keystore`() {
val keyPair = generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256) val keyPair = generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256)
val testName = CordaX500Name(commonName = "Test", organisation = "R3 Ltd", locality = "London", country = "GB") val testName = CordaX500Name(commonName = "Test", organisation = "R3 Ltd", locality = "London", country = "GB")
val selfSignCert = X509Utilities.createSelfSignedCACertificate(testName, keyPair) val selfSignCert = X509Utilities.createSelfSignedCACertificate(testName, keyPair)

View File

@ -44,7 +44,7 @@ task buildCordaJAR(type: FatCapsule, dependsOn: project(':node').compileJava) {
applicationVersion = corda_release_version applicationVersion = corda_release_version
appClassPath = ["jolokia-war-${project.rootProject.ext.jolokia_version}.war"] appClassPath = ["jolokia-war-${project.rootProject.ext.jolokia_version}.war"]
// See experimental/quasar-hook/README.md for how to generate. // See experimental/quasar-hook/README.md for how to generate.
def quasarExcludeExpression = "x(antlr**;bftsmart**;ch**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;io.github**;io.netty**;jdk**;joptsimple**;junit**;kotlin**;net.bytebuddy**;net.i2p**;org.apache**;org.assertj**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;org.hamcrest**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.junit**;org.mockito**;org.objectweb**;org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**)" def quasarExcludeExpression = "x(antlr**;bftsmart**;ch**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;io.github**;io.netty**;jdk**;joptsimple**;junit**;kotlin**;net.bytebuddy**;net.i2p**;org.apache**;org.assertj**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;org.hamcrest**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.junit**;org.mockito**;org.objectweb**;org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**;org.jolokia**)"
javaAgents = ["quasar-core-${quasar_version}-jdk8.jar=${quasarExcludeExpression}"] javaAgents = ["quasar-core-${quasar_version}-jdk8.jar=${quasarExcludeExpression}"]
systemProperties['visualvm.display.name'] = 'CordaEnterprise' systemProperties['visualvm.display.name'] = 'CordaEnterprise'
minJavaVersion = '1.8.0' minJavaVersion = '1.8.0'

View File

@ -14,11 +14,9 @@ import net.corda.testing.IntegrationTestSchemas
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
import net.corda.testing.toDatabaseSchemaName import net.corda.testing.toDatabaseSchemaName
import org.junit.ClassRule import org.junit.ClassRule
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test import org.junit.Test
import java.nio.file.Path import java.nio.file.Path
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class NodeKeystoreCheckTest : IntegrationTest() { class NodeKeystoreCheckTest : IntegrationTest() {
companion object { companion object {
@ -26,16 +24,18 @@ class NodeKeystoreCheckTest : IntegrationTest() {
val databaseSchemas = IntegrationTestSchemas(ALICE_NAME.toDatabaseSchemaName()) val databaseSchemas = IntegrationTestSchemas(ALICE_NAME.toDatabaseSchemaName())
} }
@Test
fun `starting node in non-dev mode with no key store`() {
driver(startNodesInProcess = true) {
assertThatThrownBy {
startNode(customOverrides = mapOf("devMode" to false)).getOrThrow()
}.hasMessageContaining("Identity certificate not found")
}
}
@Test @Test
fun `node should throw exception if cert path doesn't chain to the trust root`() { fun `node should throw exception if cert path doesn't chain to the trust root`() {
driver(startNodesInProcess = true) { driver(startNodesInProcess = true) {
// This will fail because there are no keystore configured.
assertFailsWith(IllegalArgumentException::class) {
startNode(customOverrides = mapOf("devMode" to false)).getOrThrow()
}.apply {
assertTrue(message?.startsWith("Identity certificate not found. ") ?: false)
}
// Create keystores // Create keystores
val keystorePassword = "password" val keystorePassword = "password"
val config = object : SSLConfiguration { val config = object : SSLConfiguration {
@ -46,9 +46,12 @@ class NodeKeystoreCheckTest : IntegrationTest() {
config.configureDevKeyAndTrustStores(ALICE_NAME) config.configureDevKeyAndTrustStores(ALICE_NAME)
// This should pass with correct keystore. // This should pass with correct keystore.
val node = startNode(providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false, val node = startNode(
"keyStorePassword" to keystorePassword, providedName = ALICE_NAME,
"trustStorePassword" to keystorePassword)).get() customOverrides = mapOf("devMode" to false,
"keyStorePassword" to keystorePassword,
"trustStorePassword" to keystorePassword)
).getOrThrow()
node.stop() node.stop()
// Fiddle with node keystore. // Fiddle with node keystore.
@ -62,11 +65,9 @@ class NodeKeystoreCheckTest : IntegrationTest() {
keystore.setKeyEntry(X509Utilities.CORDA_CLIENT_CA, nodeCA.keyPair.private, config.keyStorePassword.toCharArray(), arrayOf(badNodeCACert.cert, badRoot.cert)) keystore.setKeyEntry(X509Utilities.CORDA_CLIENT_CA, nodeCA.keyPair.private, config.keyStorePassword.toCharArray(), arrayOf(badNodeCACert.cert, badRoot.cert))
keystore.save(config.nodeKeystore, config.keyStorePassword) keystore.save(config.nodeKeystore, config.keyStorePassword)
assertFailsWith(IllegalArgumentException::class) { assertThatThrownBy {
startNode(providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false)).getOrThrow() startNode(providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false)).getOrThrow()
}.apply { }.hasMessage("Client CA certificate must chain to the trusted root.")
assertEquals("Client CA certificate must chain to the trusted root.", message)
}
} }
} }
} }

View File

@ -23,7 +23,7 @@ import net.corda.node.services.config.BFTSMaRtConfiguration
import net.corda.node.services.config.NotaryConfig import net.corda.node.services.config.NotaryConfig
import net.corda.node.services.transactions.minClusterSize import net.corda.node.services.transactions.minClusterSize
import net.corda.node.services.transactions.minCorrectReplicas import net.corda.node.services.transactions.minCorrectReplicas
import net.corda.nodeapi.internal.IdentityGenerator import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.nodeapi.internal.network.NotaryInfo import net.corda.nodeapi.internal.network.NotaryInfo
import net.corda.testing.IntegrationTest import net.corda.testing.IntegrationTest
@ -69,7 +69,7 @@ class BFTNotaryServiceTests : IntegrationTest() {
(Paths.get("config") / "currentView").deleteIfExists() // XXX: Make config object warn if this exists? (Paths.get("config") / "currentView").deleteIfExists() // XXX: Make config object warn if this exists?
val replicaIds = (0 until clusterSize) val replicaIds = (0 until clusterSize)
notary = IdentityGenerator.generateDistributedNotaryIdentity( notary = DevIdentityGenerator.generateDistributedNotaryIdentity(
replicaIds.map { mockNet.baseDirectory(mockNet.nextNodeId + it) }, replicaIds.map { mockNet.baseDirectory(mockNet.nextNodeId + it) },
CordaX500Name("BFT", "Zurich", "CH")) CordaX500Name("BFT", "Zurich", "CH"))

View File

@ -14,7 +14,7 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.StartedNode import net.corda.node.internal.StartedNode
import net.corda.node.services.config.NotaryConfig import net.corda.node.services.config.NotaryConfig
import net.corda.nodeapi.internal.IdentityGenerator import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.nodeapi.internal.network.NotaryInfo import net.corda.nodeapi.internal.network.NotaryInfo
import net.corda.testing.* import net.corda.testing.*
@ -46,7 +46,7 @@ class MySQLNotaryServiceTests : IntegrationTest() {
@Before @Before
fun before() { fun before() {
mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts")) mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
notaryParty = IdentityGenerator.generateNodeIdentity(mockNet.baseDirectory(mockNet.nextNodeId), notaryName) notaryParty = DevIdentityGenerator.installKeyStoreWithNodeIdentity(mockNet.baseDirectory(mockNet.nextNodeId), notaryName)
val networkParameters = NetworkParametersCopier(testNetworkParameters(listOf(NotaryInfo(notaryParty, false)))) val networkParameters = NetworkParametersCopier(testNetworkParameters(listOf(NotaryInfo(notaryParty, false))))
val notaryNodeUnstarted = createNotaryNode() val notaryNodeUnstarted = createNotaryNode()
val nodeUnstarted = mockNet.createUnstartedNode() val nodeUnstarted = mockNet.createUnstartedNode()

View File

@ -1,6 +1,11 @@
package net.corda.node.services.network package net.corda.node.services.network
import net.corda.cordform.CordformNode
import net.corda.core.crypto.SignedData import net.corda.core.crypto.SignedData
import net.corda.core.crypto.random63BitValue
import net.corda.core.internal.concurrent.transpose
import net.corda.core.internal.div
import net.corda.core.internal.exists
import net.corda.core.internal.list import net.corda.core.internal.list
import net.corda.core.internal.readAll import net.corda.core.internal.readAll
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
@ -10,6 +15,10 @@ import net.corda.core.utilities.seconds
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME
import net.corda.nodeapi.internal.network.NetworkParameters import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.testing.* import net.corda.testing.*
import net.corda.testing.ALICE_NAME
import net.corda.testing.BOB_NAME
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.common.internal.testNetworkParameters
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.node.internal.CompatibilityZoneParams import net.corda.testing.node.internal.CompatibilityZoneParams
@ -18,6 +27,8 @@ import net.corda.testing.node.internal.network.NetworkMapServer
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.* import org.junit.*
import java.net.URL import java.net.URL
import java.time.Instant
import kotlin.streams.toList
import kotlin.test.assertEquals import kotlin.test.assertEquals
class NetworkMapTest : IntegrationTest() { class NetworkMapTest : IntegrationTest() {
@ -29,6 +40,7 @@ class NetworkMapTest : IntegrationTest() {
@Rule @Rule
@JvmField @JvmField
val testSerialization = SerializationEnvironmentRule(true) val testSerialization = SerializationEnvironmentRule(true)
private val cacheTimeout = 1.seconds private val cacheTimeout = 1.seconds
private val portAllocation = PortAllocation.Incremental(10000) private val portAllocation = PortAllocation.Incremental(10000)
@ -39,7 +51,9 @@ class NetworkMapTest : IntegrationTest() {
fun start() { fun start() {
networkMapServer = NetworkMapServer(cacheTimeout, portAllocation.nextHostAndPort()) networkMapServer = NetworkMapServer(cacheTimeout, portAllocation.nextHostAndPort())
val address = networkMapServer.start() val address = networkMapServer.start()
compatibilityZone = CompatibilityZoneParams(URL("http://$address")) compatibilityZone = CompatibilityZoneParams(URL("http://$address"), publishNotaries = {
networkMapServer.networkParameters = testNetworkParameters(it, modifiedTime = Instant.ofEpochMilli(random63BitValue()))
})
} }
@After @After
@ -49,25 +63,35 @@ class NetworkMapTest : IntegrationTest() {
@Test @Test
fun `node correctly downloads and saves network parameters file on startup`() { fun `node correctly downloads and saves network parameters file on startup`() {
internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone, initialiseSerialization = false) { internalDriver(
portAllocation = portAllocation,
compatibilityZone = compatibilityZone,
initialiseSerialization = false,
notarySpecs = emptyList()
) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow() val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val networkParameters = alice.configuration.baseDirectory val networkParameters = (alice.configuration.baseDirectory / NETWORK_PARAMS_FILE_NAME)
.list { paths -> paths.filter { it.fileName.toString() == NETWORK_PARAMS_FILE_NAME }.findFirst().get() }
.readAll() .readAll()
.deserialize<SignedData<NetworkParameters>>() .deserialize<SignedData<NetworkParameters>>()
.verified() .verified()
assertEquals(NetworkMapServer.stubNetworkParameter, networkParameters) // We use a random modified time above to make the network parameters unqiue so that we're sure they came
// from the server
assertEquals(networkMapServer.networkParameters, networkParameters)
} }
} }
@Test @Test
fun `nodes can see each other using the http network map`() { fun `nodes can see each other using the http network map`() {
internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone, initialiseSerialization = false) { internalDriver(
val alice = startNode(providedName = ALICE_NAME) portAllocation = portAllocation,
val bob = startNode(providedName = BOB_NAME) compatibilityZone = compatibilityZone,
val notaryNode = defaultNotaryNode.get() initialiseSerialization = false
val aliceNode = alice.get() ) {
val bobNode = bob.get() val (aliceNode, bobNode, notaryNode) = listOf(
startNode(providedName = ALICE_NAME),
startNode(providedName = BOB_NAME),
defaultNotaryNode
).transpose().getOrThrow()
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)
@ -77,15 +101,20 @@ class NetworkMapTest : IntegrationTest() {
@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`() {
internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone, initialiseSerialization = false) { internalDriver(
val alice = startNode(providedName = ALICE_NAME) portAllocation = portAllocation,
val notaryNode = defaultNotaryNode.get() compatibilityZone = compatibilityZone,
val aliceNode = alice.get() initialiseSerialization = false
) {
val (aliceNode, notaryNode) = listOf(
startNode(providedName = ALICE_NAME),
defaultNotaryNode
).transpose().getOrThrow()
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 bobNode = bob.get() val bobNode = startNode(providedName = BOB_NAME).getOrThrow()
// Wait for network map client to poll for the next update. // Wait for network map client to poll for the next update.
Thread.sleep(cacheTimeout.toMillis() * 2) Thread.sleep(cacheTimeout.toMillis() * 2)
@ -98,12 +127,16 @@ class NetworkMapTest : IntegrationTest() {
@Test @Test
fun `nodes process network map remove updates correctly`() { fun `nodes process network map remove updates correctly`() {
internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone, initialiseSerialization = false) { internalDriver(
val alice = startNode(providedName = ALICE_NAME) portAllocation = portAllocation,
val bob = startNode(providedName = BOB_NAME) compatibilityZone = compatibilityZone,
val notaryNode = defaultNotaryNode.get() initialiseSerialization = false
val aliceNode = alice.get() ) {
val bobNode = bob.get() val (aliceNode, bobNode, notaryNode) = listOf(
startNode(providedName = ALICE_NAME),
startNode(providedName = BOB_NAME),
defaultNotaryNode
).transpose().getOrThrow()
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)
@ -119,5 +152,12 @@ class NetworkMapTest : IntegrationTest() {
} }
} }
private fun NodeHandle.onlySees(vararg nodes: NodeInfo) = assertThat(rpc.networkMapSnapshot()).containsOnly(*nodes) private fun NodeHandle.onlySees(vararg nodes: NodeInfo) {
// Make sure the nodes aren't getting the node infos from their additional directories
val nodeInfosDir = configuration.baseDirectory / CordformNode.NODE_INFO_DIRECTORY
if (nodeInfosDir.exists()) {
assertThat(nodeInfosDir.list { it.toList() }).isEmpty()
}
assertThat(rpc.networkMapSnapshot()).containsOnly(*nodes)
}
} }

View File

@ -3,10 +3,15 @@ package net.corda.node.utilities.registration
import net.corda.core.crypto.Crypto 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.concurrent.transpose
import net.corda.core.internal.toX509CertHolder import net.corda.core.internal.toX509CertHolder
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.minutes import net.corda.core.utilities.minutes
import net.corda.finance.DOLLARS
import net.corda.finance.flows.CashIssueAndPaymentFlow
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
import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509CertificateFactory
@ -14,10 +19,10 @@ 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.IntegrationTest import net.corda.testing.*
import net.corda.testing.IntegrationTestSchemas import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.driver.PortAllocation import net.corda.testing.driver.PortAllocation
import net.corda.testing.node.NotarySpec
import net.corda.testing.node.internal.CompatibilityZoneParams import net.corda.testing.node.internal.CompatibilityZoneParams
import net.corda.testing.node.internal.internalDriver import net.corda.testing.node.internal.internalDriver
import net.corda.testing.node.internal.network.NetworkMapServer import net.corda.testing.node.internal.network.NetworkMapServer
@ -31,6 +36,7 @@ import java.io.InputStream
import java.net.URL 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.CertPathValidatorException
import java.security.cert.Certificate import java.security.cert.Certificate
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream import java.util.zip.ZipOutputStream
@ -42,20 +48,25 @@ class NodeRegistrationTest : IntegrationTest() {
companion object { companion object {
@ClassRule @JvmField @ClassRule @JvmField
val databaseSchemas = IntegrationTestSchemas("Alice") val databaseSchemas = IntegrationTestSchemas("Alice")
private val notaryName = CordaX500Name("NotaryService", "Zurich", "CH")
private val aliceName = CordaX500Name("Alice", "London", "GB")
private val genevieveName = CordaX500Name("Genevieve", "London", "GB")
} }
@Rule @Rule
@JvmField @JvmField
val testSerialization = SerializationEnvironmentRule(true) val testSerialization = SerializationEnvironmentRule(true)
private val portAllocation = PortAllocation.Incremental(13000) private val portAllocation = PortAllocation.Incremental(13000)
private val rootCertAndKeyPair = createSelfKeyAndSelfSignedCertificate() private val registrationHandler = RegistrationHandler(ROOT_CA)
private val registrationHandler = RegistrationHandler(rootCertAndKeyPair)
private lateinit var server: NetworkMapServer private lateinit var server: NetworkMapServer
private lateinit var serverHostAndPort: NetworkHostAndPort private lateinit var serverHostAndPort: NetworkHostAndPort
@Before @Before
fun startServer() { fun startServer() {
server = NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), rootCertAndKeyPair, registrationHandler) server = NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), ROOT_CA, "localhost", registrationHandler)
serverHostAndPort = server.start() serverHostAndPort = server.start()
} }
@ -64,50 +75,65 @@ class NodeRegistrationTest : IntegrationTest() {
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`() {
val compatibilityZone = CompatibilityZoneParams(URL("http://$serverHostAndPort"), rootCert = rootCertAndKeyPair.certificate.cert) val compatibilityZone = CompatibilityZoneParams(
URL("http://$serverHostAndPort"),
publishNotaries = { server.networkParameters = testNetworkParameters(it) },
rootCert = ROOT_CA.certificate.cert)
internalDriver( internalDriver(
portAllocation = portAllocation, portAllocation = portAllocation,
notarySpecs = emptyList(),
compatibilityZone = compatibilityZone, compatibilityZone = compatibilityZone,
initialiseSerialization = false initialiseSerialization = false,
notarySpecs = listOf(NotarySpec(notaryName)),
extraCordappPackagesToScan = listOf("net.corda.finance")
) { ) {
startNode(providedName = CordaX500Name("Alice", "London", "GB")).getOrThrow() val nodes = listOf(
assertThat(registrationHandler.idsPolled).contains("Alice") startNode(providedName = aliceName),
startNode(providedName = genevieveName),
defaultNotaryNode
).transpose().getOrThrow()
val (alice, genevieve) = nodes
assertThat(registrationHandler.idsPolled).containsOnly(
aliceName.organisation,
genevieveName.organisation,
notaryName.organisation)
// Check the nodes can communicate among themselves (and the notary).
val anonymous = false
genevieve.rpc.startFlow(
::CashIssueAndPaymentFlow,
1000.DOLLARS,
OpaqueBytes.of(12),
alice.nodeInfo.singleIdentity(),
anonymous,
defaultNotaryIdentity
).returnValue.getOrThrow()
} }
} }
@Test @Test
fun `node registration wrong root cert`() { fun `node registration wrong root cert`() {
val someCert = createSelfKeyAndSelfSignedCertificate().certificate.cert val someRootCert = X509Utilities.createSelfSignedCACertificate(
val compatibilityZone = CompatibilityZoneParams(URL("http://$serverHostAndPort"), rootCert = someCert) CordaX500Name("Integration Test Corda Node Root CA", "R3 Ltd", "London", "GB"),
Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))
val compatibilityZone = CompatibilityZoneParams(
URL("http://$serverHostAndPort"),
publishNotaries = { server.networkParameters = testNetworkParameters(it) },
rootCert = someRootCert.cert)
internalDriver( internalDriver(
portAllocation = portAllocation, portAllocation = portAllocation,
notarySpecs = emptyList(),
compatibilityZone = compatibilityZone, compatibilityZone = compatibilityZone,
// Changing the content of the truststore makes the node fail in a number of ways if started out process. initialiseSerialization = false,
startNodesInProcess = true notarySpecs = listOf(NotarySpec(notaryName)),
startNodesInProcess = true // We need to run the nodes in the same process so that we can capture the correct exception
) { ) {
assertThatThrownBy { assertThatThrownBy {
startNode(providedName = CordaX500Name("Alice", "London", "GB")).getOrThrow() defaultNotaryNode.getOrThrow()
}.isInstanceOf(WrongRootCertException::class.java) }.isInstanceOf(CertPathValidatorException::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)
}
} }
@Path("certificate") @Path("certificate")
@ -124,6 +150,7 @@ class RegistrationHandler(private val rootCertAndKeyPair: CertificateAndKeyPair)
certificationRequest, certificationRequest,
rootCertAndKeyPair.keyPair, rootCertAndKeyPair.keyPair,
arrayOf(rootCertAndKeyPair.certificate.cert)) arrayOf(rootCertAndKeyPair.certificate.cert))
require(!name.organisation.contains("\\s".toRegex())) { "Whitespace in the organisation name not supported" }
certPaths[name.organisation] = certPath certPaths[name.organisation] = certPath
return Response.ok(name.organisation).build() return Response.ok(name.organisation).build()
} }
@ -154,13 +181,14 @@ class RegistrationHandler(private val rootCertAndKeyPair: CertificateAndKeyPair)
caCertPath: Array<Certificate>): Pair<CertPath, CordaX500Name> { caCertPath: Array<Certificate>): Pair<CertPath, CordaX500Name> {
val request = JcaPKCS10CertificationRequest(certificationRequest) val request = JcaPKCS10CertificationRequest(certificationRequest)
val name = CordaX500Name.parse(request.subject.toString()) val name = CordaX500Name.parse(request.subject.toString())
val x509CertificateHolder = X509Utilities.createCertificate(CertificateType.NODE_CA, val nodeCaCert = X509Utilities.createCertificate(
CertificateType.NODE_CA,
caCertPath.first().toX509CertHolder(), caCertPath.first().toX509CertHolder(),
caKeyPair, caKeyPair,
name, name,
request.publicKey, request.publicKey,
nameConstraints = null) nameConstraints = null)
val certPath = X509CertificateFactory().generateCertPath(x509CertificateHolder.cert, *caCertPath) val certPath = X509CertificateFactory().generateCertPath(nodeCaCert.cert, *caCertPath)
return Pair(certPath, name) return Pair(certPath, name)
} }
} }

View File

@ -26,6 +26,7 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREF
import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.config.User
import net.corda.testing.* import net.corda.testing.*
import net.corda.testing.internal.configureTestSSL
import net.corda.testing.node.internal.NodeBasedTest import net.corda.testing.node.internal.NodeBasedTest
import net.corda.testing.node.startFlow import net.corda.testing.node.startFlow
import org.apache.activemq.artemis.api.core.ActiveMQNonExistentQueueException import org.apache.activemq.artemis.api.core.ActiveMQNonExistentQueueException
@ -46,6 +47,7 @@ abstract class MQSecurityTest : NodeBasedTest() {
@ClassRule @JvmField @ClassRule @JvmField
val databaseSchemas = IntegrationTestSchemas(ALICE_NAME.toDatabaseSchemaName(), BOB_NAME.toDatabaseSchemaName()) val databaseSchemas = IntegrationTestSchemas(ALICE_NAME.toDatabaseSchemaName(), BOB_NAME.toDatabaseSchemaName())
} }
val rpcUser = User("user1", "pass", permissions = emptySet()) val rpcUser = User("user1", "pass", permissions = emptySet())
lateinit var alice: StartedNode<Node> lateinit var alice: StartedNode<Node>
lateinit var attacker: SimpleMQClient lateinit var attacker: SimpleMQClient

View File

@ -6,7 +6,7 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.nodeapi.ArtemisTcpTransport import net.corda.nodeapi.ArtemisTcpTransport
import net.corda.nodeapi.ConnectionDirection import net.corda.nodeapi.ConnectionDirection
import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.testing.configureTestSSL import net.corda.testing.internal.configureTestSSL
import org.apache.activemq.artemis.api.core.client.* import org.apache.activemq.artemis.api.core.client.*
/** /**

View File

@ -60,7 +60,7 @@ import net.corda.node.services.vault.NodeVaultService
import net.corda.node.services.vault.VaultSoftLockManager import net.corda.node.services.vault.VaultSoftLockManager
import net.corda.node.shell.InteractiveShell import net.corda.node.shell.InteractiveShell
import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.AffinityExecutor
import net.corda.nodeapi.internal.IdentityGenerator import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.crypto.KeyStoreWrapper import net.corda.nodeapi.internal.crypto.KeyStoreWrapper
import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509CertificateFactory
@ -108,7 +108,7 @@ import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair
// In theory the NodeInfo for the node should be passed in, instead, however currently this is constructed by the // In theory the NodeInfo for the node should be passed in, instead, however currently this is constructed by the
// AbstractNode. It should be possible to generate the NodeInfo outside of AbstractNode, so it can be passed in. // AbstractNode. It should be possible to generate the NodeInfo outside of AbstractNode, so it can be passed in.
abstract class AbstractNode(val configuration: NodeConfiguration, abstract class AbstractNode(val configuration: NodeConfiguration,
val platformClock: Clock, val platformClock: CordaClock,
protected val versionInfo: VersionInfo, protected val versionInfo: VersionInfo,
protected val cordappLoader: CordappLoader, protected val cordappLoader: CordappLoader,
private val busyNodeLatch: ReusableLatch = ReusableLatch()) : SingletonSerializeAsToken() { private val busyNodeLatch: ReusableLatch = ReusableLatch()) : SingletonSerializeAsToken() {
@ -184,13 +184,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
return SignedNodeInfo(serialised, listOf(signature)) return SignedNodeInfo(serialised, listOf(signature))
} }
open fun generateNodeInfo() { open fun generateAndSaveNodeInfo(): NodeInfo {
check(started == null) { "Node has already been started" } check(started == null) { "Node has already been started" }
log.info("Generating nodeInfo ...") log.info("Generating nodeInfo ...")
initCertificate() initCertificate()
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas) val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null) val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
initialiseDatabasePersistence(schemaService, makeIdentityService(identity.certificate)) { database -> return initialiseDatabasePersistence(schemaService, makeIdentityService(identity.certificate)) { database ->
// TODO The fact that we need to specify an empty list of notaries just to generate our node info looks like // TODO The fact that we need to specify an empty list of notaries just to generate our node info looks like
// a code smell. // a code smell.
val persistentNetworkMapCache = PersistentNetworkMapCache(database, notaries = emptyList()) val persistentNetworkMapCache = PersistentNetworkMapCache(database, notaries = emptyList())
@ -200,6 +200,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
privateKey.sign(serialised.bytes) privateKey.sign(serialised.bytes)
} }
NodeInfoWatcher.saveToFile(configuration.baseDirectory, signedNodeInfo) NodeInfoWatcher.saveToFile(configuration.baseDirectory, signedNodeInfo)
info
} }
} }
@ -239,14 +240,16 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
} }
val notaryService = makeNotaryService(nodeServices, database) val notaryService = makeNotaryService(nodeServices, database)
val smm = makeStateMachineManager(database) val smm = makeStateMachineManager(database)
val flowStarter = FlowStarterImpl(serverThread, smm) val flowLogicRefFactory = FlowLogicRefFactoryImpl(cordappLoader.appClassLoader)
val flowStarter = FlowStarterImpl(serverThread, smm, flowLogicRefFactory)
val schedulerService = NodeSchedulerService( val schedulerService = NodeSchedulerService(
platformClock, platformClock,
database, database,
flowStarter, flowStarter,
transactionStorage, transactionStorage,
unfinishedSchedules = busyNodeLatch, unfinishedSchedules = busyNodeLatch,
serverThread = serverThread) serverThread = serverThread,
flowLogicRefFactory = flowLogicRefFactory)
if (serverThread is ExecutorService) { if (serverThread is ExecutorService) {
runOnStop += { runOnStop += {
// We wait here, even though any in-flight messages should have been drained away because the // We wait here, even though any in-flight messages should have been drained away because the
@ -255,7 +258,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
MoreExecutors.shutdownAndAwaitTermination(serverThread as ExecutorService, 50, SECONDS) MoreExecutors.shutdownAndAwaitTermination(serverThread as ExecutorService, 50, SECONDS)
} }
} }
makeVaultObservers(schedulerService, database.hibernateConfig, smm, schemaService) makeVaultObservers(schedulerService, database.hibernateConfig, smm, schemaService, flowLogicRefFactory)
val rpcOps = makeRPCOps(flowStarter, database, smm) val rpcOps = makeRPCOps(flowStarter, database, smm)
startMessagingService(rpcOps) startMessagingService(rpcOps)
installCoreFlows() installCoreFlows()
@ -263,7 +266,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
tokenizableServices = nodeServices + cordaServices + schedulerService tokenizableServices = nodeServices + cordaServices + schedulerService
registerCordappFlows(smm) registerCordappFlows(smm)
_services.rpcFlows += cordappLoader.cordapps.flatMap { it.rpcFlows } _services.rpcFlows += cordappLoader.cordapps.flatMap { it.rpcFlows }
FlowLogicRefFactoryImpl.classloader = cordappLoader.appClassLoader
startShell(rpcOps) startShell(rpcOps)
Pair(StartedNodeImpl(this, _services, info, checkpointStorage, smm, attachments, network, database, rpcOps, flowStarter, notaryService), schedulerService) Pair(StartedNodeImpl(this, _services, info, checkpointStorage, smm, attachments, network, database, rpcOps, flowStarter, notaryService), schedulerService)
} }
@ -581,10 +583,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
} }
protected open fun makeTransactionStorage(database: CordaPersistence): WritableTransactionStorage = DBTransactionStorage() protected open fun makeTransactionStorage(database: CordaPersistence): WritableTransactionStorage = DBTransactionStorage()
private fun makeVaultObservers(schedulerService: SchedulerService, hibernateConfig: HibernateConfiguration, smm: StateMachineManager, schemaService: SchemaService, flowLogicRefFactory: FlowLogicRefFactory) {
private fun makeVaultObservers(schedulerService: SchedulerService, hibernateConfig: HibernateConfiguration, smm: StateMachineManager, schemaService: SchemaService) {
VaultSoftLockManager.install(services.vaultService, smm) VaultSoftLockManager.install(services.vaultService, smm)
ScheduledActivityObserver.install(services.vaultService, schedulerService) ScheduledActivityObserver.install(services.vaultService, schedulerService, flowLogicRefFactory)
HibernateObserver.install(services.vaultService.rawUpdates, hibernateConfig, schemaService) HibernateObserver.install(services.vaultService.rawUpdates, hibernateConfig, schemaService)
} }
@ -724,7 +725,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
val trustStore = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword) val trustStore = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword)
val caKeyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword) val caKeyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword)
val trustRoot = trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA) val trustRoot = trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA)
val clientCa = caKeyStore.certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA) val clientCa = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
val caCertificates = arrayOf(identityCert, clientCa.certificate.cert) val caCertificates = arrayOf(identityCert, clientCa.certificate.cert)
return PersistentIdentityService(trustRoot, *caCertificates) return PersistentIdentityService(trustRoot, *caCertificates)
} }
@ -754,10 +755,10 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
val (id, singleName) = if (notaryConfig == null || !notaryConfig.isClusterConfig) { val (id, singleName) = if (notaryConfig == null || !notaryConfig.isClusterConfig) {
// Node's main identity or if it's a single node notary // Node's main identity or if it's a single node notary
Pair(IdentityGenerator.NODE_IDENTITY_ALIAS_PREFIX, configuration.myLegalName) Pair(DevIdentityGenerator.NODE_IDENTITY_ALIAS_PREFIX, configuration.myLegalName)
} else { } else {
// The node is part of a distributed notary whose identity must already be generated beforehand. // The node is part of a distributed notary whose identity must already be generated beforehand.
Pair(IdentityGenerator.DISTRIBUTED_NOTARY_ALIAS_PREFIX, null) Pair(DevIdentityGenerator.DISTRIBUTED_NOTARY_ALIAS_PREFIX, null)
} }
// TODO: Integrate with Key management service? // TODO: Integrate with Key management service?
val privateKeyAlias = "$id-private-key" val privateKeyAlias = "$id-private-key"
@ -767,10 +768,10 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
"Unable to find in the key store the identity of the distributed notary ($id) the node is part of") "Unable to find in the key store the identity of the distributed notary ($id) the node is part of")
// TODO: Remove use of [IdentityGenerator.generateToDisk]. // TODO: Remove use of [IdentityGenerator.generateToDisk].
log.info("$privateKeyAlias not found in key store ${configuration.nodeKeystore}, generating fresh key!") log.info("$privateKeyAlias not found in key store ${configuration.nodeKeystore}, generating fresh key!")
keyStore.signAndSaveNewKeyPair(singleName, privateKeyAlias, generateKeyPair()) keyStore.storeLegalIdentity(singleName, privateKeyAlias, generateKeyPair())
} }
val (x509Cert, keyPair) = keyStore.certificateAndKeyPair(privateKeyAlias) val (x509Cert, keyPair) = keyStore.getCertificateAndKeyPair(privateKeyAlias)
// TODO: Use configuration to indicate composite key should be used instead of public key for the identity. // TODO: Use configuration to indicate composite key should be used instead of public key for the identity.
val compositeKeyAlias = "$id-composite-key" val compositeKeyAlias = "$id-composite-key"
@ -848,10 +849,19 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
} }
} }
internal class FlowStarterImpl(private val serverThread: AffinityExecutor, private val smm: StateMachineManager) : FlowStarter { internal class FlowStarterImpl(private val serverThread: AffinityExecutor, private val smm: StateMachineManager, private val flowLogicRefFactory: FlowLogicRefFactory) : FlowStarter {
override fun <T> startFlow(logic: FlowLogic<T>, context: InvocationContext): CordaFuture<FlowStateMachine<T>> { override fun <T> startFlow(logic: FlowLogic<T>, context: InvocationContext): CordaFuture<FlowStateMachine<T>> {
return serverThread.fetchFrom { smm.startFlow(logic, context) } return serverThread.fetchFrom { smm.startFlow(logic, context) }
} }
override fun <T> invokeFlowAsync(
logicType: Class<out FlowLogic<T>>,
context: InvocationContext,
vararg args: Any?): CordaFuture<FlowStateMachine<T>> {
val logicRef = flowLogicRefFactory.createForRPC(logicType, *args)
val logic: FlowLogic<T> = uncheckedCast(flowLogicRefFactory.toFlowLogic(logicRef))
return startFlow(logic, context)
}
} }
class ConfigurationException(message: String) : CordaException(message) class ConfigurationException(message: String) : CordaException(message)

View File

@ -22,10 +22,15 @@ abstract class CordaClock : Clock(), SerializeAsToken {
override fun getZone(): ZoneId = delegateClock.zone override fun getZone(): ZoneId = delegateClock.zone
@Deprecated("Do not use this. Instead seek to use ZonedDateTime methods.", level = DeprecationLevel.ERROR) @Deprecated("Do not use this. Instead seek to use ZonedDateTime methods.", level = DeprecationLevel.ERROR)
override fun withZone(zone: ZoneId) = throw UnsupportedOperationException("Tokenized clock does not support withZone()") override fun withZone(zone: ZoneId) = throw UnsupportedOperationException("Tokenized clock does not support withZone()")
/** This is an observer on the mutation count of this [Clock], which reflects the occurrence of mutations. */
abstract val mutations: Observable<Long>
} }
@ThreadSafe @ThreadSafe
class SimpleClock(override val delegateClock: Clock) : CordaClock() class SimpleClock(override val delegateClock: Clock) : CordaClock() {
override val mutations: Observable<Long> = Observable.never()
}
/** /**
* An abstract class with helper methods for a type of Clock that might have it's concept of "now" adjusted externally. * An abstract class with helper methods for a type of Clock that might have it's concept of "now" adjusted externally.
@ -38,8 +43,7 @@ abstract class MutableClock(private var _delegateClock: Clock) : CordaClock() {
_delegateClock = clock _delegateClock = clock
} }
private val _version = AtomicLong(0L) private val _version = AtomicLong(0L)
/** This is an observer on the mutation count of this [Clock], which reflects the occurence of mutations. */ override val mutations: Observable<Long> by lazy {
val mutations: Observable<Long> by lazy {
Observable.create { subscriber: Subscriber<in Long> -> Observable.create { subscriber: Subscriber<in Long> ->
if (!subscriber.isUnsubscribed) { if (!subscriber.isUnsubscribed) {
mutationObservers.add(subscriber) mutationObservers.add(subscriber)

View File

@ -2,7 +2,6 @@ package net.corda.node.internal
import com.codahale.metrics.JmxReporter import com.codahale.metrics.JmxReporter
import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.CordaFuture
import net.corda.core.context.AuthServiceId
import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.concurrent.thenMatch import net.corda.core.internal.concurrent.thenMatch
import net.corda.core.internal.uncheckedCast import net.corda.core.internal.uncheckedCast
@ -20,7 +19,9 @@ import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.security.RPCSecurityManagerImpl import net.corda.node.internal.security.RPCSecurityManagerImpl
import net.corda.node.serialization.KryoServerSerializationScheme import net.corda.node.serialization.KryoServerSerializationScheme
import net.corda.node.services.api.SchemaService import net.corda.node.services.api.SchemaService
import net.corda.node.services.config.* import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.SecurityConfiguration
import net.corda.node.services.config.VerifierType
import net.corda.node.services.messaging.* import net.corda.node.services.messaging.*
import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.services.transactions.InMemoryTransactionVerifierService
import net.corda.node.utilities.AddressUtils import net.corda.node.utilities.AddressUtils
@ -33,6 +34,7 @@ import net.corda.nodeapi.internal.serialization.*
import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import rx.Scheduler
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import java.time.Clock import java.time.Clock
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
@ -67,7 +69,7 @@ open class Node(configuration: NodeConfiguration,
exitProcess(1) exitProcess(1)
} }
private fun createClock(configuration: NodeConfiguration): Clock { private fun createClock(configuration: NodeConfiguration): CordaClock {
return (if (configuration.useTestClock) ::DemoClock else ::SimpleClock)(Clock.systemUTC()) return (if (configuration.useTestClock) ::DemoClock else ::SimpleClock)(Clock.systemUTC())
} }
@ -269,15 +271,13 @@ open class Node(configuration: NodeConfiguration,
private val _startupComplete = openFuture<Unit>() private val _startupComplete = openFuture<Unit>()
val startupComplete: CordaFuture<Unit> get() = _startupComplete val startupComplete: CordaFuture<Unit> get() = _startupComplete
override fun generateNodeInfo() { override fun generateAndSaveNodeInfo(): NodeInfo {
initialiseSerialization() initialiseSerialization()
super.generateNodeInfo() return super.generateAndSaveNodeInfo()
} }
override fun start(): StartedNode<Node> { override fun start(): StartedNode<Node> {
if (initialiseSerialization) { initialiseSerialization()
initialiseSerialization()
}
val started: StartedNode<Node> = uncheckedCast(super.start()) val started: StartedNode<Node> = uncheckedCast(super.start())
nodeReadyFuture.thenMatch({ nodeReadyFuture.thenMatch({
serverThread.execute { serverThread.execute {
@ -310,8 +310,10 @@ open class Node(configuration: NodeConfiguration,
return started return started
} }
override fun getRxIoScheduler() = Schedulers.io()!! override fun getRxIoScheduler(): Scheduler = Schedulers.io()
private fun initialiseSerialization() { private fun initialiseSerialization() {
if (!initialiseSerialization) return
val classloader = cordappLoader.appClassLoader val classloader = cordappLoader.appClassLoader
nodeSerializationEnv = SerializationEnvironmentImpl( nodeSerializationEnv = SerializationEnvironmentImpl(
SerializationFactoryImpl().apply { SerializationFactoryImpl().apply {

View File

@ -1,7 +1,6 @@
package net.corda.node.internal package net.corda.node.internal
import com.jcabi.manifests.Manifests import com.jcabi.manifests.Manifests
import com.typesafe.config.ConfigException
import joptsimple.OptionException import joptsimple.OptionException
import net.corda.core.internal.* import net.corda.core.internal.*
import net.corda.core.internal.concurrent.thenMatch import net.corda.core.internal.concurrent.thenMatch
@ -126,7 +125,7 @@ open class NodeStartup(val args: Array<String>) {
val node = createNode(conf, versionInfo) val node = createNode(conf, versionInfo)
if (cmdlineOptions.justGenerateNodeInfo) { if (cmdlineOptions.justGenerateNodeInfo) {
// Perform the minimum required start-up logic to be able to write a nodeInfo to disk // Perform the minimum required start-up logic to be able to write a nodeInfo to disk
node.generateNodeInfo() node.generateAndSaveNodeInfo()
return return
} }
if (cmdlineOptions.justRunDbMigration) { if (cmdlineOptions.justRunDbMigration) {

View File

@ -6,7 +6,6 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StateMachineRunId import net.corda.core.flows.StateMachineRunId
import net.corda.core.internal.FlowStateMachine import net.corda.core.internal.FlowStateMachine
import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.DataFeed import net.corda.core.messaging.DataFeed
import net.corda.core.messaging.StateMachineTransactionMapping import net.corda.core.messaging.StateMachineTransactionMapping
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
@ -21,7 +20,6 @@ import net.corda.node.internal.InitiatedFlowFactory
import net.corda.node.internal.cordapp.CordappProviderInternal import net.corda.node.internal.cordapp.CordappProviderInternal
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.messaging.MessagingService import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.services.statemachine.FlowStateMachineImpl
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
@ -137,11 +135,7 @@ interface FlowStarter {
fun <T> invokeFlowAsync( fun <T> invokeFlowAsync(
logicType: Class<out FlowLogic<T>>, logicType: Class<out FlowLogic<T>>,
context: InvocationContext, context: InvocationContext,
vararg args: Any?): CordaFuture<FlowStateMachine<T>> { vararg args: Any?): CordaFuture<FlowStateMachine<T>>
val logicRef = FlowLogicRefFactoryImpl.createForRPC(logicType, *args)
val logic: FlowLogic<T> = uncheckedCast(FlowLogicRefFactoryImpl.toFlowLogic(logicRef))
return startFlow(logic, context)
}
} }
interface StartedNodeServices : ServiceHubInternal, FlowStarter interface StartedNodeServices : ServiceHubInternal, FlowStarter

View File

@ -3,17 +3,21 @@ package net.corda.node.services.config
import com.typesafe.config.* import com.typesafe.config.*
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignatureScheme import net.corda.core.crypto.SignatureScheme
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigRenderOptions
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.* import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.internal.exists
import net.corda.core.internal.toX509CertHolder
import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.config.toProperties import net.corda.nodeapi.internal.config.toProperties
import net.corda.nodeapi.internal.createDevKeyStores
import net.corda.nodeapi.internal.crypto.* import net.corda.nodeapi.internal.crypto.*
import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralSubtree
import org.bouncycastle.asn1.x509.NameConstraints
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.nio.file.Path import java.nio.file.Path
import java.security.KeyStore
fun configOf(vararg pairs: Pair<String, Any?>): Config = ConfigFactory.parseMap(mapOf(*pairs)) fun configOf(vararg pairs: Pair<String, Any?>): Config = ConfigFactory.parseMap(mapOf(*pairs))
operator fun Config.plus(overrides: Map<String, Any?>): Config = ConfigFactory.parseMap(overrides).withFallback(this) operator fun Config.plus(overrides: Map<String, Any?>): Config = ConfigFactory.parseMap(overrides).withFallback(this)
@ -58,8 +62,10 @@ object ConfigHelper {
* Strictly for dev only automatically construct a server certificate/private key signed from * Strictly for dev only automatically construct a server certificate/private key signed from
* the CA certs in Node resources. Then provision KeyStores into certificates folder under node path. * the CA certs in Node resources. Then provision KeyStores into certificates folder under node path.
*/ */
// TODO Move this to KeyStoreConfigHelpers
fun NodeConfiguration.configureWithDevSSLCertificate() = configureDevKeyAndTrustStores(myLegalName) fun NodeConfiguration.configureWithDevSSLCertificate() = configureDevKeyAndTrustStores(myLegalName)
// TODO Move this to KeyStoreConfigHelpers
fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name) { fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name) {
certificatesDirectory.createDirectories() certificatesDirectory.createDirectories()
if (!trustStoreFile.exists()) { if (!trustStoreFile.exists()) {
@ -67,7 +73,9 @@ fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name) {
} }
if (!sslKeystore.exists() || !nodeKeystore.exists()) { if (!sslKeystore.exists() || !nodeKeystore.exists()) {
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass") val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
createKeystoreForCordaNode(sslKeystore, nodeKeystore, keyStorePassword, keyStorePassword, caKeyStore, "cordacadevkeypass", myLegalName) val rootCert = caKeyStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA).toX509CertHolder()
val intermediateCa = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
createDevKeyStores(rootCert, intermediateCa, myLegalName)
// Move distributed service composite key (generated by IdentityGenerator.generateToDisk) to keystore if exists. // Move distributed service composite key (generated by IdentityGenerator.generateToDisk) to keystore if exists.
val distributedServiceKeystore = certificatesDirectory / "distributedService.jks" val distributedServiceKeystore = certificatesDirectory / "distributedService.jks"
@ -86,58 +94,3 @@ fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name) {
} }
} }
} }
/**
* An all in wrapper to manufacture a server certificate and keys all stored in a KeyStore suitable for running TLS on the local machine.
* @param sslKeyStorePath KeyStore path to save ssl key and cert to.
* @param clientCAKeystorePath KeyStore path to save client CA key and cert to.
* @param storePassword access password for KeyStore.
* @param keyPassword PrivateKey access password for the generated keys.
* It is recommended that this is the same as the storePassword as most TLS libraries assume they are the same.
* @param caKeyStore KeyStore containing CA keys generated by createCAKeyStoreAndTrustStore.
* @param caKeyPassword password to unlock private keys in the CA KeyStore.
* @return The KeyStore created containing a private key, certificate chain and root CA public cert for use in TLS applications.
*/
fun createKeystoreForCordaNode(sslKeyStorePath: Path,
clientCAKeystorePath: Path,
storePassword: String,
keyPassword: String,
caKeyStore: KeyStore,
caKeyPassword: String,
legalName: CordaX500Name,
signatureScheme: SignatureScheme = X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) {
val rootCACert = caKeyStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA).toX509CertHolder()
val (intermediateCACert, intermediateCAKeyPair) = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, caKeyPassword)
val clientKey = Crypto.generateKeyPair(signatureScheme)
val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName.x500Name))), arrayOf())
val clientCACert = X509Utilities.createCertificate(CertificateType.NODE_CA,
intermediateCACert,
intermediateCAKeyPair,
legalName,
clientKey.public,
nameConstraints = nameConstraints)
val tlsKey = Crypto.generateKeyPair(signatureScheme)
val clientTLSCert = X509Utilities.createCertificate(CertificateType.TLS, clientCACert, clientKey, legalName, tlsKey.public)
val keyPass = keyPassword.toCharArray()
val clientCAKeystore = loadOrCreateKeyStore(clientCAKeystorePath, storePassword)
clientCAKeystore.addOrReplaceKey(
X509Utilities.CORDA_CLIENT_CA,
clientKey.private,
keyPass,
arrayOf(clientCACert, intermediateCACert, rootCACert))
clientCAKeystore.save(clientCAKeystorePath, storePassword)
val tlsKeystore = loadOrCreateKeyStore(sslKeyStorePath, storePassword)
tlsKeystore.addOrReplaceKey(
X509Utilities.CORDA_CLIENT_TLS,
tlsKey.private,
keyPass,
arrayOf(clientTLSCert, clientCACert, intermediateCACert, rootCACert))
tlsKeystore.save(sslKeyStorePath, storePassword)
}

View File

@ -10,6 +10,7 @@ import net.corda.core.contracts.ScheduledStateRef
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowLogicRefFactory
import net.corda.core.internal.ThreadBox import net.corda.core.internal.ThreadBox
import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.concurrent.flatMap import net.corda.core.internal.concurrent.flatMap
@ -19,10 +20,10 @@ import net.corda.core.schemas.PersistentStateRef
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.trace import net.corda.core.utilities.trace
import net.corda.node.internal.CordaClock
import net.corda.node.internal.MutableClock import net.corda.node.internal.MutableClock
import net.corda.node.services.api.FlowStarter import net.corda.node.services.api.FlowStarter
import net.corda.node.services.api.SchedulerService import net.corda.node.services.api.SchedulerService
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.AffinityExecutor
import net.corda.node.utilities.PersistentMap import net.corda.node.utilities.PersistentMap
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
@ -55,13 +56,14 @@ import com.google.common.util.concurrent.SettableFuture as GuavaSettableFuture
* activity. Only replace this for unit testing purposes. This is not the executor the [FlowLogic] is launched on. * activity. Only replace this for unit testing purposes. This is not the executor the [FlowLogic] is launched on.
*/ */
@ThreadSafe @ThreadSafe
class NodeSchedulerService(private val clock: Clock, class NodeSchedulerService(private val clock: CordaClock,
private val database: CordaPersistence, private val database: CordaPersistence,
private val flowStarter: FlowStarter, private val flowStarter: FlowStarter,
private val stateLoader: StateLoader, private val stateLoader: StateLoader,
private val schedulerTimerExecutor: Executor = Executors.newSingleThreadExecutor(), private val schedulerTimerExecutor: Executor = Executors.newSingleThreadExecutor(),
private val unfinishedSchedules: ReusableLatch = ReusableLatch(), private val unfinishedSchedules: ReusableLatch = ReusableLatch(),
private val serverThread: AffinityExecutor) private val serverThread: AffinityExecutor,
private val flowLogicRefFactory: FlowLogicRefFactory)
: SchedulerService, SingletonSerializeAsToken() { : SchedulerService, SingletonSerializeAsToken() {
companion object { companion object {
@ -78,16 +80,12 @@ class NodeSchedulerService(private val clock: Clock,
@Suspendable @Suspendable
@VisibleForTesting @VisibleForTesting
// We specify full classpath on SettableFuture to differentiate it from the Quasar class of the same name // We specify full classpath on SettableFuture to differentiate it from the Quasar class of the same name
fun awaitWithDeadline(clock: Clock, deadline: Instant, future: Future<*> = GuavaSettableFuture.create<Any>()): Boolean { fun awaitWithDeadline(clock: CordaClock, deadline: Instant, future: Future<*> = GuavaSettableFuture.create<Any>()): Boolean {
var nanos: Long var nanos: Long
do { do {
val originalFutureCompleted = makeStrandFriendlySettableFuture(future) val originalFutureCompleted = makeStrandFriendlySettableFuture(future)
val subscription = if (clock is MutableClock) { val subscription = clock.mutations.first().subscribe {
clock.mutations.first().subscribe { originalFutureCompleted.set(false)
originalFutureCompleted.set(false)
}
} else {
null
} }
nanos = (clock.instant() until deadline).toNanos() nanos = (clock.instant() until deadline).toNanos()
if (nanos > 0) { if (nanos > 0) {
@ -102,7 +100,7 @@ class NodeSchedulerService(private val clock: Clock,
// No need to take action as will fall out of the loop due to future.isDone // No need to take action as will fall out of the loop due to future.isDone
} }
} }
subscription?.unsubscribe() subscription.unsubscribe()
originalFutureCompleted.cancel(false) originalFutureCompleted.cancel(false)
} while (nanos > 0 && !future.isDone) } while (nanos > 0 && !future.isDone)
return future.isDone return future.isDone
@ -279,7 +277,7 @@ class NodeSchedulerService(private val clock: Clock,
scheduledStatesQueue.remove(scheduledState) scheduledStatesQueue.remove(scheduledState)
scheduledStatesQueue.add(newState) scheduledStatesQueue.add(newState)
} else { } else {
val flowLogic = FlowLogicRefFactoryImpl.toFlowLogic(scheduledActivity.logicRef) val flowLogic = flowLogicRefFactory.toFlowLogic(scheduledActivity.logicRef)
log.trace { "Scheduler starting FlowLogic $flowLogic" } log.trace { "Scheduler starting FlowLogic $flowLogic" }
scheduledFlow = flowLogic scheduledFlow = flowLogic
scheduledStates.remove(scheduledState.ref) scheduledStates.remove(scheduledState.ref)
@ -297,7 +295,7 @@ class NodeSchedulerService(private val clock: Clock,
val state = txState.data as SchedulableState val state = txState.data as SchedulableState
return try { return try {
// This can throw as running contract code. // This can throw as running contract code.
state.nextScheduledActivity(scheduledState.ref, FlowLogicRefFactoryImpl) state.nextScheduledActivity(scheduledState.ref, flowLogicRefFactory)
} catch (e: Exception) { } catch (e: Exception) {
log.error("Attempt to run scheduled state $scheduledState resulted in error.", e) log.error("Attempt to run scheduled state $scheduledState resulted in error.", e)
null null

View File

@ -4,19 +4,19 @@ import net.corda.core.contracts.ContractState
import net.corda.core.contracts.SchedulableState import net.corda.core.contracts.SchedulableState
import net.corda.core.contracts.ScheduledStateRef import net.corda.core.contracts.ScheduledStateRef
import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateAndRef
import net.corda.core.flows.FlowLogicRefFactory
import net.corda.core.node.services.VaultService import net.corda.core.node.services.VaultService
import net.corda.node.services.api.SchedulerService import net.corda.node.services.api.SchedulerService
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
/** /**
* This observes the vault and schedules and unschedules activities appropriately based on state production and * This observes the vault and schedules and unschedules activities appropriately based on state production and
* consumption. * consumption.
*/ */
class ScheduledActivityObserver private constructor(private val schedulerService: SchedulerService) { class ScheduledActivityObserver private constructor(private val schedulerService: SchedulerService, private val FlowLogicRefFactory: FlowLogicRefFactory) {
companion object { companion object {
@JvmStatic @JvmStatic
fun install(vaultService: VaultService, schedulerService: SchedulerService) { fun install(vaultService: VaultService, schedulerService: SchedulerService, flowLogicRefFactory: FlowLogicRefFactory) {
val observer = ScheduledActivityObserver(schedulerService) val observer = ScheduledActivityObserver(schedulerService, flowLogicRefFactory)
vaultService.rawUpdates.subscribe { (consumed, produced) -> vaultService.rawUpdates.subscribe { (consumed, produced) ->
consumed.forEach { schedulerService.unscheduleStateActivity(it.ref) } consumed.forEach { schedulerService.unscheduleStateActivity(it.ref) }
produced.forEach { observer.scheduleStateActivity(it) } produced.forEach { observer.scheduleStateActivity(it) }
@ -32,7 +32,7 @@ class ScheduledActivityObserver private constructor(private val schedulerService
private fun scheduleStateActivity(produced: StateAndRef<ContractState>) { private fun scheduleStateActivity(produced: StateAndRef<ContractState>) {
val producedState = produced.state.data val producedState = produced.state.data
if (producedState is SchedulableState) { if (producedState is SchedulableState) {
val scheduledAt = sandbox { producedState.nextScheduledActivity(produced.ref, FlowLogicRefFactoryImpl)?.scheduledAt } ?: return val scheduledAt = sandbox { producedState.nextScheduledActivity(produced.ref, FlowLogicRefFactory)?.scheduledAt } ?: return
schedulerService.scheduleStateActivity(ScheduledStateRef(produced.ref, scheduledAt)) schedulerService.scheduleStateActivity(ScheduledStateRef(produced.ref, scheduledAt))
} }
} }

View File

@ -31,10 +31,8 @@ data class FlowLogicRefImpl internal constructor(val flowLogicClassName: String,
* measure we might want the ability for the node admin to blacklist a flow such that it moves immediately to the "Flow Hospital" * measure we might want the ability for the node admin to blacklist a flow such that it moves immediately to the "Flow Hospital"
* in response to a potential malicious use or buggy update to an app etc. * in response to a potential malicious use or buggy update to an app etc.
*/ */
object FlowLogicRefFactoryImpl : SingletonSerializeAsToken(), FlowLogicRefFactory { // TODO: Replace with a per app classloader/cordapp provider/cordapp loader - this will do for now
// TODO: Replace with a per app classloader/cordapp provider/cordapp loader - this will do for now class FlowLogicRefFactoryImpl(private val classloader: ClassLoader) : SingletonSerializeAsToken(), FlowLogicRefFactory {
var classloader: ClassLoader = javaClass.classLoader
override fun create(flowClass: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef { override fun create(flowClass: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef {
if (!flowClass.isAnnotationPresent(SchedulableFlow::class.java)) { if (!flowClass.isAnnotationPresent(SchedulableFlow::class.java)) {
throw IllegalFlowLogicException(flowClass, "because it's not a schedulable flow") throw IllegalFlowLogicException(flowClass, "because it's not a schedulable flow")
@ -42,7 +40,7 @@ object FlowLogicRefFactoryImpl : SingletonSerializeAsToken(), FlowLogicRefFactor
return createForRPC(flowClass, *args) return createForRPC(flowClass, *args)
} }
fun createForRPC(flowClass: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef { override fun createForRPC(flowClass: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef {
// TODO: This is used via RPC but it's probably better if we pass in argument names and values explicitly // TODO: This is used via RPC but it's probably better if we pass in argument names and values explicitly
// to avoid requiring only a single constructor. // to avoid requiring only a single constructor.
val argTypes = args.map { it?.javaClass } val argTypes = args.map { it?.javaClass }
@ -81,7 +79,7 @@ object FlowLogicRefFactoryImpl : SingletonSerializeAsToken(), FlowLogicRefFactor
return FlowLogicRefImpl(type.name, args) return FlowLogicRefImpl(type.name, args)
} }
fun toFlowLogic(ref: FlowLogicRef): FlowLogic<*> { override fun toFlowLogic(ref: FlowLogicRef): FlowLogic<*> {
if (ref !is FlowLogicRefImpl) throw IllegalFlowLogicException(ref.javaClass, "FlowLogicRef was not created via correct FlowLogicRefFactory interface") if (ref !is FlowLogicRefImpl) throw IllegalFlowLogicException(ref.javaClass, "FlowLogicRef was not created via correct FlowLogicRefFactory interface")
val klass = Class.forName(ref.flowLogicClassName, true, classloader).asSubclass(FlowLogic::class.java) val klass = Class.forName(ref.flowLogicClassName, true, classloader).asSubclass(FlowLogic::class.java)
return createConstructor(klass, ref.args)() return createConstructor(klass, ref.args)()

View File

@ -12,10 +12,10 @@ import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
import org.bouncycastle.openssl.jcajce.JcaPEMWriter import org.bouncycastle.openssl.jcajce.JcaPEMWriter
import org.bouncycastle.util.io.pem.PemObject import org.bouncycastle.util.io.pem.PemObject
import java.io.StringWriter import java.io.StringWriter
import java.nio.file.Path
import java.security.KeyPair import java.security.KeyPair
import java.security.KeyStore import java.security.KeyStore
import java.security.cert.Certificate import java.security.cert.Certificate
import java.security.cert.X509Certificate
/** /**
* Helper for managing the node registration process, which checks for any existing certificates and requests them if * Helper for managing the node registration process, which checks for any existing certificates and requests them if
@ -32,7 +32,7 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
// 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 trustStore: KeyStore private val trustStore: KeyStore
private val rootCert: Certificate private val rootCert: X509Certificate
init { init {
require(config.trustStoreFile.exists()) { require(config.trustStoreFile.exists()) {
@ -46,7 +46,7 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
"This file must contain the root CA cert of your compatibility zone. " + "This file must contain the root CA cert of your compatibility zone. " +
"Please contact your CZ operator." "Please contact your CZ operator."
} }
this.rootCert = rootCert this.rootCert = rootCert as X509Certificate
} }
/** /**
@ -94,11 +94,8 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
caKeyStore.save(config.nodeKeystore, keystorePassword) caKeyStore.save(config.nodeKeystore, keystorePassword)
println("Node private key and certificate stored in ${config.nodeKeystore}.") println("Node private key and certificate stored in ${config.nodeKeystore}.")
// Check that the root of the signed certificate matches the expected certificate in the truststore. println("Checking root of the certificate path is what we expect.")
if (rootCert != certificates.last()) { X509Utilities.validateCertificateChain(rootCert, *certificates)
// Assumes certificate chain always starts with client certificate and end with root certificate.
throw WrongRootCertException(rootCert, certificates.last(), config.trustStoreFile)
}
println("Generating SSL certificate for node messaging service.") println("Generating SSL certificate for node messaging service.")
val sslKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val sslKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
@ -168,17 +165,3 @@ 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 that there has been a Man-in-the-middle attack when contacting the doorman.
*/
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
actual: $actual
the expected certificate is stored in: $expectedFilePath with alias $CORDA_ROOT_CA
""".trimMargin())

View File

@ -48,13 +48,15 @@ public class FlowLogicRefFromJavaTest {
} }
} }
private final FlowLogicRefFactoryImpl flowLogicRefFactory = new FlowLogicRefFactoryImpl(FlowLogicRefFactoryImpl.class.getClassLoader());
@Test @Test
public void test() { public void test() {
FlowLogicRefFactoryImpl.INSTANCE.createForRPC(JavaFlowLogic.class, new ParamType1(1), new ParamType2("Hello Jack")); flowLogicRefFactory.createForRPC(JavaFlowLogic.class, new ParamType1(1), new ParamType2("Hello Jack"));
} }
@Test @Test
public void testNoArg() { public void testNoArg() {
FlowLogicRefFactoryImpl.INSTANCE.createForRPC(JavaNoArgFlowLogic.class); flowLogicRefFactory.createForRPC(JavaNoArgFlowLogic.class);
} }
} }

View File

@ -34,47 +34,48 @@ class FlowLogicRefTest {
override fun call() = Unit override fun call() = Unit
} }
private val flowLogicRefFactory = FlowLogicRefFactoryImpl(FlowLogicRefFactoryImpl::class.java.classLoader)
@Test @Test
fun `create kotlin no arg`() { fun `create kotlin no arg`() {
FlowLogicRefFactoryImpl.createForRPC(KotlinNoArgFlowLogic::class.java) flowLogicRefFactory.createForRPC(KotlinNoArgFlowLogic::class.java)
} }
@Test @Test
fun `create kotlin`() { fun `create kotlin`() {
val args = mapOf(Pair("A", ParamType1(1)), Pair("b", ParamType2("Hello Jack"))) val args = mapOf(Pair("A", ParamType1(1)), Pair("b", ParamType2("Hello Jack")))
FlowLogicRefFactoryImpl.createKotlin(KotlinFlowLogic::class.java, args) flowLogicRefFactory.createKotlin(KotlinFlowLogic::class.java, args)
} }
@Test @Test
fun `create primary`() { fun `create primary`() {
FlowLogicRefFactoryImpl.createForRPC(KotlinFlowLogic::class.java, ParamType1(1), ParamType2("Hello Jack")) flowLogicRefFactory.createForRPC(KotlinFlowLogic::class.java, ParamType1(1), ParamType2("Hello Jack"))
} }
@Test @Test
fun `create kotlin void`() { fun `create kotlin void`() {
FlowLogicRefFactoryImpl.createKotlin(KotlinFlowLogic::class.java, emptyMap()) flowLogicRefFactory.createKotlin(KotlinFlowLogic::class.java, emptyMap())
} }
@Test @Test
fun `create kotlin non primary`() { fun `create kotlin non primary`() {
val args = mapOf(Pair("C", ParamType2("Hello Jack"))) val args = mapOf(Pair("C", ParamType2("Hello Jack")))
FlowLogicRefFactoryImpl.createKotlin(KotlinFlowLogic::class.java, args) flowLogicRefFactory.createKotlin(KotlinFlowLogic::class.java, args)
} }
@Test @Test
fun `create java primitive no registration required`() { fun `create java primitive no registration required`() {
val args = mapOf(Pair("primitive", "A string")) val args = mapOf(Pair("primitive", "A string"))
FlowLogicRefFactoryImpl.createKotlin(KotlinFlowLogic::class.java, args) flowLogicRefFactory.createKotlin(KotlinFlowLogic::class.java, args)
} }
@Test @Test
fun `create kotlin primitive no registration required`() { fun `create kotlin primitive no registration required`() {
val args = mapOf(Pair("kotlinType", 3)) val args = mapOf(Pair("kotlinType", 3))
FlowLogicRefFactoryImpl.createKotlin(KotlinFlowLogic::class.java, args) flowLogicRefFactory.createKotlin(KotlinFlowLogic::class.java, args)
} }
@Test(expected = IllegalFlowLogicException::class) @Test(expected = IllegalFlowLogicException::class)
fun `create for non-schedulable flow logic`() { fun `create for non-schedulable flow logic`() {
FlowLogicRefFactoryImpl.create(NonSchedulableFlow::class.java) flowLogicRefFactory.create(NonSchedulableFlow::class.java)
} }
} }

View File

@ -64,6 +64,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
@Rule @Rule
@JvmField @JvmField
val testSerialization = SerializationEnvironmentRule(true) val testSerialization = SerializationEnvironmentRule(true)
private val flowLogicRefFactory = FlowLogicRefFactoryImpl(FlowLogicRefFactoryImpl::class.java.classLoader)
private val realClock: Clock = Clock.systemUTC() private val realClock: Clock = Clock.systemUTC()
private val stoppedClock: Clock = Clock.fixed(realClock.instant(), realClock.zone) private val stoppedClock: Clock = Clock.fixed(realClock.instant(), realClock.zone)
private val testClock = TestClock(stoppedClock) private val testClock = TestClock(stoppedClock)
@ -122,7 +123,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
} }
smmExecutor = AffinityExecutor.ServiceAffinityExecutor("test", 1) smmExecutor = AffinityExecutor.ServiceAffinityExecutor("test", 1)
mockSMM = StateMachineManagerImpl(services, DBCheckpointStorage(), smmExecutor, database, newSecureRandom()) mockSMM = StateMachineManagerImpl(services, DBCheckpointStorage(), smmExecutor, database, newSecureRandom())
scheduler = NodeSchedulerService(testClock, database, FlowStarterImpl(smmExecutor, mockSMM), validatedTransactions, schedulerGatedExecutor, serverThread = smmExecutor) scheduler = NodeSchedulerService(testClock, database, FlowStarterImpl(smmExecutor, mockSMM, flowLogicRefFactory), validatedTransactions, schedulerGatedExecutor, serverThread = smmExecutor, flowLogicRefFactory = flowLogicRefFactory)
mockSMM.changes.subscribe { change -> mockSMM.changes.subscribe { change ->
if (change is StateMachineManager.Change.Removed && mockSMM.allStateMachines.isEmpty()) { if (change is StateMachineManager.Change.Removed && mockSMM.allStateMachines.isEmpty()) {
smmHasRemovedAllFlows.countDown() smmHasRemovedAllFlows.countDown()
@ -305,7 +306,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
database.transaction { database.transaction {
apply { apply {
val freshKey = kms.freshKey() val freshKey = kms.freshKey()
val state = TestState(FlowLogicRefFactoryImpl.createForRPC(TestFlowLogic::class.java, increment), instant, DUMMY_IDENTITY_1.party) val state = TestState(flowLogicRefFactory.createForRPC(TestFlowLogic::class.java, increment), instant, DUMMY_IDENTITY_1.party)
val builder = TransactionBuilder(null).apply { val builder = TransactionBuilder(null).apply {
addOutputState(state, DummyContract.PROGRAM_ID, DUMMY_NOTARY) addOutputState(state, DummyContract.PROGRAM_ID, DUMMY_NOTARY)
addCommand(Command(), freshKey) addCommand(Command(), freshKey)

View File

@ -70,7 +70,7 @@ class NetworkMapClientTest {
// The test server returns same network parameter for any hash. // The test server returns same network parameter for any hash.
val networkParameter = networkMapClient.getNetworkParameter(SecureHash.randomSHA256())?.verified() val networkParameter = networkMapClient.getNetworkParameter(SecureHash.randomSHA256())?.verified()
assertNotNull(networkParameter) assertNotNull(networkParameter)
assertEquals(NetworkMapServer.stubNetworkParameter, networkParameter) assertEquals(server.networkParameters, networkParameter)
} }
@Test @Test

View File

@ -8,6 +8,8 @@ import com.google.common.util.concurrent.SettableFuture
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.hours import net.corda.core.utilities.hours
import net.corda.core.utilities.minutes import net.corda.core.utilities.minutes
import net.corda.node.internal.CordaClock
import net.corda.node.internal.SimpleClock
import net.corda.node.services.events.NodeSchedulerService import net.corda.node.services.events.NodeSchedulerService
import net.corda.testing.node.TestClock import net.corda.testing.node.TestClock
import org.junit.After import org.junit.After
@ -25,13 +27,13 @@ import kotlin.test.fail
class ClockUtilsTest { class ClockUtilsTest {
lateinit var realClock: Clock lateinit var realClock: Clock
lateinit var stoppedClock: Clock lateinit var stoppedClock: CordaClock
lateinit var executor: ExecutorService lateinit var executor: ExecutorService
@Before @Before
fun setup() { fun setup() {
realClock = Clock.systemUTC() realClock = Clock.systemUTC()
stoppedClock = Clock.fixed(realClock.instant(), realClock.zone) stoppedClock = SimpleClock(Clock.fixed(realClock.instant(), realClock.zone))
executor = Executors.newSingleThreadExecutor() executor = Executors.newSingleThreadExecutor()
} }

View File

@ -14,12 +14,14 @@ import net.corda.core.internal.createDirectories
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.nodeapi.internal.crypto.* import net.corda.nodeapi.internal.crypto.*
import net.corda.testing.ALICE_NAME import net.corda.testing.ALICE_NAME
import net.corda.testing.internal.createDevNodeCaCertPath
import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.rigorousMock
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import java.security.cert.CertPathValidatorException
import java.security.cert.Certificate import java.security.cert.Certificate
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import kotlin.test.assertFalse import kotlin.test.assertFalse
@ -29,16 +31,19 @@ class NetworkRegistrationHelperTest {
private val fs = Jimfs.newFileSystem(unix()) private val fs = Jimfs.newFileSystem(unix())
private val requestId = SecureHash.randomSHA256().toString() private val requestId = SecureHash.randomSHA256().toString()
private val nodeLegalName = ALICE_NAME private val nodeLegalName = ALICE_NAME
private val intermediateCaName = CordaX500Name("CORDA_INTERMEDIATE_CA", "R3 Ltd", "London", "GB")
private val rootCaName = CordaX500Name("CORDA_ROOT_CA", "R3 Ltd", "London", "GB")
private val nodeCaCert = createCaCert(nodeLegalName)
private val intermediateCaCert = createCaCert(intermediateCaName)
private val rootCaCert = createCaCert(rootCaName)
private lateinit var rootCaCert: X509Certificate
private lateinit var intermediateCaCert: X509Certificate
private lateinit var nodeCaCert: X509Certificate
private lateinit var config: NodeConfiguration private lateinit var config: NodeConfiguration
@Before @Before
fun init() { fun init() {
val (rootCa, intermediateCa, nodeCa) = createDevNodeCaCertPath(nodeLegalName)
this.rootCaCert = rootCa.certificate.cert
this.intermediateCaCert = intermediateCa.certificate.cert
this.nodeCaCert = nodeCa.certificate.cert
val baseDirectory = fs.getPath("/baseDir").createDirectories() val baseDirectory = fs.getPath("/baseDir").createDirectories()
abstract class AbstractNodeConfiguration : NodeConfiguration abstract class AbstractNodeConfiguration : NodeConfiguration
config = rigorousMock<AbstractNodeConfiguration>().also { config = rigorousMock<AbstractNodeConfiguration>().also {
@ -108,11 +113,13 @@ class NetworkRegistrationHelperTest {
@Test @Test
fun `wrong root cert in truststore`() { fun `wrong root cert in truststore`() {
saveTrustStoreWithRootCa(createCaCert(CordaX500Name("Foo", "MU", "GB"))) val rootKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val rootCert = X509Utilities.createSelfSignedCACertificate(CordaX500Name("Foo", "MU", "GB"), rootKeyPair)
saveTrustStoreWithRootCa(rootCert.cert)
val registrationHelper = createRegistrationHelper() val registrationHelper = createRegistrationHelper()
assertThatThrownBy { assertThatThrownBy {
registrationHelper.buildKeystore() registrationHelper.buildKeystore()
}.isInstanceOf(WrongRootCertException::class.java) }.isInstanceOf(CertPathValidatorException::class.java)
} }
private fun createRegistrationHelper(): NetworkRegistrationHelper { private fun createRegistrationHelper(): NetworkRegistrationHelper {
@ -123,15 +130,11 @@ class NetworkRegistrationHelperTest {
return NetworkRegistrationHelper(config, certService) return NetworkRegistrationHelper(config, certService)
} }
private fun saveTrustStoreWithRootCa(rootCa: X509Certificate) { private fun saveTrustStoreWithRootCa(rootCert: X509Certificate) {
config.trustStoreFile.parent.createDirectories() config.certificatesDirectory.createDirectories()
loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword).also { loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword).also {
it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCa) it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCert)
it.save(config.trustStoreFile, config.trustStorePassword) it.save(config.trustStoreFile, config.trustStorePassword)
} }
} }
private fun createCaCert(name: CordaX500Name): X509Certificate {
return X509Utilities.createSelfSignedCACertificate(name, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)).cert
}
} }

View File

@ -21,7 +21,7 @@ define([
$scope.formError = resp.data; $scope.formError = resp.data;
}, handleHttpFail); }, handleHttpFail);
}; };
$('input.percent').mask("9.999999%", {placeholder: "", autoclear: false}); $('input.percent').mask("9.999999", {placeholder: "", autoclear: false});
$('#swapirscolumns').click(() => { $('#swapirscolumns').click(() => {
let first = $('#irscolumns .irscolumn:eq( 0 )'); let first = $('#irscolumns .irscolumn:eq( 0 )');
let last = $('#irscolumns .irscolumn:eq( 1 )'); let last = $('#irscolumns .irscolumn:eq( 1 )');

View File

@ -8,7 +8,7 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.services.config.BFTSMaRtConfiguration import net.corda.node.services.config.BFTSMaRtConfiguration
import net.corda.node.services.config.NotaryConfig import net.corda.node.services.config.NotaryConfig
import net.corda.node.services.transactions.minCorrectReplicas import net.corda.node.services.transactions.minCorrectReplicas
import net.corda.nodeapi.internal.IdentityGenerator import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.testing.node.internal.demorun.* import net.corda.testing.node.internal.demorun.*
import net.corda.testing.ALICE_NAME import net.corda.testing.ALICE_NAME
import net.corda.testing.BOB_NAME import net.corda.testing.BOB_NAME
@ -62,7 +62,7 @@ class BFTNotaryCordform : CordformDefinition() {
} }
override fun setup(context: CordformContext) { override fun setup(context: CordformContext) {
IdentityGenerator.generateDistributedNotaryIdentity( DevIdentityGenerator.generateDistributedNotaryIdentity(
notaryNames.map { context.baseDirectory(it.toString()) }, notaryNames.map { context.baseDirectory(it.toString()) },
clusterName, clusterName,
minCorrectReplicas(clusterSize) minCorrectReplicas(clusterSize)

View File

@ -4,11 +4,10 @@ import net.corda.cordform.CordformContext
import net.corda.cordform.CordformDefinition import net.corda.cordform.CordformDefinition
import net.corda.cordform.CordformNode import net.corda.cordform.CordformNode
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.node.services.NotaryService
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.services.config.NotaryConfig import net.corda.node.services.config.NotaryConfig
import net.corda.node.services.config.RaftConfig import net.corda.node.services.config.RaftConfig
import net.corda.nodeapi.internal.IdentityGenerator import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.testing.node.internal.demorun.* import net.corda.testing.node.internal.demorun.*
import net.corda.testing.ALICE_NAME import net.corda.testing.ALICE_NAME
import net.corda.testing.BOB_NAME import net.corda.testing.BOB_NAME
@ -59,7 +58,7 @@ class RaftNotaryCordform : CordformDefinition() {
} }
override fun setup(context: CordformContext) { override fun setup(context: CordformContext) {
IdentityGenerator.generateDistributedNotaryIdentity( DevIdentityGenerator.generateDistributedNotaryIdentity(
notaryNames.map { context.baseDirectory(it.toString()) }, notaryNames.map { context.baseDirectory(it.toString()) },
clusterName clusterName
) )

View File

@ -72,6 +72,9 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
notary = [validating : true] notary = [validating : true]
p2pPort 10002 p2pPort 10002
cordapps = ["$project.group:finance:$corda_release_version"] cordapps = ["$project.group:finance:$corda_release_version"]
extraConfig = [
jvmArgs : [ "-Xmx1g"]
]
} }
node { node {
name "O=Bank A,L=London,C=GB" name "O=Bank A,L=London,C=GB"
@ -80,6 +83,9 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
rpcPort 10006 rpcPort 10006
cordapps = ["$project.group:finance:$corda_release_version"] cordapps = ["$project.group:finance:$corda_release_version"]
rpcUsers = ext.rpcUsers rpcUsers = ext.rpcUsers
extraConfig = [
jvmArgs : [ "-Xmx1g"]
]
} }
node { node {
name "O=Bank B,L=New York,C=US" name "O=Bank B,L=New York,C=US"
@ -88,6 +94,9 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
rpcPort 10009 rpcPort 10009
cordapps = ["$project.group:finance:$corda_release_version"] cordapps = ["$project.group:finance:$corda_release_version"]
rpcUsers = ext.rpcUsers rpcUsers = ext.rpcUsers
extraConfig = [
jvmArgs : [ "-Xmx1g"]
]
} }
node { node {
name "O=Bank C,L=Tokyo,C=JP" name "O=Bank C,L=Tokyo,C=JP"
@ -96,6 +105,9 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
rpcPort 10012 rpcPort 10012
cordapps = ["$project.group:finance:$corda_release_version"] cordapps = ["$project.group:finance:$corda_release_version"]
rpcUsers = ext.rpcUsers rpcUsers = ext.rpcUsers
extraConfig = [
jvmArgs : [ "-Xmx1g"]
]
} }
} }

View File

@ -40,7 +40,8 @@ class SellerFlow(private val otherParty: Party,
progressTracker.currentStep = SELF_ISSUING progressTracker.currentStep = SELF_ISSUING
val cpOwner = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, false) val cpOwner = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, false)
val commercialPaper = serviceHub.vaultService.queryBy(CommercialPaper.State::class.java).states.first() val commercialPaper = serviceHub.vaultService.queryBy(CommercialPaper.State::class.java)
.states.firstOrNull() ?: throw IllegalStateException("No commercial paper found. Please check if you issued the papers first, follow the README for instructions.")
progressTracker.currentStep = TRADING progressTracker.currentStep = TRADING

View File

@ -2,6 +2,7 @@ package net.corda.testing.driver
import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.CordaFuture
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.CertRole
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.core.internal.list import net.corda.core.internal.list
import net.corda.core.internal.readLines import net.corda.core.internal.readLines
@ -47,12 +48,28 @@ class DriverTests : IntegrationTest() {
@Test @Test
fun `simple node startup and shutdown`() { fun `simple node startup and shutdown`() {
val handle = driver { val handle = driver {
val regulator = startNode(providedName = DUMMY_REGULATOR_NAME) val node = startNode(providedName = DUMMY_REGULATOR_NAME)
nodeMustBeUp(regulator) nodeMustBeUp(node)
} }
nodeMustBeDown(handle) nodeMustBeDown(handle)
} }
@Test
fun `starting with default notary`() {
driver {
// Make sure the default is a single-node notary
val notary = defaultNotaryNode.getOrThrow()
val notaryIdentities = notary.nodeInfo.legalIdentitiesAndCerts
// Make sure the notary node has only one identity
assertThat(notaryIdentities).hasSize(1)
val identity = notaryIdentities[0]
// Make sure this identity is a legal identity, like it is for normal nodes.
assertThat(CertRole.extract(identity.certificate)).isEqualTo(CertRole.LEGAL_IDENTITY)
// And make sure this identity is published as the notary identity (via the network parameters)
assertThat(notary.rpc.notaryIdentities()).containsOnly(identity.party)
}
}
@Test @Test
fun `random free port allocation`() { fun `random free port allocation`() {
val nodeHandle = driver(portAllocation = PortAllocation.RandomFree) { val nodeHandle = driver(portAllocation = PortAllocation.RandomFree) {

View File

@ -14,11 +14,11 @@ import net.corda.node.internal.StartedNode
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.VerifierType import net.corda.node.services.config.VerifierType
import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.config.User
import net.corda.testing.DUMMY_NOTARY_NAME
import net.corda.testing.node.NotarySpec
import net.corda.testing.node.internal.DriverDSLImpl import net.corda.testing.node.internal.DriverDSLImpl
import net.corda.testing.node.internal.genericDriver import net.corda.testing.node.internal.genericDriver
import net.corda.testing.node.internal.getTimestampAsDirectoryName import net.corda.testing.node.internal.getTimestampAsDirectoryName
import net.corda.testing.DUMMY_NOTARY_NAME
import net.corda.testing.node.NotarySpec
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.net.ServerSocket import java.net.ServerSocket
import java.nio.file.Path import java.nio.file.Path

View File

@ -38,7 +38,7 @@ import net.corda.node.services.transactions.BFTSMaRt
import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.services.transactions.InMemoryTransactionVerifierService
import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.AffinityExecutor
import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor
import net.corda.nodeapi.internal.IdentityGenerator import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.nodeapi.internal.network.NotaryInfo import net.corda.nodeapi.internal.network.NotaryInfo
@ -236,7 +236,7 @@ open class MockNetwork(private val cordappPackages: List<String>,
private fun generateNotaryIdentities(): List<NotaryInfo> { private fun generateNotaryIdentities(): List<NotaryInfo> {
return notarySpecs.mapIndexed { index, (name, validating) -> return notarySpecs.mapIndexed { index, (name, validating) ->
val identity = IdentityGenerator.generateNodeIdentity(baseDirectory(nextNodeId + index), name) val identity = DevIdentityGenerator.installKeyStoreWithNodeIdentity(baseDirectory(nextNodeId + index), name)
NotaryInfo(identity, validating) NotaryInfo(identity, validating)
} }
} }

View File

@ -10,15 +10,12 @@ import net.corda.cordform.CordformContext
import net.corda.cordform.CordformNode import net.corda.cordform.CordformNode
import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.CordaFuture
import net.corda.core.concurrent.firstOf import net.corda.core.concurrent.firstOf
import net.corda.core.crypto.random63BitValue
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.ThreadBox import net.corda.core.internal.*
import net.corda.core.internal.concurrent.* import net.corda.core.internal.concurrent.*
import net.corda.core.internal.copyTo
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.CordaRPCOps
import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.NetworkMapCache
import net.corda.core.serialization.deserialize
import net.corda.core.toFuture import net.corda.core.toFuture
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
@ -31,7 +28,8 @@ import net.corda.node.services.Permissions
import net.corda.node.services.config.* import net.corda.node.services.config.*
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
import net.corda.node.utilities.registration.NetworkRegistrationHelper import net.corda.node.utilities.registration.NetworkRegistrationHelper
import net.corda.nodeapi.internal.IdentityGenerator import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.addShutdownHook import net.corda.nodeapi.internal.addShutdownHook
import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.config.parseAs import net.corda.nodeapi.internal.config.parseAs
@ -68,7 +66,7 @@ import java.nio.file.StandardCopyOption
import java.security.cert.X509Certificate 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 import java.time.ZoneOffset.UTC
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.util.* import java.util.*
import java.util.concurrent.Executors import java.util.concurrent.Executors
@ -96,15 +94,16 @@ class DriverDSLImpl(
private var _shutdownManager: ShutdownManager? = null private var _shutdownManager: ShutdownManager? = null
override val shutdownManager get() = _shutdownManager!! override val shutdownManager get() = _shutdownManager!!
private val cordappPackages = extraCordappPackagesToScan + getCallerPackage() private val cordappPackages = extraCordappPackagesToScan + getCallerPackage()
// TODO: this object will copy NodeInfo files from started nodes to other nodes additional-node-infos/
// This uses the FileSystem and adds a delay (~5 seconds) given by the time we wait before polling the file system.
// Investigate whether we can avoid that.
private var nodeInfoFilesCopier: NodeInfoFilesCopier? = null
// Map from a nodes legal name to an observable emitting the number of nodes in its network map. // Map from a nodes legal name to an observable emitting the number of nodes in its network map.
private val countObservables = mutableMapOf<CordaX500Name, Observable<Int>>() private val countObservables = mutableMapOf<CordaX500Name, Observable<Int>>()
private lateinit var _notaries: List<NotaryHandle> /**
override val notaryHandles: List<NotaryHandle> get() = _notaries * Future which completes when the network map is available, whether a local one or one from the CZ. This future acts
private var networkParameters: NetworkParametersCopier? = null * as a gate to prevent nodes from starting too early. The value of the future is a [LocalNetworkMap] object, which
* is null if the network map is being provided by the CZ.
*/
private lateinit var networkMapAvailability: CordaFuture<LocalNetworkMap?>
private lateinit var _notaries: CordaFuture<List<NotaryHandle>>
override val notaryHandles: List<NotaryHandle> get() = _notaries.getOrThrow()
class State { class State {
val processes = ArrayList<Process>() val processes = ArrayList<Process>()
@ -145,12 +144,12 @@ class DriverDSLImpl(
_executorService?.shutdownNow() _executorService?.shutdownNow()
} }
private fun establishRpc(config: NodeConfiguration, processDeathFuture: CordaFuture<out Process>): CordaFuture<CordaRPCOps> { private fun establishRpc(config: NodeConfig, processDeathFuture: CordaFuture<out Process>): CordaFuture<CordaRPCOps> {
val rpcAddress = config.rpcAddress!! val rpcAddress = config.corda.rpcAddress!!
val client = CordaRPCClient(rpcAddress) val client = CordaRPCClient(rpcAddress)
val connectionFuture = poll(executorService, "RPC connection") { val connectionFuture = poll(executorService, "RPC connection") {
try { try {
client.start(config.rpcUsers[0].username, config.rpcUsers[0].password) config.corda.rpcUsers[0].run { client.start(username, password) }
} catch (e: Exception) { } catch (e: Exception) {
if (processDeathFuture.isDone) throw e if (processDeathFuture.isDone) throw e
log.error("Exception $e, Retrying RPC connection at $rpcAddress") log.error("Exception $e, Retrying RPC connection at $rpcAddress")
@ -178,65 +177,74 @@ class DriverDSLImpl(
): 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("${oneOf(names).organisation}-${p2pAddress.port}", "London", "GB")
val registrationFuture = if (compatibilityZone?.rootCert != null) { val registrationFuture = if (compatibilityZone?.rootCert != null) {
nodeRegistration(name, compatibilityZone.rootCert, compatibilityZone.url) // We don't need the network map to be available to be able to register the node
startNodeRegistration(name, compatibilityZone.rootCert, compatibilityZone.url)
} else { } else {
doneFuture(Unit) doneFuture(Unit)
} }
return registrationFuture.flatMap { return registrationFuture.flatMap {
val rpcAddress = portAllocation.nextHostAndPort() networkMapAvailability.flatMap {
val webAddress = portAllocation.nextHostAndPort() // But starting the node proper does require the network map
val users = rpcUsers.map { it.copy(permissions = it.permissions + DRIVER_REQUIRED_PERMISSIONS) } startRegisteredNode(name, it, rpcUsers, verifierType, customOverrides, startInSameProcess, maximumHeapSize, p2pAddress)
val configMap = configOf( }
"myLegalName" to name.toString(),
"p2pAddress" to p2pAddress.toString(),
"rpcAddress" to rpcAddress.toString(),
"webAddress" to webAddress.toString(),
"useTestClock" to useTestClock,
"rpcUsers" to if (users.isEmpty()) defaultRpcUserList else users.map { it.toConfig().root().unwrapped() },
"verifierType" to verifierType.name
) + customOverrides
val config = ConfigHelper.loadConfig(
baseDirectory = baseDirectory(name),
allowMissingConfig = true,
configOverrides = if (compatibilityZone != null) {
configMap + mapOf("compatibilityZoneURL" to compatibilityZone.url.toString())
} else {
configMap
}
)
startNodeInternal(config, webAddress, startInSameProcess, maximumHeapSize)
} }
} }
private fun nodeRegistration(providedName: CordaX500Name, rootCert: X509Certificate, compatibilityZoneURL: URL): CordaFuture<Unit> { private fun startRegisteredNode(name: CordaX500Name,
localNetworkMap: LocalNetworkMap?,
rpcUsers: List<User>,
verifierType: VerifierType,
customOverrides: Map<String, Any?>,
startInSameProcess: Boolean? = null,
maximumHeapSize: String = "200m",
p2pAddress: NetworkHostAndPort = portAllocation.nextHostAndPort()): CordaFuture<NodeHandle> {
val rpcAddress = portAllocation.nextHostAndPort()
val webAddress = portAllocation.nextHostAndPort()
val users = rpcUsers.map { it.copy(permissions = it.permissions + DRIVER_REQUIRED_PERMISSIONS) }
val czUrlConfig = if (compatibilityZone != null) mapOf("compatibilityZoneURL" to compatibilityZone.url.toString()) else emptyMap()
val config = NodeConfig(ConfigHelper.loadConfig(
baseDirectory = baseDirectory(name),
allowMissingConfig = true,
configOverrides = configOf(
"myLegalName" to name.toString(),
"p2pAddress" to p2pAddress.toString(),
"rpcAddress" to rpcAddress.toString(),
"webAddress" to webAddress.toString(),
"useTestClock" to useTestClock,
"rpcUsers" to if (users.isEmpty()) defaultRpcUserList else users.map { it.toConfig().root().unwrapped() },
"verifierType" to verifierType.name
) + czUrlConfig + customOverrides
))
return startNodeInternal(config, webAddress, startInSameProcess, maximumHeapSize, localNetworkMap)
}
private fun startNodeRegistration(providedName: CordaX500Name, rootCert: X509Certificate, compatibilityZoneURL: URL): CordaFuture<NodeConfig> {
val baseDirectory = baseDirectory(providedName).createDirectories() val baseDirectory = baseDirectory(providedName).createDirectories()
val config = ConfigHelper.loadConfig( val config = NodeConfig(ConfigHelper.loadConfig(
baseDirectory = baseDirectory, 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
"compatibilityZoneURL" to compatibilityZoneURL.toString(), "compatibilityZoneURL" to compatibilityZoneURL.toString(),
"myLegalName" to providedName.toString()) "myLegalName" to providedName.toString())
) ))
val configuration = config.parseAsNodeConfiguration()
configuration.trustStoreFile.parent.createDirectories() config.corda.certificatesDirectory.createDirectories()
loadOrCreateKeyStore(configuration.trustStoreFile, configuration.trustStorePassword).also { loadOrCreateKeyStore(config.corda.trustStoreFile, config.corda.trustStorePassword).apply {
it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCert) addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCert)
it.save(configuration.trustStoreFile, configuration.trustStorePassword) save(config.corda.trustStoreFile, config.corda.trustStorePassword)
} }
return if (startNodesInProcess) { return if (startNodesInProcess) {
// This is a bit cheating, we're not starting a full node, we're just calling the code nodes call executorService.fork {
// when registering. NetworkRegistrationHelper(config.corda, HTTPNetworkRegistrationService(compatibilityZoneURL)).buildKeystore()
NetworkRegistrationHelper(configuration, HTTPNetworkRegistrationService(compatibilityZoneURL)).buildKeystore() config
doneFuture(Unit) }
} else { } else {
startOutOfProcessNodeRegistration(config, configuration) startOutOfProcessMiniNode(config, "--initial-registration").map { config }
} }
} }
@ -247,7 +255,9 @@ class DriverDSLImpl(
} }
internal fun startCordformNodes(cordforms: List<CordformNode>): CordaFuture<*> { internal fun startCordformNodes(cordforms: List<CordformNode>): CordaFuture<*> {
check(compatibilityZone == null) { "Cordform nodes should be run without compatibilityZone configuration" } check(notarySpecs.isEmpty()) { "Specify notaries in the CordformDefinition" }
check(compatibilityZone == null) { "Cordform nodes cannot be run with compatibilityZoneURL" }
val clusterNodes = HashMultimap.create<ClusterType, CordaX500Name>() val clusterNodes = HashMultimap.create<ClusterType, CordaX500Name>()
val notaryInfos = ArrayList<NotaryInfo>() val notaryInfos = ArrayList<NotaryInfo>()
@ -266,23 +276,23 @@ class DriverDSLImpl(
clusterNodes.put(ClusterType.NON_VALIDATING_BFT, name) clusterNodes.put(ClusterType.NON_VALIDATING_BFT, name)
} else { } else {
// We have all we need here to generate the identity for single node notaries // We have all we need here to generate the identity for single node notaries
val identity = IdentityGenerator.generateNodeIdentity(baseDirectory(name), legalName = name) val identity = DevIdentityGenerator.installKeyStoreWithNodeIdentity(baseDirectory(name), legalName = name)
notaryInfos += NotaryInfo(identity, notaryConfig.validating) notaryInfos += NotaryInfo(identity, notaryConfig.validating)
} }
} }
clusterNodes.asMap().forEach { type, nodeNames -> clusterNodes.asMap().forEach { type, nodeNames ->
val identity = IdentityGenerator.generateDistributedNotaryIdentity( val identity = DevIdentityGenerator.generateDistributedNotaryIdentity(
dirs = nodeNames.map { baseDirectory(it) }, dirs = nodeNames.map { baseDirectory(it) },
notaryName = type.clusterName notaryName = type.clusterName
) )
notaryInfos += NotaryInfo(identity, type.validating) notaryInfos += NotaryInfo(identity, type.validating)
} }
networkParameters = NetworkParametersCopier(testNetworkParameters(notaryInfos)) val localNetworkMap = LocalNetworkMap(notaryInfos)
return cordforms.map { return cordforms.map {
val startedNode = startCordformNode(it) val startedNode = startCordformNode(it, localNetworkMap)
if (it.webAddress != null) { if (it.webAddress != null) {
// Start a webserver if an address for it was specified // Start a webserver if an address for it was specified
startedNode.flatMap { startWebserver(it) } startedNode.flatMap { startWebserver(it) }
@ -292,21 +302,21 @@ class DriverDSLImpl(
}.transpose() }.transpose()
} }
private fun startCordformNode(cordform: CordformNode): CordaFuture<NodeHandle> { private fun startCordformNode(cordform: CordformNode, localNetworkMap: LocalNetworkMap): CordaFuture<NodeHandle> {
val name = CordaX500Name.parse(cordform.name) val name = CordaX500Name.parse(cordform.name)
// TODO We shouldn't have to allocate an RPC or web address if they're not specified. We're having to do this because of startNodeInternal // TODO We shouldn't have to allocate an RPC or web address if they're not specified. We're having to do this because of startNodeInternal
val rpcAddress = if (cordform.rpcAddress == null) mapOf("rpcAddress" to portAllocation.nextHostAndPort().toString()) else emptyMap() val rpcAddress = if (cordform.rpcAddress == null) mapOf("rpcAddress" to portAllocation.nextHostAndPort().toString()) else emptyMap()
val webAddress = cordform.webAddress?.let { NetworkHostAndPort.parse(it) } ?: portAllocation.nextHostAndPort() val webAddress = cordform.webAddress?.let { NetworkHostAndPort.parse(it) } ?: portAllocation.nextHostAndPort()
val notary = if (cordform.notary != null) mapOf("notary" to cordform.notary) else emptyMap() val notary = if (cordform.notary != null) mapOf("notary" to cordform.notary) else emptyMap()
val rpcUsers = cordform.rpcUsers val rpcUsers = cordform.rpcUsers
val config = ConfigHelper.loadConfig( val config = NodeConfig(ConfigHelper.loadConfig(
baseDirectory = baseDirectory(name), baseDirectory = baseDirectory(name),
allowMissingConfig = true, allowMissingConfig = true,
configOverrides = cordform.config + rpcAddress + notary + mapOf( configOverrides = cordform.config + rpcAddress + notary + mapOf(
"rpcUsers" to if (rpcUsers.isEmpty()) defaultRpcUserList else rpcUsers "rpcUsers" to if (rpcUsers.isEmpty()) defaultRpcUserList else rpcUsers
) )
) ))
return startNodeInternal(config, webAddress, null, "200m") return startNodeInternal(config, webAddress, null, "200m", localNetworkMap)
} }
private fun queryWebserver(handle: NodeHandle, process: Process): WebserverHandle { private fun queryWebserver(handle: NodeHandle, process: Process): WebserverHandle {
@ -340,31 +350,80 @@ class DriverDSLImpl(
} }
_executorService = Executors.newScheduledThreadPool(2, ThreadFactoryBuilder().setNameFormat("driver-pool-thread-%d").build()) _executorService = Executors.newScheduledThreadPool(2, ThreadFactoryBuilder().setNameFormat("driver-pool-thread-%d").build())
_shutdownManager = ShutdownManager(executorService) _shutdownManager = ShutdownManager(executorService)
if (compatibilityZone == null) { val notaryInfosFuture = if (compatibilityZone == null) {
// Without a compatibility zone URL we have to copy the node info files ourselves to make sure the nodes see each other // If no CZ is specified then the driver does the generation of the network parameters and the copying of the
nodeInfoFilesCopier = NodeInfoFilesCopier().also { // node info files.
shutdownManager.registerShutdown(it::close) startNotaryIdentityGeneration().map { notaryInfos -> Pair(notaryInfos, LocalNetworkMap(notaryInfos)) }
} else {
// Otherwise it's the CZ's job to distribute thse via the HTTP network map, as that is what the nodes will be expecting.
val notaryInfosFuture = if (compatibilityZone.rootCert == null) {
// No root cert specified so we use the dev root cert to generate the notary identities.
startNotaryIdentityGeneration()
} else {
// With a root cert specified we delegate generation of the notary identities to the CZ.
startAllNotaryRegistrations(compatibilityZone.rootCert, compatibilityZone.url)
}
notaryInfosFuture.map { notaryInfos ->
compatibilityZone.publishNotaries(notaryInfos)
Pair(notaryInfos, null)
}
}
networkMapAvailability = notaryInfosFuture.map { it.second }
_notaries = notaryInfosFuture.map { (notaryInfos, localNetworkMap) ->
val listOfFutureNodeHandles = startNotaries(localNetworkMap)
notaryInfos.zip(listOfFutureNodeHandles) { (identity, validating), nodeHandlesFuture ->
NotaryHandle(identity, validating, nodeHandlesFuture)
} }
} }
val notaryInfos = generateNotaryIdentities()
// The network parameters must be serialised before starting any of the nodes
if (compatibilityZone == null) networkParameters = NetworkParametersCopier(testNetworkParameters(notaryInfos))
val nodeHandles = startNotaries()
_notaries = notaryInfos.zip(nodeHandles) { (identity, validating), nodes -> NotaryHandle(identity, validating, nodes) }
} }
private fun generateNotaryIdentities(): List<NotaryInfo> { private fun startNotaryIdentityGeneration(): CordaFuture<List<NotaryInfo>> {
return notarySpecs.map { spec -> return executorService.fork {
val identity = if (spec.cluster == null) { notarySpecs.map { spec ->
IdentityGenerator.generateNodeIdentity(baseDirectory(spec.name), spec.name, compatibilityZone?.rootCert) val identity = if (spec.cluster == null) {
} else { DevIdentityGenerator.installKeyStoreWithNodeIdentity(baseDirectory(spec.name), spec.name)
IdentityGenerator.generateDistributedNotaryIdentity( } else {
dirs = generateNodeNames(spec).map { baseDirectory(it) }, DevIdentityGenerator.generateDistributedNotaryIdentity(
notaryName = spec.name, dirs = generateNodeNames(spec).map { baseDirectory(it) },
customRootCert = compatibilityZone?.rootCert notaryName = spec.name
) )
}
NotaryInfo(identity, spec.validating)
}
}
}
private fun startAllNotaryRegistrations(rootCert: X509Certificate, compatibilityZoneURL: URL): CordaFuture<List<NotaryInfo>> {
// Start the registration process for all the notaries together then wait for their responses.
return notarySpecs.map { spec ->
require(spec.cluster == null) { "Registering distributed notaries not supported" }
startNotaryRegistration(spec, rootCert, compatibilityZoneURL)
}.transpose()
}
private fun startNotaryRegistration(spec: NotarySpec, rootCert: X509Certificate, compatibilityZoneURL: URL): CordaFuture<NotaryInfo> {
return startNodeRegistration(spec.name, rootCert, compatibilityZoneURL).flatMap { config ->
// Node registration only gives us the node CA cert, not the identity cert. That is only created on first
// startup or when the node is told to just generate its node info file. We do that here.
if (startNodesInProcess) {
executorService.fork {
val nodeInfo = Node(config.corda, MOCK_VERSION_INFO, initialiseSerialization = false).generateAndSaveNodeInfo()
NotaryInfo(nodeInfo.legalIdentities[0], spec.validating)
}
} else {
// TODO The config we use here is uses a hardocded p2p port which changes when the node is run proper
// This causes two node info files to be generated.
startOutOfProcessMiniNode(config, "--just-generate-node-info").map {
// Once done we have to read the signed node info file that's been generated
val nodeInfoFile = config.corda.baseDirectory.list { paths ->
paths.filter { it.fileName.toString().startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) }.findFirst().get()
}
val nodeInfo = nodeInfoFile.readAll().deserialize<SignedNodeInfo>().verified()
NotaryInfo(nodeInfo.legalIdentities[0], spec.validating)
}
} }
NotaryInfo(identity, spec.validating)
} }
} }
@ -372,11 +431,11 @@ class DriverDSLImpl(
return (0 until spec.cluster!!.clusterSize).map { spec.name.copy(organisation = "${spec.name.organisation}-$it") } return (0 until spec.cluster!!.clusterSize).map { spec.name.copy(organisation = "${spec.name.organisation}-$it") }
} }
private fun startNotaries(): List<CordaFuture<List<NodeHandle>>> { private fun startNotaries(localNetworkMap: LocalNetworkMap?): List<CordaFuture<List<NodeHandle>>> {
return notarySpecs.map { return notarySpecs.map {
when { when {
it.cluster == null -> startSingleNotary(it) it.cluster == null -> startSingleNotary(it, localNetworkMap)
it.cluster is ClusterSpec.Raft -> startRaftNotaryCluster(it) it.cluster is ClusterSpec.Raft -> startRaftNotaryCluster(it, localNetworkMap)
else -> throw IllegalArgumentException("BFT-SMaRt not supported") else -> throw IllegalArgumentException("BFT-SMaRt not supported")
} }
} }
@ -386,16 +445,17 @@ class DriverDSLImpl(
// generating the configs for the nodes, probably making use of Any.toConfig() // generating the configs for the nodes, probably making use of Any.toConfig()
private fun NotaryConfig.toConfigMap(): Map<String, Any> = mapOf("notary" to toConfig().root().unwrapped()) private fun NotaryConfig.toConfigMap(): Map<String, Any> = mapOf("notary" to toConfig().root().unwrapped())
private fun startSingleNotary(spec: NotarySpec): CordaFuture<List<NodeHandle>> { private fun startSingleNotary(spec: NotarySpec, localNetworkMap: LocalNetworkMap?): CordaFuture<List<NodeHandle>> {
return startNode( return startRegisteredNode(
providedName = spec.name, spec.name,
rpcUsers = spec.rpcUsers, localNetworkMap,
verifierType = spec.verifierType, spec.rpcUsers,
spec.verifierType,
customOverrides = NotaryConfig(spec.validating).toConfigMap() customOverrides = NotaryConfig(spec.validating).toConfigMap()
).map { listOf(it) } ).map { listOf(it) }
} }
private fun startRaftNotaryCluster(spec: NotarySpec): CordaFuture<List<NodeHandle>> { private fun startRaftNotaryCluster(spec: NotarySpec, localNetworkMap: LocalNetworkMap?): CordaFuture<List<NodeHandle>> {
fun notaryConfig(nodeAddress: NetworkHostAndPort, clusterAddress: NetworkHostAndPort? = null): Map<String, Any> { fun notaryConfig(nodeAddress: NetworkHostAndPort, clusterAddress: NetworkHostAndPort? = null): Map<String, Any> {
val clusterAddresses = if (clusterAddress != null) listOf(clusterAddress) else emptyList() val clusterAddresses = if (clusterAddress != null) listOf(clusterAddress) else emptyList()
val config = NotaryConfig( val config = NotaryConfig(
@ -408,20 +468,22 @@ class DriverDSLImpl(
val clusterAddress = portAllocation.nextHostAndPort() val clusterAddress = portAllocation.nextHostAndPort()
// Start the first node that will bootstrap the cluster // Start the first node that will bootstrap the cluster
val firstNodeFuture = startNode( val firstNodeFuture = startRegisteredNode(
providedName = nodeNames[0], nodeNames[0],
rpcUsers = spec.rpcUsers, localNetworkMap,
verifierType = spec.verifierType, spec.rpcUsers,
spec.verifierType,
customOverrides = notaryConfig(clusterAddress) customOverrides = notaryConfig(clusterAddress)
) )
// All other nodes will join the cluster // All other nodes will join the cluster
val restNodeFutures = nodeNames.drop(1).map { val restNodeFutures = nodeNames.drop(1).map {
val nodeAddress = portAllocation.nextHostAndPort() val nodeAddress = portAllocation.nextHostAndPort()
startNode( startRegisteredNode(
providedName = it, it,
rpcUsers = spec.rpcUsers, localNetworkMap,
verifierType = spec.verifierType, spec.rpcUsers,
spec.verifierType,
customOverrides = notaryConfig(nodeAddress, clusterAddress) customOverrides = notaryConfig(nodeAddress, clusterAddress)
) )
} }
@ -436,8 +498,6 @@ class DriverDSLImpl(
return driverDirectory / nodeDirectoryName return driverDirectory / nodeDirectoryName
} }
override fun baseDirectory(nodeName: String): Path = baseDirectory(CordaX500Name.parse(nodeName))
/** /**
* @param initial number of nodes currently in the network map of a running node. * @param initial number of nodes currently in the network map of a running node.
* @param networkMapCacheChangeObservable an observable returning the updates to the node network map. * @param networkMapCacheChangeObservable an observable returning the updates to the node network map.
@ -447,7 +507,7 @@ class DriverDSLImpl(
private fun nodeCountObservable(initial: Int, networkMapCacheChangeObservable: Observable<NetworkMapCache.MapChange>): private fun nodeCountObservable(initial: Int, networkMapCacheChangeObservable: Observable<NetworkMapCache.MapChange>):
ConnectableObservable<Int> { ConnectableObservable<Int> {
val count = AtomicInteger(initial) val count = AtomicInteger(initial)
return networkMapCacheChangeObservable.map { it -> return networkMapCacheChangeObservable.map {
when (it) { when (it) {
is NetworkMapCache.MapChange.Added -> count.incrementAndGet() is NetworkMapCache.MapChange.Added -> count.incrementAndGet()
is NetworkMapCache.MapChange.Removed -> count.decrementAndGet() is NetworkMapCache.MapChange.Removed -> count.decrementAndGet()
@ -482,31 +542,44 @@ class DriverDSLImpl(
return future return future
} }
private fun startOutOfProcessNodeRegistration(config: Config, configuration: NodeConfiguration): CordaFuture<Unit> { /**
* Start the node with the given flag which is expected to start the node for some function, which once complete will
* terminate the node.
*/
private fun startOutOfProcessMiniNode(config: NodeConfig, extraCmdLineFlag: String): CordaFuture<Unit> {
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
val monitorPort = if (jmxPolicy.startJmxHttpServer) jmxPolicy.jmxHttpServerPortAllocation?.nextPort() else null val monitorPort = if (jmxPolicy.startJmxHttpServer) jmxPolicy.jmxHttpServerPortAllocation?.nextPort() else null
val process = startOutOfProcessNode(configuration, config, quasarJarPath, debugPort, jolokiaJarPath, monitorPort, val process = startOutOfProcessNode(
systemProperties, cordappPackages, "200m", initialRegistration = true) config,
quasarJarPath,
debugPort,
jolokiaJarPath,
monitorPort,
systemProperties,
cordappPackages,
"200m",
extraCmdLineFlag
)
return poll(executorService, "node registration (${configuration.myLegalName})") { return poll(executorService, "$extraCmdLineFlag (${config.corda.myLegalName})") {
if (process.isAlive) null else Unit if (process.isAlive) null else Unit
} }
} }
private fun startNodeInternal(config: Config, private fun startNodeInternal(config: NodeConfig,
webAddress: NetworkHostAndPort, webAddress: NetworkHostAndPort,
startInProcess: Boolean?, startInProcess: Boolean?,
maximumHeapSize: String): CordaFuture<NodeHandle> { maximumHeapSize: String,
val configuration = config.parseAsNodeConfiguration() localNetworkMap: LocalNetworkMap?): CordaFuture<NodeHandle> {
val baseDirectory = configuration.baseDirectory.createDirectories() val baseDirectory = config.corda.baseDirectory.createDirectories()
nodeInfoFilesCopier?.addConfig(baseDirectory) localNetworkMap?.networkParametersCopier?.install(baseDirectory)
networkParameters?.install(baseDirectory) localNetworkMap?.nodeInfosCopier?.addConfig(baseDirectory)
val onNodeExit: () -> Unit = { val onNodeExit: () -> Unit = {
nodeInfoFilesCopier?.removeConfig(baseDirectory) localNetworkMap?.nodeInfosCopier?.removeConfig(baseDirectory)
countObservables.remove(configuration.myLegalName) countObservables.remove(config.corda.myLegalName)
} }
if (startInProcess ?: startNodesInProcess) { if (startInProcess ?: startNodesInProcess) {
val nodeAndThreadFuture = startInProcessNode(executorService, configuration, config, cordappPackages) val nodeAndThreadFuture = startInProcessNode(executorService, config, cordappPackages)
shutdownManager.registerShutdown( shutdownManager.registerShutdown(
nodeAndThreadFuture.map { (node, thread) -> nodeAndThreadFuture.map { (node, thread) ->
{ {
@ -516,16 +589,16 @@ class DriverDSLImpl(
} }
) )
return nodeAndThreadFuture.flatMap { (node, thread) -> return nodeAndThreadFuture.flatMap { (node, thread) ->
establishRpc(configuration, openFuture()).flatMap { rpc -> establishRpc(config, openFuture()).flatMap { rpc ->
allNodesConnected(rpc).map { allNodesConnected(rpc).map {
NodeHandle.InProcess(rpc.nodeInfo(), rpc, configuration, webAddress, node, thread, onNodeExit) NodeHandle.InProcess(rpc.nodeInfo(), rpc, config.corda, webAddress, node, thread, onNodeExit)
} }
} }
} }
} else { } else {
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
val monitorPort = if (jmxPolicy.startJmxHttpServer) jmxPolicy.jmxHttpServerPortAllocation?.nextPort() else null val monitorPort = if (jmxPolicy.startJmxHttpServer) jmxPolicy.jmxHttpServerPortAllocation?.nextPort() else null
val process = startOutOfProcessNode(configuration, config, quasarJarPath, debugPort, jolokiaJarPath, monitorPort, systemProperties, cordappPackages, maximumHeapSize, initialRegistration = false) val process = startOutOfProcessNode(config, quasarJarPath, debugPort, jolokiaJarPath, monitorPort, systemProperties, cordappPackages, maximumHeapSize, null)
if (waitForNodesToFinish) { if (waitForNodesToFinish) {
state.locked { state.locked {
processes += process processes += process
@ -533,22 +606,21 @@ class DriverDSLImpl(
} else { } else {
shutdownManager.registerProcessShutdown(process) shutdownManager.registerProcessShutdown(process)
} }
val p2pReadyFuture = addressMustBeBoundFuture(executorService, configuration.p2pAddress, process) val p2pReadyFuture = addressMustBeBoundFuture(executorService, config.corda.p2pAddress, process)
return p2pReadyFuture.flatMap { return p2pReadyFuture.flatMap {
val processDeathFuture = poll(executorService, "process death while waiting for RPC (${configuration.myLegalName})") { val processDeathFuture = poll(executorService, "process death while waiting for RPC (${config.corda.myLegalName})") {
if (process.isAlive) null else process if (process.isAlive) null else process
} }
establishRpc(configuration, processDeathFuture).flatMap { rpc -> establishRpc(config, processDeathFuture).flatMap { rpc ->
// Check for all nodes to have all other nodes in background in case RPC is failing over: // Check for all nodes to have all other nodes in background in case RPC is failing over:
val networkMapFuture = executorService.fork { allNodesConnected(rpc) }.flatMap { it } val networkMapFuture = executorService.fork { allNodesConnected(rpc) }.flatMap { it }
firstOf(processDeathFuture, networkMapFuture) { firstOf(processDeathFuture, networkMapFuture) {
if (it == processDeathFuture) { if (it == processDeathFuture) {
throw ListenProcessDeathException(configuration.p2pAddress, process) throw ListenProcessDeathException(config.corda.p2pAddress, process)
} }
processDeathFuture.cancel(false) processDeathFuture.cancel(false)
log.info("Node handle is ready. NodeInfo: ${rpc.nodeInfo()}, WebAddress: $webAddress") log.info("Node handle is ready. NodeInfo: ${rpc.nodeInfo()}, WebAddress: $webAddress")
NodeHandle.OutOfProcess(rpc.nodeInfo(), rpc, configuration, webAddress, debugPort, process, NodeHandle.OutOfProcess(rpc.nodeInfo(), rpc, config.corda, webAddress, debugPort, process, onNodeExit)
onNodeExit)
} }
} }
} }
@ -561,6 +633,25 @@ class DriverDSLImpl(
return pollFuture return pollFuture
} }
/**
* The local version of the network map, which is a bunch of classes that copy the relevant files to the node directories.
*/
private inner class LocalNetworkMap(notaryInfos: List<NotaryInfo>) {
val networkParametersCopier = NetworkParametersCopier(testNetworkParameters(notaryInfos))
// TODO: this object will copy NodeInfo files from started nodes to other nodes additional-node-infos/
// This uses the FileSystem and adds a delay (~5 seconds) given by the time we wait before polling the file system.
// Investigate whether we can avoid that.
val nodeInfosCopier = NodeInfoFilesCopier().also { shutdownManager.registerShutdown(it::close) }
}
/**
* Simple holder class to capture the node configuration both as the raw [Config] object and the parsed [NodeConfiguration].
* Keeping [Config] around is needed as the user may specify extra config options not specified in [NodeConfiguration].
*/
private class NodeConfig(val typesafe: Config) {
val corda: NodeConfiguration = typesafe.parseAsNodeConfiguration()
}
companion object { companion object {
internal val log = contextLogger() internal val log = contextLogger()
@ -588,28 +679,26 @@ class DriverDSLImpl(
private fun startInProcessNode( private fun startInProcessNode(
executorService: ScheduledExecutorService, executorService: ScheduledExecutorService,
nodeConf: NodeConfiguration, config: NodeConfig,
config: Config,
cordappPackages: List<String> cordappPackages: List<String>
): CordaFuture<Pair<StartedNode<Node>, Thread>> { ): CordaFuture<Pair<StartedNode<Node>, Thread>> {
return executorService.fork { return executorService.fork {
log.info("Starting in-process Node ${nodeConf.myLegalName.organisation}") log.info("Starting in-process Node ${config.corda.myLegalName.organisation}")
// Write node.conf // Write node.conf
writeConfig(nodeConf.baseDirectory, "node.conf", config) writeConfig(config.corda.baseDirectory, "node.conf", config.typesafe)
// TODO pass the version in? // TODO pass the version in?
val node = InProcessNode(nodeConf, MOCK_VERSION_INFO, cordappPackages).start() val node = InProcessNode(config.corda, MOCK_VERSION_INFO, cordappPackages).start()
val nodeThread = thread(name = nodeConf.myLegalName.organisation) { val nodeThread = thread(name = config.corda.myLegalName.organisation) {
node.internals.run() node.internals.run()
} }
node to nodeThread node to nodeThread
}.flatMap { nodeAndThread -> }.flatMap { nodeAndThread ->
addressMustBeBoundFuture(executorService, nodeConf.p2pAddress).map { nodeAndThread } addressMustBeBoundFuture(executorService, config.corda.p2pAddress).map { nodeAndThread }
} }
} }
private fun startOutOfProcessNode( private fun startOutOfProcessNode(
nodeConf: NodeConfiguration, config: NodeConfig,
config: Config,
quasarJarPath: String, quasarJarPath: String,
debugPort: Int?, debugPort: Int?,
jolokiaJarPath: String, jolokiaJarPath: String,
@ -617,15 +706,17 @@ class DriverDSLImpl(
overriddenSystemProperties: Map<String, String>, overriddenSystemProperties: Map<String, String>,
cordappPackages: List<String>, cordappPackages: List<String>,
maximumHeapSize: String, maximumHeapSize: String,
initialRegistration: Boolean extraCmdLineFlag: String?
): Process { ): Process {
log.info("Starting out-of-process Node ${nodeConf.myLegalName.organisation}, debug port is " + (debugPort ?: "not enabled") + ", jolokia monitoring port is " + (monitorPort ?: "not enabled")) log.info("Starting out-of-process Node ${config.corda.myLegalName.organisation}, " +
"debug port is " + (debugPort ?: "not enabled") + ", " +
"jolokia monitoring port is " + (monitorPort ?: "not enabled"))
// Write node.conf // Write node.conf
writeConfig(nodeConf.baseDirectory, "node.conf", config) writeConfig(config.corda.baseDirectory, "node.conf", config.typesafe)
val systemProperties = mutableMapOf( val systemProperties = mutableMapOf(
"name" to nodeConf.myLegalName, "name" to config.corda.myLegalName,
"visualvm.display.name" to "corda-${nodeConf.myLegalName}", "visualvm.display.name" to "corda-${config.corda.myLegalName}",
"java.io.tmpdir" to System.getProperty("java.io.tmpdir"), // Inherit from parent process "java.io.tmpdir" to System.getProperty("java.io.tmpdir"), // Inherit from parent process
"log4j2.debug" to if(debugPort != null) "true" else "false" "log4j2.debug" to if(debugPort != null) "true" else "false"
) )
@ -643,18 +734,18 @@ class DriverDSLImpl(
"io.github**;io.netty**;jdk**;joptsimple**;junit**;kotlin**;net.bytebuddy**;net.i2p**;org.apache**;" + "io.github**;io.netty**;jdk**;joptsimple**;junit**;kotlin**;net.bytebuddy**;net.i2p**;org.apache**;" +
"org.assertj**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;" + "org.assertj**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;" +
"org.hamcrest**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.junit**;org.mockito**;org.objectweb**;" + "org.hamcrest**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.junit**;org.mockito**;org.objectweb**;" +
"org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**)" "org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**;org.jolokia**;)"
val extraJvmArguments = systemProperties.removeResolvedClasspath().map { "-D${it.key}=${it.value}" } + val extraJvmArguments = systemProperties.removeResolvedClasspath().map { "-D${it.key}=${it.value}" } +
"-javaagent:$quasarJarPath=$excludePattern" "-javaagent:$quasarJarPath=$excludePattern"
val jolokiaAgent = monitorPort?.let { "-javaagent:$jolokiaJarPath=port=$monitorPort,host=localhost" } val jolokiaAgent = monitorPort?.let { "-javaagent:$jolokiaJarPath=port=$monitorPort,host=localhost" }
val loggingLevel = if (debugPort == null) "INFO" else "DEBUG" val loggingLevel = if (debugPort == null) "INFO" else "DEBUG"
val arguments = mutableListOf( val arguments = mutableListOf(
"--base-directory=${nodeConf.baseDirectory}", "--base-directory=${config.corda.baseDirectory}",
"--logging-level=$loggingLevel", "--logging-level=$loggingLevel",
"--no-local-shell").also { "--no-local-shell").also {
if (initialRegistration) { if (extraCmdLineFlag != null) {
it += "--initial-registration" it += extraCmdLineFlag
} }
}.toList() }.toList()
@ -663,8 +754,8 @@ class DriverDSLImpl(
arguments = arguments, arguments = arguments,
jdwpPort = debugPort, jdwpPort = debugPort,
extraJvmArguments = extraJvmArguments + listOfNotNull(jolokiaAgent), extraJvmArguments = extraJvmArguments + listOfNotNull(jolokiaAgent),
errorLogPath = nodeConf.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME / "error.log", errorLogPath = config.corda.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME / "error.log",
workingDirectory = nodeConf.baseDirectory, workingDirectory = config.corda.baseDirectory,
maximumHeapSize = maximumHeapSize maximumHeapSize = maximumHeapSize
) )
} }
@ -831,11 +922,16 @@ fun <DI : DriverDSL, D : InternalDriverDSL, A> genericDriver(
} }
/** /**
* Internal API to enable testing of the network map service and node registration process using the internal driver.
* @property url The base CZ URL for registration and network map updates * @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 * @property publishNotaries Hook for a network map server to capture the generated [NotaryInfo] objects needed for
* to be rooted at this cert. * creating the network parameters. This is needed as the network map server is expected to distribute it. The callback
* will occur on a different thread to the driver-calling thread.
* @property rootCert If specified then the nodes will register themselves with the doorman service using [url] and expect
* the registration response to be rooted at this cert. If not specified then no registration is performed and the dev
* root cert is used as normal.
*/ */
data class CompatibilityZoneParams(val url: URL, val rootCert: X509Certificate? = null) data class CompatibilityZoneParams(val url: URL, val publishNotaries: (List<NotaryInfo>) -> Unit, val rootCert: X509Certificate? = null)
fun <A> internalDriver( fun <A> internalDriver(
isDebug: Boolean = DriverParameters().isDebug, isDebug: Boolean = DriverParameters().isDebug,
@ -875,8 +971,7 @@ fun <A> internalDriver(
} }
fun getTimestampAsDirectoryName(): String { fun getTimestampAsDirectoryName(): String {
// Add a random number in case 2 tests are started in the same instant. return DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss.SSS").withZone(UTC).format(Instant.now())
return DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(ZoneOffset.UTC).format(Instant.now()) + random63BitValue()
} }
fun writeConfig(path: Path, filename: String, config: Config) { fun writeConfig(path: Path, filename: String, config: Config) {

View File

@ -1,22 +1,22 @@
package net.corda.testing.node.internal.network package net.corda.testing.node.internal.network
import net.corda.core.crypto.* import net.corda.core.crypto.*
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.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.network.DigitalSignatureWithCert import net.corda.nodeapi.internal.network.DigitalSignatureWithCert
import net.corda.nodeapi.internal.network.NetworkMap import net.corda.nodeapi.internal.network.NetworkMap
import net.corda.nodeapi.internal.network.NetworkParameters import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.network.SignedNetworkMap import net.corda.nodeapi.internal.network.SignedNetworkMap
import net.corda.nodeapi.internal.*
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.testing.ROOT_CA import net.corda.testing.ROOT_CA
import org.bouncycastle.asn1.x500.X500Name
import org.eclipse.jetty.server.Server import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.ServerConnector import org.eclipse.jetty.server.ServerConnector
import org.eclipse.jetty.server.handler.HandlerCollection import org.eclipse.jetty.server.handler.HandlerCollection
@ -36,26 +36,36 @@ import javax.ws.rs.core.Response.ok
class NetworkMapServer(cacheTimeout: Duration, class NetworkMapServer(cacheTimeout: Duration,
hostAndPort: NetworkHostAndPort, hostAndPort: NetworkHostAndPort,
root_ca: CertificateAndKeyPair = ROOT_CA, // Default to ROOT_CA for testing. rootCa: CertificateAndKeyPair = ROOT_CA, // Default to ROOT_CA for testing.
private val myHostNameValue: String = "test.host.name",
vararg additionalServices: Any) : Closeable { vararg additionalServices: Any) : Closeable {
companion object { companion object {
val stubNetworkParameter = NetworkParameters(1, emptyList(), 10485760, 40000, Instant.now(), 10) private val stubNetworkParameters = NetworkParameters(1, emptyList(), 10485760, 40000, Instant.now(), 10)
private val serializedParameters = stubNetworkParameter.serialize()
private fun networkMapKeyAndCert(rootCAKeyAndCert: CertificateAndKeyPair): CertificateAndKeyPair { private fun networkMapKeyAndCert(rootCAKeyAndCert: CertificateAndKeyPair): CertificateAndKeyPair {
val networkMapKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val networkMapKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val networkMapCert = X509Utilities.createCertificate( val networkMapCert = X509Utilities.createCertificate(
CertificateType.INTERMEDIATE_CA, CertificateType.NETWORK_MAP,
rootCAKeyAndCert.certificate, rootCAKeyAndCert.certificate,
rootCAKeyAndCert.keyPair, rootCAKeyAndCert.keyPair,
X500Name("CN=Corda Network Map,L=London"), CordaX500Name("Corda Network Map", "R3 Ltd", "London","GB"),
networkMapKey.public).cert networkMapKey.public).cert
// Check that the certificate validates. Nodes will perform this check upon receiving a network map,
// it's better to fail here than there.
X509Utilities.validateCertificateChain(rootCAKeyAndCert.certificate.cert, networkMapCert)
return CertificateAndKeyPair(networkMapCert.toX509CertHolder(), networkMapKey) return CertificateAndKeyPair(networkMapCert.toX509CertHolder(), networkMapKey)
} }
} }
private val server: Server private val server: Server
private val service = InMemoryNetworkMapService(cacheTimeout, networkMapKeyAndCert(root_ca)) var networkParameters: NetworkParameters = stubNetworkParameters
set(networkParameters) {
check(field == stubNetworkParameters) { "Network parameters can be set only once" }
field = networkParameters
}
private val serializedParameters get() = networkParameters.serialize()
private val service = InMemoryNetworkMapService(cacheTimeout, networkMapKeyAndCert(rootCa))
init { init {
server = Server(InetSocketAddress(hostAndPort.host, hostAndPort.port)).apply { server = Server(InetSocketAddress(hostAndPort.host, hostAndPort.port)).apply {
@ -95,13 +105,13 @@ class NetworkMapServer(cacheTimeout: Duration,
} }
@Path("network-map") @Path("network-map")
class InMemoryNetworkMapService(private val cacheTimeout: Duration, private val networkMapKeyAndCert: CertificateAndKeyPair) { inner class InMemoryNetworkMapService(private val cacheTimeout: Duration,
private val networkMapKeyAndCert: CertificateAndKeyPair) {
private val nodeInfoMap = mutableMapOf<SecureHash, SignedNodeInfo>() private val nodeInfoMap = mutableMapOf<SecureHash, SignedNodeInfo>()
private val parametersHash = serializedParameters.hash private val parametersHash by lazy { serializedParameters.hash }
private val signedParameters = SignedData( private val signedParameters by lazy { SignedData(
serializedParameters, serializedParameters,
DigitalSignature.WithKey(networkMapKeyAndCert.keyPair.public, Crypto.doSign(networkMapKeyAndCert.keyPair.private, serializedParameters.bytes)) DigitalSignature.WithKey(networkMapKeyAndCert.keyPair.public, Crypto.doSign(networkMapKeyAndCert.keyPair.private, serializedParameters.bytes))) }
)
@POST @POST
@Path("publish") @Path("publish")
@ -151,7 +161,7 @@ class NetworkMapServer(cacheTimeout: Duration,
@GET @GET
@Path("my-hostname") @Path("my-hostname")
fun getHostName(): Response { fun getHostName(): Response {
return Response.ok("test.host.name").build() return Response.ok(myHostNameValue).build()
} }
} }
} }

View File

@ -5,27 +5,24 @@ package net.corda.testing
import net.corda.core.contracts.PartyAndReference import net.corda.core.contracts.PartyAndReference
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.crypto.* import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.entropyToKeyPair
import net.corda.core.crypto.generateKeyPair
import net.corda.core.crypto.toStringShort
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.cert import net.corda.core.internal.cert
import net.corda.core.internal.unspecifiedCountry import net.corda.core.internal.unspecifiedCountry
import net.corda.core.internal.x500Name
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.services.config.configureDevKeyAndTrustStores import net.corda.nodeapi.internal.createDevNodeCa
import net.corda.nodeapi.internal.config.SSLConfiguration
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
import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralSubtree
import org.bouncycastle.asn1.x509.NameConstraints
import org.bouncycastle.cert.X509CertificateHolder import org.bouncycastle.cert.X509CertificateHolder
import java.math.BigInteger import java.math.BigInteger
import java.nio.file.Files
import java.security.KeyPair import java.security.KeyPair
import java.security.PublicKey import java.security.PublicKey
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
@ -78,29 +75,11 @@ fun getFreeLocalPorts(hostName: String, numberToAlloc: Int): List<NetworkHostAnd
return (0 until numberToAlloc).map { NetworkHostAndPort(hostName, freePort + it) } return (0 until numberToAlloc).map { NetworkHostAndPort(hostName, freePort + it) }
} }
fun configureTestSSL(legalName: CordaX500Name): SSLConfiguration = object : SSLConfiguration {
override val certificatesDirectory = Files.createTempDirectory("certs")
override val keyStorePassword: String get() = "cordacadevpass"
override val trustStorePassword: String get() = "trustpass"
init {
configureDevKeyAndTrustStores(legalName)
}
}
fun getTestPartyAndCertificate(party: Party): PartyAndCertificate { fun getTestPartyAndCertificate(party: Party): PartyAndCertificate {
val trustRoot: X509CertificateHolder = DEV_TRUST_ROOT val trustRoot: X509CertificateHolder = DEV_TRUST_ROOT
val intermediate: CertificateAndKeyPair = DEV_CA val intermediate: CertificateAndKeyPair = DEV_CA
val (nodeCaCert, nodeCaKeyPair) = createDevNodeCa(intermediate, party.name)
val nodeCaKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val nodeCaCert = X509Utilities.createCertificate(
CertificateType.NODE_CA,
intermediate.certificate,
intermediate.keyPair,
party.name,
nodeCaKeyPair.public,
nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, party.name.x500Name))), arrayOf()))
val identityCert = X509Utilities.createCertificate( val identityCert = X509Utilities.createCertificate(
CertificateType.LEGAL_IDENTITY, CertificateType.LEGAL_IDENTITY,

View File

@ -1,10 +1,19 @@
package net.corda.testing.internal package net.corda.testing.internal
import net.corda.core.crypto.Crypto
import net.corda.core.identity.CordaX500Name
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.node.services.config.configureDevKeyAndTrustStores
import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.createDevNodeCa
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.serialization.amqp.AMQP_ENABLED import net.corda.nodeapi.internal.serialization.amqp.AMQP_ENABLED
import org.mockito.Mockito import org.mockito.Mockito
import org.mockito.internal.stubbing.answers.ThrowsException import org.mockito.internal.stubbing.answers.ThrowsException
import java.lang.reflect.Modifier import java.lang.reflect.Modifier
import java.nio.file.Files
import java.util.* import java.util.*
@Suppress("unused") @Suppress("unused")
@ -42,3 +51,56 @@ fun <T> rigorousMock(clazz: Class<T>): T = Mockito.mock(clazz) {
it.callRealMethod() it.callRealMethod()
} }
} }
fun configureTestSSL(legalName: CordaX500Name): SSLConfiguration {
return object : SSLConfiguration {
override val certificatesDirectory = Files.createTempDirectory("certs")
override val keyStorePassword: String get() = "cordacadevpass"
override val trustStorePassword: String get() = "trustpass"
init {
configureDevKeyAndTrustStores(legalName)
}
}
}
private val defaultRootCaName = CordaX500Name("Corda Root CA", "R3 Ltd", "London", "GB")
private val defaultIntermediateCaName = CordaX500Name("Corda Intermediate CA", "R3 Ltd", "London", "GB")
/**
* Returns a pair of [CertificateAndKeyPair]s, the first being the root CA and the second the intermediate CA.
* @param rootCaName The subject name for the root CA cert.
* @param intermediateCaName The subject name for the intermediate CA cert.
*/
fun createDevIntermediateCaCertPath(
rootCaName: CordaX500Name = defaultRootCaName,
intermediateCaName: CordaX500Name = defaultIntermediateCaName
): Pair<CertificateAndKeyPair, CertificateAndKeyPair> {
val rootKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val rootCert = X509Utilities.createSelfSignedCACertificate(rootCaName, rootKeyPair)
val intermediateCaKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val intermediateCaCert = X509Utilities.createCertificate(
CertificateType.INTERMEDIATE_CA,
rootCert,
rootKeyPair,
intermediateCaName,
intermediateCaKeyPair.public)
return Pair(CertificateAndKeyPair(rootCert, rootKeyPair), CertificateAndKeyPair(intermediateCaCert, intermediateCaKeyPair))
}
/**
* Returns a triple of [CertificateAndKeyPair]s, the first being the root CA, the second the intermediate CA and the third
* the node CA.
* @param legalName The subject name for the node CA cert.
*/
fun createDevNodeCaCertPath(
legalName: CordaX500Name,
rootCaName: CordaX500Name = defaultRootCaName,
intermediateCaName: CordaX500Name = defaultIntermediateCaName
): Triple<CertificateAndKeyPair, CertificateAndKeyPair, CertificateAndKeyPair> {
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath(rootCaName, intermediateCaName)
val nodeCa = createDevNodeCa(intermediateCa, legalName)
return Triple(rootCa, intermediateCa, nodeCa)
}

View File

@ -6,7 +6,10 @@ configurations {
runtimeArtifacts runtimeArtifacts
} }
// TODO Fix SLF4J warnings that occur when running the bootstrapper dependencies {
compile "org.slf4j:slf4j-nop:$slf4j_version"
}
task buildBootstrapperJar(type: FatCapsule, dependsOn: project(':node-api').compileJava) { task buildBootstrapperJar(type: FatCapsule, dependsOn: project(':node-api').compileJava) {
applicationClass 'net.corda.nodeapi.internal.network.NetworkBootstrapper' applicationClass 'net.corda.nodeapi.internal.network.NetworkBootstrapper'
archiveName "network-bootstrapper.jar" archiveName "network-bootstrapper.jar"
@ -16,6 +19,9 @@ task buildBootstrapperJar(type: FatCapsule, dependsOn: project(':node-api').comp
minJavaVersion = '1.8.0' minJavaVersion = '1.8.0'
jvmArgs = ['-XX:+UseG1GC'] jvmArgs = ['-XX:+UseG1GC']
} }
from(project(':node:capsule').tasks['buildCordaJAR']) {
rename 'corda-(.*)', 'corda.jar'
}
applicationSource = files( applicationSource = files(
project(':node-api').configurations.runtime, project(':node-api').configurations.runtime,
project(':node-api').jar project(':node-api').jar

View File

@ -13,7 +13,7 @@ import net.corda.demobench.pty.R3Pty
import net.corda.nodeapi.internal.network.NetworkParameters import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.nodeapi.internal.network.NotaryInfo import net.corda.nodeapi.internal.network.NotaryInfo
import net.corda.nodeapi.internal.IdentityGenerator import net.corda.nodeapi.internal.DevIdentityGenerator
import tornadofx.* import tornadofx.*
import java.io.IOException import java.io.IOException
import java.lang.management.ManagementFactory import java.lang.management.ManagementFactory
@ -153,7 +153,7 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() {
// Generate notary identity and save it into node's directory. This identity will be used in network parameters. // Generate notary identity and save it into node's directory. This identity will be used in network parameters.
private fun getNotaryIdentity(config: NodeConfigWrapper): Party { private fun getNotaryIdentity(config: NodeConfigWrapper): Party {
return IdentityGenerator.generateNodeIdentity(config.nodeDir, config.nodeConfig.myLegalName) return DevIdentityGenerator.installKeyStoreWithNodeIdentity(config.nodeDir, config.nodeConfig.myLegalName)
} }
fun reset() { fun reset() {