diff --git a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt index cd153a18c0..e736628215 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt @@ -110,7 +110,11 @@ object Crypto { "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 val EDDSA_ED25519_SHA512 = SignatureScheme( 4, diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowLogicRef.kt b/core/src/main/kotlin/net/corda/core/flows/FlowLogicRef.kt index a3043709d0..4768ff9259 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowLogicRef.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowLogicRef.kt @@ -11,6 +11,8 @@ import net.corda.core.serialization.CordaSerializable @DoNotImplement interface FlowLogicRefFactory { fun create(flowClass: Class>, vararg args: Any?): FlowLogicRef + fun createForRPC(flowClass: Class>, vararg args: Any?): FlowLogicRef + fun toFlowLogic(ref: FlowLogicRef): FlowLogic<*> } @CordaSerializable diff --git a/core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt b/core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt index f45fc377fb..d10944bdb8 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt @@ -27,8 +27,8 @@ import kotlin.test.* */ class CryptoUtilsTest { - val testString = "Hello World" - val testBytes = testString.toByteArray() + private val testString = "Hello World" + private val testBytes = testString.toByteArray() // key generation test @Test diff --git a/core/src/test/kotlin/net/corda/core/crypto/EdDSATests.kt b/core/src/test/kotlin/net/corda/core/crypto/EdDSATests.kt new file mode 100644 index 0000000000..2023cc76e5 --- /dev/null +++ b/core/src/test/kotlin/net/corda/core/crypto/EdDSATests.kt @@ -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() + } +} diff --git a/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt b/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt index e135d1c93c..707a32bba4 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt @@ -1,9 +1,9 @@ package net.corda.core.crypto import net.corda.core.identity.CordaX500Name -import net.corda.core.internal.toTypedArray import net.corda.core.internal.cert import net.corda.nodeapi.internal.crypto.* +import net.corda.testing.internal.createDevIntermediateCaCertPath import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x509.GeneralName import org.bouncycastle.asn1.x509.GeneralSubtree @@ -11,35 +11,40 @@ import org.bouncycastle.asn1.x509.NameConstraints import org.bouncycastle.jce.provider.BouncyCastleProvider import org.junit.Test import java.security.KeyStore -import java.security.cert.* -import java.util.stream.Stream +import java.security.cert.CertPathValidator +import java.security.cert.CertPathValidatorException +import java.security.cert.PKIXParameters import kotlin.test.assertFailsWith import kotlin.test.assertTrue class X509NameConstraintsTest { private fun makeKeyStores(subjectName: X500Name, nameConstraints: NameConstraints): Pair { - val rootKeys = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Corda Root CA", organisation = "R3 Ltd", locality = "London", country = "GB"), rootKeys) - - val intermediateCAKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootKeys, CordaX500Name(commonName = "Corda Intermediate CA", organisation = "R3 Ltd", locality = "London", country = "GB"), intermediateCAKeyPair.public) - - val clientCAKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - 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) + val (rootCa, intermediateCa) = createDevIntermediateCaCertPath() + val nodeCaKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val nodeCaCert = X509Utilities.createCertificate( + CertificateType.NODE_CA, + intermediateCa.certificate, + intermediateCa.keyPair, + CordaX500Name("Corda Client CA", "R3 Ltd", "London", "GB"), + nodeCaKeyPair.public, + nameConstraints = nameConstraints) val keyPass = "password" val trustStore = KeyStore.getInstance(KEYSTORE_TYPE) 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 tlsCert = X509Utilities.createCertificate(CertificateType.TLS, clientCACert, clientCAKeyPair, subjectName, tlsKey.public) + val tlsKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, nodeCaCert, nodeCaKeyPair, subjectName, tlsKeyPair.public) val keyStore = KeyStore.getInstance(KEYSTORE_TYPE) keyStore.load(null, keyPass.toCharArray()) - keyStore.addOrReplaceKey(X509Utilities.CORDA_CLIENT_TLS, tlsKey.private, keyPass.toCharArray(), - Stream.of(tlsCert, clientCACert, intermediateCACert, rootCACert).map { it.cert }.toTypedArray()) + keyStore.addOrReplaceKey( + X509Utilities.CORDA_CLIENT_TLS, + tlsKeyPair.private, + keyPass.toCharArray(), + arrayOf(tlsCert, nodeCaCert, intermediateCa.certificate, rootCa.certificate)) return Pair(keyStore, trustStore) } diff --git a/docs/source/cordapp-build-systems.rst b/docs/source/cordapp-build-systems.rst index f2561429c0..5cba71b9af 100644 --- a/docs/source/cordapp-build-systems.rst +++ b/docs/source/cordapp-build-systems.rst @@ -28,18 +28,16 @@ Setting your dependencies 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 ext.corda_release_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. ``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 ^^^^^^^^^^^^^^^^^^ -The ``cordformation`` plugin adds: +The ``cordformation`` plugin adds two new gradle configurations: -* ``cordaCompile`` as a new configuration that ``compile`` extends from -* ``cordaRuntime`` which ``runtime`` extends from +* ``cordaCompile``, which extends ``compile`` +* ``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:`` JAR as a ``cordaRuntime`` dependency -* Each compile dependency (eg ``corda-core``) as a ``cordaCompile`` dependency +* ``net.corda:corda:$corda_release_version`` as a ``cordaRuntime`` 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:`` as a ``testCompile`` dependency -(i.e. a default Java/Kotlin test compile task). +You may also want to add: + +* ``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. Dependencies on other CorDapps ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Sometimes, a CorDapp you build will depend on states, contracts or flows defined in another CorDapp. You must include -the CorDapp your CorDapp depends upon as a ``cordapp`` dependency in your ``build.gradle`` file. +You CorDapp may also depend on classes defined in another CorDapp, such as states, contracts and flows. There are two +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 ^^^^^^^^^^^^^^^^^^ diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/twoparty/flow.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/twoparty/flow.kt index 27f4705501..dc4825c1b1 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/twoparty/flow.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/twoparty/flow.kt @@ -10,7 +10,6 @@ import net.corda.core.messaging.CordaRPCOps import net.corda.core.serialization.SerializationWhitelist import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.ProgressTracker -import net.corda.webserver.services.WebServerPluginRegistry import java.util.function.Function import javax.ws.rs.GET import javax.ws.rs.Path diff --git a/docs/source/setting-up-a-corda-network.rst b/docs/source/setting-up-a-corda-network.rst index 4ae763dce2..1f1e65a966 100644 --- a/docs/source/setting-up-a-corda-network.rst +++ b/docs/source/setting-up-a-corda-network.rst @@ -67,10 +67,21 @@ The bootstrapper tool can be built with the command: 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 `` +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 ~~~~~~~~~~~~~~~~~~ diff --git a/docs/source/shell.rst b/docs/source/shell.rst index d993c5e3bf..86833f002b 100644 --- a/docs/source/shell.rst +++ b/docs/source/shell.rst @@ -7,25 +7,32 @@ Shell ===== -The Corda shell is an embedded command line that allows an administrator to control and monitor the node. -Some of its features include: +.. contents:: -* Invoking any of the RPCs the node exposes to applications. -* Starting flows. -* 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. +The Corda shell is an embedded command line that allows an administrator to control and monitor a node. It is based on +the `CRaSH`_ shell and supports many of the same features. These features include: -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 @@ -33,80 +40,159 @@ Shell can also be accessible via SSH. By default SSH server is *disabled*. To en port = 2222 } -Authentication and authorization --------------------------------- -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 -with the node using RPC calls. This also means that RPC permissions are enforced. No permissions are required to allow the connection -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. +Authentication +************** +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. No RPC permissions are required to allow the connection and log in. -Host key --------- +The host key is loaded from the ``/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 -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 to the shell +*********************** -Connecting ----------- +Linux and MacOS +^^^^^^^^^^^^^^^ -Linux and MacOS computers usually come with SSH client preinstalled. On Windows it usually requires extra download. -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. +Run the following command from the terminal: -:note: While developing, checking multiple samples or simply restarting a node frequently host key may be regenerated. SSH usually - 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! +.. code:: bash -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 -``--help`` or ``-h`` flags to a command to get info about what switches it supports. +* ``[portNumber]`` is the port number specified in the ``node.conf`` file +* ``[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 -list the supported subcommands. +The RPC password will be requested after a connection is established. -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`" -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. +Windows +^^^^^^^ -**RPCs** (remote procedure calls) are commands that can be sent to the node to query it, control it and manage it. -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. +Windows does not provide a built-in SSH tool. An alternative such as PuTTY should be used. -.. raw:: html +Permissions +*********** -
Documentation of available RPCs

+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 -constructor. Because parameters can be any arbitrary Java object graph, we need a convenient syntax to express -this sort of data. The shell uses a syntax called `Yaml`_ to do this. +* Watching flows (``flow watch``) requires ``InvokeRpc.stateMachinesFeed`` +* Starting flows requires ``InvokeRpc.startTrackedFlowDynamic`` and ``InvokeRpc.registeredFlows``, as well as a + 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 -that make it helpful for our use case, like a lightweight syntax and support for "bare words" which mean you can -often skip the quotes around strings. Here is an example of how this syntax is used: +The shell interacts with the node by issuing RPCs (remote procedure calls). You make an RPC from the shell by typing +``run`` followed by the name of the desired RPC method. For example, you'd see a list of the registered flows on your +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 `_. + +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`` 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"`` -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 @@ -117,50 +203,44 @@ This invokes a constructor of a flow with the following prototype in the code: val recipient: Party, val notary: Party) : AbstractCashFlow(progressTracker) -Here, everything after ``CashIssue`` is specifying the arguments to the constructor of a flow. In Yaml, an object -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: +Querying the vault +~~~~~~~~~~~~~~~~~~ -* When a parameter is of type ``Amount`` you can write it as either one of the dollar symbol ($), - 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. +We would query the vault for ``IOUState`` states as follows: -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 -parser is defined for the type you need, for instance, if an API requires a ``Pair`` -which could be represented as ``{ first: foo, second: 123 }``. +This breaks down as follows: -.. note:: If your CorDapp is written in Java, - named arguments won't work unless you compiled using the ``-parameters`` argument to javac. - See :doc:`generating-a-node` for how to specify it via Gradle. - -The same syntax is also used to specify the parameters for RPCs, accessed via the ``run`` command, like this: - -``run registeredFlows`` +* ``run`` is a shell command for making an RPC call +* ``vaultQuery`` is the RPC call we want to make +* ``contractStateType: com.template.IOUState`` is the fully-qualified name of the state type we are querying for Attachments ------------ +*********** -The shell can be used to upload and download attachments from the node interactively. To learn more, see -the tutorial ":doc:`tutorial-attachments`". +The shell can be used to upload and download attachments from the node. To learn more, see the tutorial +":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 ------------------- -The shell can be extended using commands written in either Java or `Groovy`_ (Groovy is a scripting language that -is Java compatible). Such commands have full access to the node internal APIs and thus can be used to achieve -almost anything. +The shell can be extended using commands written in either Java or `Groovy`_ (a Java-compatible scripting language). +These commands have full access to the node's internal APIs and thus can be used to achieve almost anything. -A full tutorial on how to write such commands is out of scope for this documentation, to learn more please -refer to the `CRaSH`_ documentation. New commands can be placed in the ``shell-commands`` subdirectory in the -node directory. Edits to existing commands will be used automatically, but at this time commands added after the -node has started won't be automatically detected. Commands should be named in all lower case with either a -``.java`` or ``.groovy`` extension. +A full tutorial on how to write such commands is out of scope for this documentation. To learn more, please refer to +the `CRaSH`_ documentation. New commands are placed in the ``shell-commands`` subdirectory in the node directory. Edits +to existing commands will be used automatically, but currently commands added after the node has started won't be +automatically detected. Commands must have names all in lower-case with either a ``.java`` or ``.groovy`` extension. .. 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 @@ -171,14 +251,13 @@ Limitations 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. -* Command history is not preserved across restarts. -* 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. -* The ``jul`` command advertises access to logs, but it doesn't work with the logging framework we're using. +* There is no command completion for flows or RPCs +* Command history is not preserved across restarts +* 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 +* 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 -.. _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/ .. _CRaSH: http://www.crashub.org/ diff --git a/docs/source/tutorial-observer-nodes.rst b/docs/source/tutorial-observer-nodes.rst index ecb5f04089..52c7a5e67c 100644 --- a/docs/source/tutorial-observer-nodes.rst +++ b/docs/source/tutorial-observer-nodes.rst @@ -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 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 - 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 - identities. \ No newline at end of file +Caveats +------- + +* Nodes which act as both observers and direct participants in the ledger are not supported at this 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 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 \ No newline at end of file diff --git a/experimental/quasar-hook/README.md b/experimental/quasar-hook/README.md index b0b1de0c99..829c26979c 100644 --- a/experimental/quasar-hook/README.md +++ b/experimental/quasar-hook/README.md @@ -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 samples:irs-demo:deployNodes 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. diff --git a/gradle-plugins/cordformation/src/noderunner/kotlin/net/corda/plugins/NodeRunner.kt b/gradle-plugins/cordformation/src/noderunner/kotlin/net/corda/plugins/NodeRunner.kt index c49bd847f7..bb308a8191 100644 --- a/gradle-plugins/cordformation/src/noderunner/kotlin/net/corda/plugins/NodeRunner.kt +++ b/gradle-plugins/cordformation/src/noderunner/kotlin/net/corda/plugins/NodeRunner.kt @@ -90,8 +90,12 @@ private abstract class JavaCommand( add(getJavaPath()) addAll(jvmArgs) add("-Dname=$nodeName") - null != debugPort && add("-Dcapsule.jvm.args=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=$debugPort") - null != monitoringPort && add("-Dcapsule.jvm.args=-javaagent:drivers/$jolokiaJar=port=$monitoringPort") + val jvmArgs: MutableList = mutableListOf() + 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(jarName) init() diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/SigningServiceIntegrationTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/SigningServiceIntegrationTest.kt index 7616b07d60..1c0bef94f2 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/SigningServiceIntegrationTest.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/SigningServiceIntegrationTest.kt @@ -3,14 +3,12 @@ package com.r3.corda.networkmanage.hsm import com.nhaarman.mockito_kotlin.* import com.r3.corda.networkmanage.common.persistence.configureDatabase 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.NetworkManagementServer import com.r3.corda.networkmanage.hsm.persistence.ApprovedCertificateRequestData import com.r3.corda.networkmanage.hsm.persistence.DBSignedCertificateRequestStorage import com.r3.corda.networkmanage.hsm.persistence.SignedCertificateRequestStorage import com.r3.corda.networkmanage.hsm.signer.HsmCsrSigner -import net.corda.core.crypto.Crypto import net.corda.core.identity.CordaX500Name import net.corda.core.internal.cert 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.utilities.registration.HTTPNetworkRegistrationService import net.corda.node.utilities.registration.NetworkRegistrationHelper +import net.corda.nodeapi.internal.createDevNodeCa import net.corda.nodeapi.internal.crypto.* import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.testing.ALICE_NAME import net.corda.testing.BOB_NAME import net.corda.testing.CHARLIE_NAME import net.corda.testing.SerializationEnvironmentRule +import net.corda.testing.internal.createDevIntermediateCaCertPath import net.corda.testing.internal.rigorousMock import org.bouncycastle.cert.X509CertificateHolder import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest @@ -34,7 +34,6 @@ import org.h2.tools.Server import org.junit.* import org.junit.rules.TemporaryFolder import java.net.URL -import java.security.KeyPair import java.util.* import javax.persistence.PersistenceException import kotlin.concurrent.scheduleAtFixedRate @@ -56,15 +55,15 @@ class SigningServiceIntegrationTest { val testSerialization = SerializationEnvironmentRule(true) 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 fun setUp() { timer = Timer() - rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Integration Test Corda Node Root CA", - organisation = "R3 Ltd", locality = "London", country = "GB"), rootCAKey) + val (rootCa, intermediateCa) = createDevIntermediateCaCertPath() + rootCaCert = rootCa.certificate + this.intermediateCa = intermediateCa } @After @@ -73,22 +72,17 @@ class SigningServiceIntegrationTest { } 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 return mock { on { sign(any()) }.then { - val toSign: List = uncheckedCast(it.arguments[0]) - toSign.forEach { - JcaPKCS10CertificationRequest(it.request).run { - val certificate = X509Utilities.createCertificate(CertificateType.TLS, intermediateCACert, intermediateCAKey, subject, publicKey).toX509Certificate() - it.certPath = buildCertPath(certificate, rootCACert.toX509Certificate()) + val approvedRequests: List = uncheckedCast(it.arguments[0]) + for (approvedRequest in approvedRequests) { + JcaPKCS10CertificationRequest(approvedRequest.request).run { + val nodeCa = createDevNodeCa(intermediateCa, CordaX500Name.parse(subject.toString())) + 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. } } - config.trustStoreFile.parent.createDirectories() + config.certificatesDirectory.createDirectories() 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) } NetworkRegistrationHelper(config, HTTPNetworkRegistrationService(config.compatibilityZoneURL!!)).buildKeystore() @@ -172,9 +166,9 @@ class SigningServiceIntegrationTest { }).whenever(it).myLegalName doReturn(URL("http://$HOST:${server.hostAndPort.port}")).whenever(it).compatibilityZoneURL } - config.trustStoreFile.parent.createDirectories() + config.certificatesDirectory.createDirectories() 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) } NetworkRegistrationHelper(config, HTTPNetworkRegistrationService(config.compatibilityZoneURL!!)).buildKeystore() @@ -183,7 +177,7 @@ class SigningServiceIntegrationTest { } } - fun createConfig(): NodeConfiguration { + private fun createConfig(): NodeConfiguration { return rigorousMock().also { doReturn(tempFolder.root.toPath()).whenever(it).baseDirectory doReturn(it.baseDirectory / "certificates").whenever(it).certificatesDirectory @@ -195,15 +189,15 @@ class SigningServiceIntegrationTest { 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 { - 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) \ No newline at end of file +internal fun makeNotInitialisingTestDatabaseProperties() = DatabaseConfig(runMigration = false) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt new file mode 100644 index 0000000000..ef58a930b2 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt @@ -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, 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) + } +} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/IdentityGenerator.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/IdentityGenerator.kt deleted file mode 100644 index 97927e54db..0000000000 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/IdentityGenerator.kt +++ /dev/null @@ -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, 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, - 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) - } -} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt new file mode 100644 index 0000000000..03bd39e635 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt @@ -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) +} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/SSLConfiguration.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/SSLConfiguration.kt index 90544e92a6..fb5424e19d 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/SSLConfiguration.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/SSLConfiguration.kt @@ -8,6 +8,7 @@ interface SSLConfiguration { val trustStorePassword: String val certificatesDirectory: Path val sslKeystore: Path get() = certificatesDirectory / "sslkeystore.jks" + // TODO This looks like it should be in NodeSSLConfiguration val nodeKeystore: Path get() = certificatesDirectory / "nodekeystore.jks" val trustStoreFile: Path get() = certificatesDirectory / "truststore.jks" } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/KeyStoreWrapper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/KeyStoreWrapper.kt index 211f156a8d..2504ce221b 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/KeyStoreWrapper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/KeyStoreWrapper.kt @@ -1,41 +1,26 @@ package net.corda.nodeapi.internal.crypto import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.cert import net.corda.core.internal.read import java.nio.file.Path import java.security.KeyPair -import java.security.PublicKey -import java.security.cert.CertPath import java.security.cert.Certificate class KeyStoreWrapper(private val storePath: Path, private val storePassword: String) { private val keyStore = storePath.read { loadKeyStore(it, storePassword) } - private fun createCertificate(serviceName: CordaX500Name, pubKey: PublicKey): CertPath { - val clientCertPath = keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA) + // TODO This method seems misplaced in this class. + 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. - val clientCA = certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA) - // 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.addOrReplaceKey(alias, keyPair.private, storePassword.toCharArray(), identityCertPath.certificates.toTypedArray()) keyStore.save(storePath, storePassword) + return PartyAndCertificate(identityCertPath) } // 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 certificateAndKeyPair(alias: String): CertificateAndKeyPair = keyStore.getCertificateAndKeyPair(alias, storePassword) + fun getCertificateAndKeyPair(alias: String): CertificateAndKeyPair = keyStore.getCertificateAndKeyPair(alias, storePassword) } \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt index 563ddaa9f0..78b44a8786 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt @@ -4,12 +4,8 @@ import net.corda.core.CordaOID import net.corda.core.crypto.Crypto import net.corda.core.crypto.SignatureScheme import net.corda.core.crypto.random63BitValue -import net.corda.core.internal.CertRole import net.corda.core.identity.CordaX500Name -import net.corda.core.internal.cert -import net.corda.core.internal.reader -import net.corda.core.internal.writer -import net.corda.core.internal.x500Name +import net.corda.core.internal.* import net.corda.core.utilities.days import net.corda.core.utilities.millis import org.bouncycastle.asn1.* @@ -43,6 +39,7 @@ object X509Utilities { val DEFAULT_IDENTITY_SIGNATURE_SCHEME = Crypto.EDDSA_ED25519_SHA512 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. const val CORDA_ROOT_CA = "cordarootca" const val CORDA_INTERMEDIATE_CA = "cordaintermediateca" diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt index 34ee05851a..af625adae5 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt @@ -4,23 +4,28 @@ import com.typesafe.config.ConfigFactory import net.corda.cordform.CordformNode import net.corda.core.identity.Party import net.corda.core.internal.* +import net.corda.core.internal.concurrent.fork import net.corda.core.node.NodeInfo import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.deserialize import net.corda.core.serialization.internal.SerializationEnvironmentImpl import net.corda.core.serialization.internal._contextSerializationEnv 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.serialization.AMQP_P2P_CONTEXT import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 +import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths import java.nio.file.StandardCopyOption import java.time.Instant -import java.util.concurrent.TimeUnit.SECONDS +import java.util.concurrent.Executors +import java.util.concurrent.TimeoutException import kotlin.streams.toList /** @@ -48,13 +53,14 @@ class NetworkBootstrapper { fun bootstrap(directory: Path) { directory.createDirectories() println("Bootstrapping local network in $directory") + generateDirectoriesIfNeeded(directory) val nodeDirs = directory.list { paths -> paths.filter { (it / "corda.jar").exists() }.toList() } require(nodeDirs.isNotEmpty()) { "No nodes found" } println("Nodes found in the following sub-directories: ${nodeDirs.map { it.fileName }}") val processes = startNodeInfoGeneration(nodeDirs) initialiseSerialization() 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) println("Distributing all node info-files to all nodes") 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): List { return nodeDirs.map { nodeDir -> val logsDir = (nodeDir / LOGS_DIR_NAME).createDirectories() @@ -82,15 +109,22 @@ class NetworkBootstrapper { } private fun gatherNodeInfoFiles(processes: List, nodeDirs: List): List { - val timeOutInSeconds = 60L - return processes.zip(nodeDirs).map { (process, nodeDir) -> - check(process.waitFor(timeOutInSeconds, SECONDS)) { - "Node in ${nodeDir.fileName} took longer than ${timeOutInSeconds}s to generate its node-info - see logs in ${nodeDir / LOGS_DIR_NAME}" + val executor = Executors.newSingleThreadExecutor() + + val future = executor.fork { + 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}" - } - nodeDir.list { paths -> paths.filter { it.fileName.toString().startsWith("nodeInfo-") }.findFirst().get() } + } + + return try { + 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 { 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] - // 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. + // 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. 2 -> legalIdentities[1] 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 { return byteSequence == KryoHeaderV0_1 && target == SerializationContext.UseCase.P2P } + override fun rpcClientKryoPool(context: SerializationContext) = throw UnsupportedOperationException() override fun rpcServerKryoPool(context: SerializationContext) = throw UnsupportedOperationException() } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt index 8435ff5791..47c2c8b9ef 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt @@ -12,12 +12,17 @@ import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize 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.SerializationContextImpl import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl 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.x509.BasicConstraints import org.bouncycastle.asn1.x509.Extension @@ -33,11 +38,8 @@ import java.io.IOException import java.net.InetAddress import java.net.InetSocketAddress import java.nio.file.Path -import java.security.KeyStore -import java.security.PrivateKey import java.security.SecureRandom import java.security.cert.CertPath -import java.security.cert.Certificate import java.security.cert.X509Certificate import java.util.* import java.util.stream.Stream @@ -52,6 +54,11 @@ class X509UtilitiesTest { val MEGA_CORP = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")).party val BOB get() = bob.party 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 @@ -155,107 +162,62 @@ class X509UtilitiesTest { 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 fun `create server certificate in keystore for SSL`() { - val tmpCAKeyStore = tempFile("keystore.jks") - val tmpTrustStore = tempFile("truststore.jks") - val tmpSSLKeyStore = tempFile("sslkeystore.jks") - val tmpServerKeyStore = tempFile("serverkeystore.jks") + val sslConfig = object : SSLConfiguration { + override val certificatesDirectory = tempFolder.root.toPath() + override val keyStorePassword = "serverstorepass" + override val trustStorePassword = "trustpass" + } - // Generate Root and Intermediate CA cert and put both into key store and root ca cert into trust store - 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") + val (rootCa, intermediateCa) = createDevIntermediateCaCertPath() // 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 - val serverKeyStore = loadKeyStore(tmpServerKeyStore, "serverstorepass") - val serverCertAndKey = serverKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, "serverkeypass") + val serverKeyStore = loadKeyStore(sslConfig.nodeKeystore, sslConfig.keyStorePassword) + val (serverCert, serverKeyPair) = serverKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, sslConfig.keyStorePassword) - serverCertAndKey.certificate.isValidOn(Date()) - serverCertAndKey.certificate.isSignatureValid(JcaContentVerifierProviderBuilder().build(caCertAndKey.certificate.subjectPublicKeyInfo)) + serverCert.cert.checkValidity() + 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 - val sslKeyStore = loadKeyStore(tmpSSLKeyStore, "serverstorepass") - val sslCertAndKey = sslKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_TLS, "serverkeypass") + sslCert.cert.checkValidity() + sslCert.cert.verify(serverCert.cert.publicKey) + 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 val testData = "123456".toByteArray() - val signature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.keyPair.private, testData) - val publicKey = Crypto.toSupportedPublicKey(serverCertAndKey.certificate.subjectPublicKeyInfo) + val signature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, serverKeyPair.private, testData) + val publicKey = Crypto.toSupportedPublicKey(serverCert.subjectPublicKeyInfo) assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, publicKey, signature, testData) } } @Test fun `create server cert and use in SSL socket`() { - val tmpCAKeyStore = tempFile("keystore.jks") - val tmpTrustStore = tempFile("truststore.jks") - val tmpSSLKeyStore = tempFile("sslkeystore.jks") - val tmpServerKeyStore = tempFile("serverkeystore.jks") + val sslConfig = object : SSLConfiguration { + override val certificatesDirectory = tempFolder.root.toPath() + override val keyStorePassword = "serverstorepass" + override val trustStorePassword = "trustpass" + } - // Generate Root and Intermediate CA cert and put both into key store and root ca cert into trust store - val caKeyStore = createCAKeyStoreAndTrustStore(tmpCAKeyStore, - "cakeystorepass", - "cakeypass", - tmpTrustStore, - "trustpass") + val (rootCa, intermediateCa) = createDevIntermediateCaCertPath() // Generate server cert and private key and populate another keystore suitable for SSL - createKeystoreForCordaNode(tmpSSLKeyStore, tmpServerKeyStore, "serverstorepass", "serverstorepass", caKeyStore, "cakeypass", MEGA_CORP.name) - val keyStore = loadKeyStore(tmpSSLKeyStore, "serverstorepass") - val trustStore = loadKeyStore(tmpTrustStore, "trustpass") + sslConfig.createDevKeyStores(rootCa.certificate, intermediateCa, MEGA_CORP.name) + sslConfig.createTrustStore(rootCa.certificate.cert) + + val keyStore = loadKeyStore(sslConfig.sslKeystore, sslConfig.keyStorePassword) + val trustStore = loadKeyStore(sslConfig.trustStoreFile, sslConfig.trustStorePassword) val context = SSLContext.getInstance("TLS") val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) - keyManagerFactory.init(keyStore, "serverstorepass".toCharArray()) + keyManagerFactory.init(keyStore, sslConfig.keyStorePassword.toCharArray()) val keyManagers = keyManagerFactory.keyManagers val trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) trustMgrFactory.init(trustStore) @@ -266,13 +228,7 @@ class X509UtilitiesTest { val clientSocketFactory = context.socketFactory 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", - "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")) + val serverParams = SSLParameters(CIPHER_SUITES, arrayOf("TLSv1.2")) serverParams.wantClientAuth = true serverParams.needClientAuth = true serverParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator. @@ -280,13 +236,7 @@ class X509UtilitiesTest { serverSocket.useClientMode = false val clientSocket = clientSocketFactory.createSocket() as SSLSocket - val clientParams = SSLParameters(arrayOf("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", - "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")) + val clientParams = SSLParameters(CIPHER_SUITES, arrayOf("TLSv1.2")) clientParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator. clientSocket.sslParameters = clientParams clientSocket.useClientMode = true @@ -344,60 +294,14 @@ class X509UtilitiesTest { private fun tempFile(name: String): Path = tempFolder.root.toPath() / name - /** - * All in one wrapper to manufacture a root CA cert and an Intermediate CA cert. - * Normally this would be run once and then the outputs would be re-used repeatedly to manufacture the server certs - * @param keyStoreFilePath The output KeyStore path to publish the private keys of the CA root and intermediate certs into. - * @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(rootCACert.cert)) - - keyStore.addOrReplaceKey(X509Utilities.CORDA_INTERMEDIATE_CA, - intermediateCAKeyPair.private, - keyPass, - Stream.of(intermediateCACert, rootCACert).map { it.cert }.toTypedArray()) - - 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 + private fun SSLConfiguration.createTrustStore(rootCert: X509Certificate) { + val trustStore = loadOrCreateKeyStore(trustStoreFile, trustStorePassword) + trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCert) + trustStore.save(trustStoreFile, trustStorePassword) } @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 testName = CordaX500Name(commonName = "Test", organisation = "R3 Ltd", locality = "London", country = "GB") val selfSignCert = X509Utilities.createSelfSignedCACertificate(testName, keyPair) diff --git a/node/capsule/build.gradle b/node/capsule/build.gradle index d5c8e7906b..17ad12f9f4 100644 --- a/node/capsule/build.gradle +++ b/node/capsule/build.gradle @@ -44,7 +44,7 @@ task buildCordaJAR(type: FatCapsule, dependsOn: project(':node').compileJava) { applicationVersion = corda_release_version appClassPath = ["jolokia-war-${project.rootProject.ext.jolokia_version}.war"] // 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}"] systemProperties['visualvm.display.name'] = 'CordaEnterprise' minJavaVersion = '1.8.0' diff --git a/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt b/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt index 81a0ea1c9c..c8bd587e37 100644 --- a/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt @@ -14,11 +14,9 @@ import net.corda.testing.IntegrationTestSchemas import net.corda.testing.driver.driver import net.corda.testing.toDatabaseSchemaName import org.junit.ClassRule +import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Test import java.nio.file.Path -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertTrue class NodeKeystoreCheckTest : IntegrationTest() { companion object { @@ -26,16 +24,18 @@ class NodeKeystoreCheckTest : IntegrationTest() { 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 fun `node should throw exception if cert path doesn't chain to the trust root`() { 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 val keystorePassword = "password" val config = object : SSLConfiguration { @@ -46,9 +46,12 @@ class NodeKeystoreCheckTest : IntegrationTest() { config.configureDevKeyAndTrustStores(ALICE_NAME) // This should pass with correct keystore. - val node = startNode(providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false, - "keyStorePassword" to keystorePassword, - "trustStorePassword" to keystorePassword)).get() + val node = startNode( + providedName = ALICE_NAME, + customOverrides = mapOf("devMode" to false, + "keyStorePassword" to keystorePassword, + "trustStorePassword" to keystorePassword) + ).getOrThrow() node.stop() // 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.save(config.nodeKeystore, config.keyStorePassword) - assertFailsWith(IllegalArgumentException::class) { + assertThatThrownBy { startNode(providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false)).getOrThrow() - }.apply { - assertEquals("Client CA certificate must chain to the trusted root.", message) - } + }.hasMessage("Client CA certificate must chain to the trusted root.") } } } diff --git a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt index 0577bff135..ea05e0c223 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt @@ -23,7 +23,7 @@ import net.corda.node.services.config.BFTSMaRtConfiguration import net.corda.node.services.config.NotaryConfig import net.corda.node.services.transactions.minClusterSize 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.NotaryInfo 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? val replicaIds = (0 until clusterSize) - notary = IdentityGenerator.generateDistributedNotaryIdentity( + notary = DevIdentityGenerator.generateDistributedNotaryIdentity( replicaIds.map { mockNet.baseDirectory(mockNet.nextNodeId + it) }, CordaX500Name("BFT", "Zurich", "CH")) diff --git a/node/src/integration-test/kotlin/net/corda/node/services/MySQLNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/MySQLNotaryServiceTests.kt index c727e42000..832514e770 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/MySQLNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/MySQLNotaryServiceTests.kt @@ -14,7 +14,7 @@ import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow import net.corda.node.internal.StartedNode 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.NotaryInfo import net.corda.testing.* @@ -46,7 +46,7 @@ class MySQLNotaryServiceTests : IntegrationTest() { @Before fun before() { 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 notaryNodeUnstarted = createNotaryNode() val nodeUnstarted = mockNet.createUnstartedNode() diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt index 67c14407c5..09dff74761 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt @@ -1,6 +1,11 @@ package net.corda.node.services.network +import net.corda.cordform.CordformNode 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.readAll 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.NetworkParameters 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.PortAllocation 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.junit.* import java.net.URL +import java.time.Instant +import kotlin.streams.toList import kotlin.test.assertEquals class NetworkMapTest : IntegrationTest() { @@ -29,6 +40,7 @@ class NetworkMapTest : IntegrationTest() { @Rule @JvmField val testSerialization = SerializationEnvironmentRule(true) + private val cacheTimeout = 1.seconds private val portAllocation = PortAllocation.Incremental(10000) @@ -39,7 +51,9 @@ class NetworkMapTest : IntegrationTest() { fun start() { networkMapServer = NetworkMapServer(cacheTimeout, portAllocation.nextHostAndPort()) val address = networkMapServer.start() - compatibilityZone = CompatibilityZoneParams(URL("http://$address")) + compatibilityZone = CompatibilityZoneParams(URL("http://$address"), publishNotaries = { + networkMapServer.networkParameters = testNetworkParameters(it, modifiedTime = Instant.ofEpochMilli(random63BitValue())) + }) } @After @@ -49,25 +63,35 @@ class NetworkMapTest : IntegrationTest() { @Test 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 networkParameters = alice.configuration.baseDirectory - .list { paths -> paths.filter { it.fileName.toString() == NETWORK_PARAMS_FILE_NAME }.findFirst().get() } + val networkParameters = (alice.configuration.baseDirectory / NETWORK_PARAMS_FILE_NAME) .readAll() .deserialize>() .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 fun `nodes can see each other using the http network map`() { - internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone, initialiseSerialization = false) { - val alice = startNode(providedName = ALICE_NAME) - val bob = startNode(providedName = BOB_NAME) - val notaryNode = defaultNotaryNode.get() - val aliceNode = alice.get() - val bobNode = bob.get() + internalDriver( + portAllocation = portAllocation, + compatibilityZone = compatibilityZone, + initialiseSerialization = false + ) { + val (aliceNode, bobNode, notaryNode) = listOf( + startNode(providedName = ALICE_NAME), + startNode(providedName = BOB_NAME), + defaultNotaryNode + ).transpose().getOrThrow() notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) @@ -77,15 +101,20 @@ class NetworkMapTest : IntegrationTest() { @Test fun `nodes process network map add updates correctly when adding new node to network map`() { - internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone, initialiseSerialization = false) { - val alice = startNode(providedName = ALICE_NAME) - val notaryNode = defaultNotaryNode.get() - val aliceNode = alice.get() + internalDriver( + portAllocation = portAllocation, + compatibilityZone = compatibilityZone, + initialiseSerialization = false + ) { + val (aliceNode, notaryNode) = listOf( + startNode(providedName = ALICE_NAME), + defaultNotaryNode + ).transpose().getOrThrow() notaryNode.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. Thread.sleep(cacheTimeout.toMillis() * 2) @@ -98,12 +127,16 @@ class NetworkMapTest : IntegrationTest() { @Test fun `nodes process network map remove updates correctly`() { - internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone, initialiseSerialization = false) { - val alice = startNode(providedName = ALICE_NAME) - val bob = startNode(providedName = BOB_NAME) - val notaryNode = defaultNotaryNode.get() - val aliceNode = alice.get() - val bobNode = bob.get() + internalDriver( + portAllocation = portAllocation, + compatibilityZone = compatibilityZone, + initialiseSerialization = false + ) { + val (aliceNode, bobNode, notaryNode) = listOf( + startNode(providedName = ALICE_NAME), + startNode(providedName = BOB_NAME), + defaultNotaryNode + ).transpose().getOrThrow() notaryNode.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) + } } diff --git a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt index cb955b7be0..c3af62c480 100644 --- a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt @@ -3,10 +3,15 @@ package net.corda.node.utilities.registration import net.corda.core.crypto.Crypto import net.corda.core.identity.CordaX500Name import net.corda.core.internal.cert +import net.corda.core.internal.concurrent.transpose import net.corda.core.internal.toX509CertHolder +import net.corda.core.messaging.startFlow import net.corda.core.utilities.NetworkHostAndPort +import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.getOrThrow 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.CertificateType 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_INTERMEDIATE_CA import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA -import net.corda.testing.IntegrationTest -import net.corda.testing.IntegrationTestSchemas -import net.corda.testing.SerializationEnvironmentRule +import net.corda.testing.* +import net.corda.testing.common.internal.testNetworkParameters 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.internalDriver import net.corda.testing.node.internal.network.NetworkMapServer @@ -31,6 +36,7 @@ import java.io.InputStream import java.net.URL import java.security.KeyPair import java.security.cert.CertPath +import java.security.cert.CertPathValidatorException import java.security.cert.Certificate import java.util.zip.ZipEntry import java.util.zip.ZipOutputStream @@ -42,20 +48,25 @@ class NodeRegistrationTest : IntegrationTest() { companion object { @ClassRule @JvmField 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 @JvmField val testSerialization = SerializationEnvironmentRule(true) + private val portAllocation = PortAllocation.Incremental(13000) - private val rootCertAndKeyPair = createSelfKeyAndSelfSignedCertificate() - private val registrationHandler = RegistrationHandler(rootCertAndKeyPair) + private val registrationHandler = RegistrationHandler(ROOT_CA) private lateinit var server: NetworkMapServer private lateinit var serverHostAndPort: NetworkHostAndPort @Before fun startServer() { - server = NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), rootCertAndKeyPair, registrationHandler) + server = NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), ROOT_CA, "localhost", registrationHandler) serverHostAndPort = server.start() } @@ -64,50 +75,65 @@ class NodeRegistrationTest : IntegrationTest() { server.close() } - // TODO Ideally this test should be checking that two nodes that register are able to transact with each other. However - // starting a second node hangs so that needs to be fixed. @Test fun `node registration correct root cert`() { - 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( portAllocation = portAllocation, - notarySpecs = emptyList(), compatibilityZone = compatibilityZone, - initialiseSerialization = false + initialiseSerialization = false, + notarySpecs = listOf(NotarySpec(notaryName)), + extraCordappPackagesToScan = listOf("net.corda.finance") ) { - startNode(providedName = CordaX500Name("Alice", "London", "GB")).getOrThrow() - assertThat(registrationHandler.idsPolled).contains("Alice") + val nodes = listOf( + 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 fun `node registration wrong root cert`() { - val someCert = createSelfKeyAndSelfSignedCertificate().certificate.cert - val compatibilityZone = CompatibilityZoneParams(URL("http://$serverHostAndPort"), rootCert = someCert) + val someRootCert = X509Utilities.createSelfSignedCACertificate( + 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( portAllocation = portAllocation, - notarySpecs = emptyList(), compatibilityZone = compatibilityZone, - // Changing the content of the truststore makes the node fail in a number of ways if started out process. - startNodesInProcess = true + initialiseSerialization = false, + 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 { - startNode(providedName = CordaX500Name("Alice", "London", "GB")).getOrThrow() - }.isInstanceOf(WrongRootCertException::class.java) + defaultNotaryNode.getOrThrow() + }.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") @@ -124,6 +150,7 @@ class RegistrationHandler(private val rootCertAndKeyPair: CertificateAndKeyPair) certificationRequest, rootCertAndKeyPair.keyPair, arrayOf(rootCertAndKeyPair.certificate.cert)) + require(!name.organisation.contains("\\s".toRegex())) { "Whitespace in the organisation name not supported" } certPaths[name.organisation] = certPath return Response.ok(name.organisation).build() } @@ -154,13 +181,14 @@ class RegistrationHandler(private val rootCertAndKeyPair: CertificateAndKeyPair) caCertPath: Array): Pair { val request = JcaPKCS10CertificationRequest(certificationRequest) val name = CordaX500Name.parse(request.subject.toString()) - val x509CertificateHolder = X509Utilities.createCertificate(CertificateType.NODE_CA, + val nodeCaCert = X509Utilities.createCertificate( + CertificateType.NODE_CA, caCertPath.first().toX509CertHolder(), caKeyPair, name, request.publicKey, nameConstraints = null) - val certPath = X509CertificateFactory().generateCertPath(x509CertificateHolder.cert, *caCertPath) + val certPath = X509CertificateFactory().generateCertPath(nodeCaCert.cert, *caCertPath) return Pair(certPath, name) } } diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt index cc146d78d2..5f6d32e69d 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt @@ -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.User import net.corda.testing.* +import net.corda.testing.internal.configureTestSSL import net.corda.testing.node.internal.NodeBasedTest import net.corda.testing.node.startFlow import org.apache.activemq.artemis.api.core.ActiveMQNonExistentQueueException @@ -46,6 +47,7 @@ abstract class MQSecurityTest : NodeBasedTest() { @ClassRule @JvmField val databaseSchemas = IntegrationTestSchemas(ALICE_NAME.toDatabaseSchemaName(), BOB_NAME.toDatabaseSchemaName()) } + val rpcUser = User("user1", "pass", permissions = emptySet()) lateinit var alice: StartedNode lateinit var attacker: SimpleMQClient diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/SimpleMQClient.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/SimpleMQClient.kt index 4257e7e3c0..70d6c90ecb 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/SimpleMQClient.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/SimpleMQClient.kt @@ -6,7 +6,7 @@ import net.corda.core.utilities.NetworkHostAndPort import net.corda.nodeapi.ArtemisTcpTransport import net.corda.nodeapi.ConnectionDirection 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.* /** diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 7c59454012..aaffb40be2 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -60,7 +60,7 @@ import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.VaultSoftLockManager import net.corda.node.shell.InteractiveShell 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.crypto.KeyStoreWrapper 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 // AbstractNode. It should be possible to generate the NodeInfo outside of AbstractNode, so it can be passed in. abstract class AbstractNode(val configuration: NodeConfiguration, - val platformClock: Clock, + val platformClock: CordaClock, protected val versionInfo: VersionInfo, protected val cordappLoader: CordappLoader, private val busyNodeLatch: ReusableLatch = ReusableLatch()) : SingletonSerializeAsToken() { @@ -184,13 +184,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration, return SignedNodeInfo(serialised, listOf(signature)) } - open fun generateNodeInfo() { + open fun generateAndSaveNodeInfo(): NodeInfo { check(started == null) { "Node has already been started" } log.info("Generating nodeInfo ...") initCertificate() val schemaService = NodeSchemaService(cordappLoader.cordappSchemas) 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 // a code smell. val persistentNetworkMapCache = PersistentNetworkMapCache(database, notaries = emptyList()) @@ -200,6 +200,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, privateKey.sign(serialised.bytes) } NodeInfoWatcher.saveToFile(configuration.baseDirectory, signedNodeInfo) + info } } @@ -239,14 +240,16 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } val notaryService = makeNotaryService(nodeServices, database) val smm = makeStateMachineManager(database) - val flowStarter = FlowStarterImpl(serverThread, smm) + val flowLogicRefFactory = FlowLogicRefFactoryImpl(cordappLoader.appClassLoader) + val flowStarter = FlowStarterImpl(serverThread, smm, flowLogicRefFactory) val schedulerService = NodeSchedulerService( platformClock, database, flowStarter, transactionStorage, unfinishedSchedules = busyNodeLatch, - serverThread = serverThread) + serverThread = serverThread, + flowLogicRefFactory = flowLogicRefFactory) if (serverThread is ExecutorService) { runOnStop += { // 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) } } - makeVaultObservers(schedulerService, database.hibernateConfig, smm, schemaService) + makeVaultObservers(schedulerService, database.hibernateConfig, smm, schemaService, flowLogicRefFactory) val rpcOps = makeRPCOps(flowStarter, database, smm) startMessagingService(rpcOps) installCoreFlows() @@ -263,7 +266,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration, tokenizableServices = nodeServices + cordaServices + schedulerService registerCordappFlows(smm) _services.rpcFlows += cordappLoader.cordapps.flatMap { it.rpcFlows } - FlowLogicRefFactoryImpl.classloader = cordappLoader.appClassLoader startShell(rpcOps) 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() - - private fun makeVaultObservers(schedulerService: SchedulerService, hibernateConfig: HibernateConfiguration, smm: StateMachineManager, schemaService: SchemaService) { + private fun makeVaultObservers(schedulerService: SchedulerService, hibernateConfig: HibernateConfiguration, smm: StateMachineManager, schemaService: SchemaService, flowLogicRefFactory: FlowLogicRefFactory) { VaultSoftLockManager.install(services.vaultService, smm) - ScheduledActivityObserver.install(services.vaultService, schedulerService) + ScheduledActivityObserver.install(services.vaultService, schedulerService, flowLogicRefFactory) 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 caKeyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword) 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) return PersistentIdentityService(trustRoot, *caCertificates) } @@ -754,10 +755,10 @@ abstract class AbstractNode(val configuration: NodeConfiguration, val (id, singleName) = if (notaryConfig == null || !notaryConfig.isClusterConfig) { // 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 { // 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? 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") // TODO: Remove use of [IdentityGenerator.generateToDisk]. 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. 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 startFlow(logic: FlowLogic, context: InvocationContext): CordaFuture> { return serverThread.fetchFrom { smm.startFlow(logic, context) } } + + override fun invokeFlowAsync( + logicType: Class>, + context: InvocationContext, + vararg args: Any?): CordaFuture> { + val logicRef = flowLogicRefFactory.createForRPC(logicType, *args) + val logic: FlowLogic = uncheckedCast(flowLogicRefFactory.toFlowLogic(logicRef)) + return startFlow(logic, context) + } } class ConfigurationException(message: String) : CordaException(message) diff --git a/node/src/main/kotlin/net/corda/node/internal/CordaClock.kt b/node/src/main/kotlin/net/corda/node/internal/CordaClock.kt index cd86529f67..c08961ee77 100644 --- a/node/src/main/kotlin/net/corda/node/internal/CordaClock.kt +++ b/node/src/main/kotlin/net/corda/node/internal/CordaClock.kt @@ -22,10 +22,15 @@ abstract class CordaClock : Clock(), SerializeAsToken { override fun getZone(): ZoneId = delegateClock.zone @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()") + + /** This is an observer on the mutation count of this [Clock], which reflects the occurrence of mutations. */ + abstract val mutations: Observable } @ThreadSafe -class SimpleClock(override val delegateClock: Clock) : CordaClock() +class SimpleClock(override val delegateClock: Clock) : CordaClock() { + override val mutations: Observable = Observable.never() +} /** * 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 } private val _version = AtomicLong(0L) - /** This is an observer on the mutation count of this [Clock], which reflects the occurence of mutations. */ - val mutations: Observable by lazy { + override val mutations: Observable by lazy { Observable.create { subscriber: Subscriber -> if (!subscriber.isUnsubscribed) { mutationObservers.add(subscriber) diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index db5b2eb12e..7a71ea615f 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -2,7 +2,6 @@ package net.corda.node.internal import com.codahale.metrics.JmxReporter 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.thenMatch 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.serialization.KryoServerSerializationScheme 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.transactions.InMemoryTransactionVerifierService 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 org.slf4j.Logger import org.slf4j.LoggerFactory +import rx.Scheduler import rx.schedulers.Schedulers import java.time.Clock import java.util.concurrent.atomic.AtomicInteger @@ -67,7 +69,7 @@ open class Node(configuration: NodeConfiguration, exitProcess(1) } - private fun createClock(configuration: NodeConfiguration): Clock { + private fun createClock(configuration: NodeConfiguration): CordaClock { return (if (configuration.useTestClock) ::DemoClock else ::SimpleClock)(Clock.systemUTC()) } @@ -269,15 +271,13 @@ open class Node(configuration: NodeConfiguration, private val _startupComplete = openFuture() val startupComplete: CordaFuture get() = _startupComplete - override fun generateNodeInfo() { + override fun generateAndSaveNodeInfo(): NodeInfo { initialiseSerialization() - super.generateNodeInfo() + return super.generateAndSaveNodeInfo() } override fun start(): StartedNode { - if (initialiseSerialization) { - initialiseSerialization() - } + initialiseSerialization() val started: StartedNode = uncheckedCast(super.start()) nodeReadyFuture.thenMatch({ serverThread.execute { @@ -310,8 +310,10 @@ open class Node(configuration: NodeConfiguration, return started } - override fun getRxIoScheduler() = Schedulers.io()!! + override fun getRxIoScheduler(): Scheduler = Schedulers.io() + private fun initialiseSerialization() { + if (!initialiseSerialization) return val classloader = cordappLoader.appClassLoader nodeSerializationEnv = SerializationEnvironmentImpl( SerializationFactoryImpl().apply { diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index 7d0aa2a69a..a1dde93b70 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -1,7 +1,6 @@ package net.corda.node.internal import com.jcabi.manifests.Manifests -import com.typesafe.config.ConfigException import joptsimple.OptionException import net.corda.core.internal.* import net.corda.core.internal.concurrent.thenMatch @@ -126,7 +125,7 @@ open class NodeStartup(val args: Array) { val node = createNode(conf, versionInfo) if (cmdlineOptions.justGenerateNodeInfo) { // Perform the minimum required start-up logic to be able to write a nodeInfo to disk - node.generateNodeInfo() + node.generateAndSaveNodeInfo() return } if (cmdlineOptions.justRunDbMigration) { diff --git a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt index e9794842d6..7a4f2f669d 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt @@ -6,7 +6,6 @@ import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowLogic import net.corda.core.flows.StateMachineRunId import net.corda.core.internal.FlowStateMachine -import net.corda.core.internal.uncheckedCast import net.corda.core.messaging.DataFeed import net.corda.core.messaging.StateMachineTransactionMapping 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.services.config.NodeConfiguration import net.corda.node.services.messaging.MessagingService -import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.nodeapi.internal.persistence.CordaPersistence @@ -137,11 +135,7 @@ interface FlowStarter { fun invokeFlowAsync( logicType: Class>, context: InvocationContext, - vararg args: Any?): CordaFuture> { - val logicRef = FlowLogicRefFactoryImpl.createForRPC(logicType, *args) - val logic: FlowLogic = uncheckedCast(FlowLogicRefFactoryImpl.toFlowLogic(logicRef)) - return startFlow(logic, context) - } + vararg args: Any?): CordaFuture> } interface StartedNodeServices : ServiceHubInternal, FlowStarter diff --git a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt index ed6028f13c..3e31a977ae 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt @@ -3,17 +3,21 @@ package net.corda.node.services.config import com.typesafe.config.* import net.corda.core.crypto.Crypto 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.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.toProperties +import net.corda.nodeapi.internal.createDevKeyStores 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 java.nio.file.Path -import java.security.KeyStore fun configOf(vararg pairs: Pair): Config = ConfigFactory.parseMap(mapOf(*pairs)) operator fun Config.plus(overrides: Map): 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 * 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) +// TODO Move this to KeyStoreConfigHelpers fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name) { certificatesDirectory.createDirectories() if (!trustStoreFile.exists()) { @@ -67,7 +73,9 @@ fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name) { } if (!sslKeystore.exists() || !nodeKeystore.exists()) { 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. 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) -} diff --git a/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt b/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt index 31badf6ec0..8c18043db7 100644 --- a/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt +++ b/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt @@ -10,6 +10,7 @@ import net.corda.core.contracts.ScheduledStateRef import net.corda.core.contracts.StateRef import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowLogicRefFactory import net.corda.core.internal.ThreadBox import net.corda.core.internal.VisibleForTesting 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.utilities.contextLogger import net.corda.core.utilities.trace +import net.corda.node.internal.CordaClock import net.corda.node.internal.MutableClock import net.corda.node.services.api.FlowStarter 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.PersistentMap 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. */ @ThreadSafe -class NodeSchedulerService(private val clock: Clock, +class NodeSchedulerService(private val clock: CordaClock, private val database: CordaPersistence, private val flowStarter: FlowStarter, private val stateLoader: StateLoader, private val schedulerTimerExecutor: Executor = Executors.newSingleThreadExecutor(), private val unfinishedSchedules: ReusableLatch = ReusableLatch(), - private val serverThread: AffinityExecutor) + private val serverThread: AffinityExecutor, + private val flowLogicRefFactory: FlowLogicRefFactory) : SchedulerService, SingletonSerializeAsToken() { companion object { @@ -78,16 +80,12 @@ class NodeSchedulerService(private val clock: Clock, @Suspendable @VisibleForTesting // 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()): Boolean { + fun awaitWithDeadline(clock: CordaClock, deadline: Instant, future: Future<*> = GuavaSettableFuture.create()): Boolean { var nanos: Long do { val originalFutureCompleted = makeStrandFriendlySettableFuture(future) - val subscription = if (clock is MutableClock) { - clock.mutations.first().subscribe { - originalFutureCompleted.set(false) - } - } else { - null + val subscription = clock.mutations.first().subscribe { + originalFutureCompleted.set(false) } nanos = (clock.instant() until deadline).toNanos() 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 } } - subscription?.unsubscribe() + subscription.unsubscribe() originalFutureCompleted.cancel(false) } while (nanos > 0 && !future.isDone) return future.isDone @@ -279,7 +277,7 @@ class NodeSchedulerService(private val clock: Clock, scheduledStatesQueue.remove(scheduledState) scheduledStatesQueue.add(newState) } else { - val flowLogic = FlowLogicRefFactoryImpl.toFlowLogic(scheduledActivity.logicRef) + val flowLogic = flowLogicRefFactory.toFlowLogic(scheduledActivity.logicRef) log.trace { "Scheduler starting FlowLogic $flowLogic" } scheduledFlow = flowLogic scheduledStates.remove(scheduledState.ref) @@ -297,7 +295,7 @@ class NodeSchedulerService(private val clock: Clock, val state = txState.data as SchedulableState return try { // This can throw as running contract code. - state.nextScheduledActivity(scheduledState.ref, FlowLogicRefFactoryImpl) + state.nextScheduledActivity(scheduledState.ref, flowLogicRefFactory) } catch (e: Exception) { log.error("Attempt to run scheduled state $scheduledState resulted in error.", e) null diff --git a/node/src/main/kotlin/net/corda/node/services/events/ScheduledActivityObserver.kt b/node/src/main/kotlin/net/corda/node/services/events/ScheduledActivityObserver.kt index 3020a9e528..2c3e939c98 100644 --- a/node/src/main/kotlin/net/corda/node/services/events/ScheduledActivityObserver.kt +++ b/node/src/main/kotlin/net/corda/node/services/events/ScheduledActivityObserver.kt @@ -4,19 +4,19 @@ import net.corda.core.contracts.ContractState import net.corda.core.contracts.SchedulableState import net.corda.core.contracts.ScheduledStateRef import net.corda.core.contracts.StateAndRef +import net.corda.core.flows.FlowLogicRefFactory import net.corda.core.node.services.VaultService 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 * consumption. */ -class ScheduledActivityObserver private constructor(private val schedulerService: SchedulerService) { +class ScheduledActivityObserver private constructor(private val schedulerService: SchedulerService, private val FlowLogicRefFactory: FlowLogicRefFactory) { companion object { @JvmStatic - fun install(vaultService: VaultService, schedulerService: SchedulerService) { - val observer = ScheduledActivityObserver(schedulerService) + fun install(vaultService: VaultService, schedulerService: SchedulerService, flowLogicRefFactory: FlowLogicRefFactory) { + val observer = ScheduledActivityObserver(schedulerService, flowLogicRefFactory) vaultService.rawUpdates.subscribe { (consumed, produced) -> consumed.forEach { schedulerService.unscheduleStateActivity(it.ref) } produced.forEach { observer.scheduleStateActivity(it) } @@ -32,7 +32,7 @@ class ScheduledActivityObserver private constructor(private val schedulerService private fun scheduleStateActivity(produced: StateAndRef) { val producedState = produced.state.data 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)) } } diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowLogicRefFactoryImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowLogicRefFactoryImpl.kt index f0f10ebed4..1b20e75c5b 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowLogicRefFactoryImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowLogicRefFactoryImpl.kt @@ -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" * 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 - var classloader: ClassLoader = javaClass.classLoader - +// TODO: Replace with a per app classloader/cordapp provider/cordapp loader - this will do for now +class FlowLogicRefFactoryImpl(private val classloader: ClassLoader) : SingletonSerializeAsToken(), FlowLogicRefFactory { override fun create(flowClass: Class>, vararg args: Any?): FlowLogicRef { if (!flowClass.isAnnotationPresent(SchedulableFlow::class.java)) { throw IllegalFlowLogicException(flowClass, "because it's not a schedulable flow") @@ -42,7 +40,7 @@ object FlowLogicRefFactoryImpl : SingletonSerializeAsToken(), FlowLogicRefFactor return createForRPC(flowClass, *args) } - fun createForRPC(flowClass: Class>, vararg args: Any?): FlowLogicRef { + override fun createForRPC(flowClass: Class>, vararg args: Any?): FlowLogicRef { // 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. val argTypes = args.map { it?.javaClass } @@ -81,7 +79,7 @@ object FlowLogicRefFactoryImpl : SingletonSerializeAsToken(), FlowLogicRefFactor 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") val klass = Class.forName(ref.flowLogicClassName, true, classloader).asSubclass(FlowLogic::class.java) return createConstructor(klass, ref.args)() diff --git a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt index 698420ca0a..af9735b89a 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt @@ -12,10 +12,10 @@ import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA import org.bouncycastle.openssl.jcajce.JcaPEMWriter import org.bouncycastle.util.io.pem.PemObject import java.io.StringWriter -import java.nio.file.Path import java.security.KeyPair import java.security.KeyStore 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 @@ -32,7 +32,7 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v // TODO: Use different password for private key. private val privateKeyPassword = config.keyStorePassword private val trustStore: KeyStore - private val rootCert: Certificate + private val rootCert: X509Certificate init { 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. " + "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) 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. - if (rootCert != certificates.last()) { - // Assumes certificate chain always starts with client certificate and end with root certificate. - throw WrongRootCertException(rootCert, certificates.last(), config.trustStoreFile) - } + println("Checking root of the certificate path is what we expect.") + X509Utilities.validateCertificateChain(rootCert, *certificates) println("Generating SSL certificate for node messaging service.") 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()) diff --git a/node/src/test/java/net/corda/node/services/events/FlowLogicRefFromJavaTest.java b/node/src/test/java/net/corda/node/services/events/FlowLogicRefFromJavaTest.java index 5e0a69cbad..0f83de90cf 100644 --- a/node/src/test/java/net/corda/node/services/events/FlowLogicRefFromJavaTest.java +++ b/node/src/test/java/net/corda/node/services/events/FlowLogicRefFromJavaTest.java @@ -48,13 +48,15 @@ public class FlowLogicRefFromJavaTest { } } + private final FlowLogicRefFactoryImpl flowLogicRefFactory = new FlowLogicRefFactoryImpl(FlowLogicRefFactoryImpl.class.getClassLoader()); + @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 public void testNoArg() { - FlowLogicRefFactoryImpl.INSTANCE.createForRPC(JavaNoArgFlowLogic.class); + flowLogicRefFactory.createForRPC(JavaNoArgFlowLogic.class); } } diff --git a/node/src/test/kotlin/net/corda/node/services/events/FlowLogicRefTest.kt b/node/src/test/kotlin/net/corda/node/services/events/FlowLogicRefTest.kt index ae6a5b7680..3794a39ebf 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/FlowLogicRefTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/FlowLogicRefTest.kt @@ -34,47 +34,48 @@ class FlowLogicRefTest { override fun call() = Unit } + private val flowLogicRefFactory = FlowLogicRefFactoryImpl(FlowLogicRefFactoryImpl::class.java.classLoader) @Test fun `create kotlin no arg`() { - FlowLogicRefFactoryImpl.createForRPC(KotlinNoArgFlowLogic::class.java) + flowLogicRefFactory.createForRPC(KotlinNoArgFlowLogic::class.java) } @Test fun `create kotlin`() { 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 fun `create primary`() { - FlowLogicRefFactoryImpl.createForRPC(KotlinFlowLogic::class.java, ParamType1(1), ParamType2("Hello Jack")) + flowLogicRefFactory.createForRPC(KotlinFlowLogic::class.java, ParamType1(1), ParamType2("Hello Jack")) } @Test fun `create kotlin void`() { - FlowLogicRefFactoryImpl.createKotlin(KotlinFlowLogic::class.java, emptyMap()) + flowLogicRefFactory.createKotlin(KotlinFlowLogic::class.java, emptyMap()) } @Test fun `create kotlin non primary`() { val args = mapOf(Pair("C", ParamType2("Hello Jack"))) - FlowLogicRefFactoryImpl.createKotlin(KotlinFlowLogic::class.java, args) + flowLogicRefFactory.createKotlin(KotlinFlowLogic::class.java, args) } @Test fun `create java primitive no registration required`() { val args = mapOf(Pair("primitive", "A string")) - FlowLogicRefFactoryImpl.createKotlin(KotlinFlowLogic::class.java, args) + flowLogicRefFactory.createKotlin(KotlinFlowLogic::class.java, args) } @Test fun `create kotlin primitive no registration required`() { val args = mapOf(Pair("kotlinType", 3)) - FlowLogicRefFactoryImpl.createKotlin(KotlinFlowLogic::class.java, args) + flowLogicRefFactory.createKotlin(KotlinFlowLogic::class.java, args) } @Test(expected = IllegalFlowLogicException::class) fun `create for non-schedulable flow logic`() { - FlowLogicRefFactoryImpl.create(NonSchedulableFlow::class.java) + flowLogicRefFactory.create(NonSchedulableFlow::class.java) } } diff --git a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt index 3c3e356f12..79d27435bc 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt @@ -64,6 +64,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { @Rule @JvmField val testSerialization = SerializationEnvironmentRule(true) + private val flowLogicRefFactory = FlowLogicRefFactoryImpl(FlowLogicRefFactoryImpl::class.java.classLoader) private val realClock: Clock = Clock.systemUTC() private val stoppedClock: Clock = Clock.fixed(realClock.instant(), realClock.zone) private val testClock = TestClock(stoppedClock) @@ -122,7 +123,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { } smmExecutor = AffinityExecutor.ServiceAffinityExecutor("test", 1) 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 -> if (change is StateMachineManager.Change.Removed && mockSMM.allStateMachines.isEmpty()) { smmHasRemovedAllFlows.countDown() @@ -305,7 +306,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { database.transaction { apply { 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 { addOutputState(state, DummyContract.PROGRAM_ID, DUMMY_NOTARY) addCommand(Command(), freshKey) diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt index 608f583f89..c609d59f2c 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt @@ -70,7 +70,7 @@ class NetworkMapClientTest { // The test server returns same network parameter for any hash. val networkParameter = networkMapClient.getNetworkParameter(SecureHash.randomSHA256())?.verified() assertNotNull(networkParameter) - assertEquals(NetworkMapServer.stubNetworkParameter, networkParameter) + assertEquals(server.networkParameters, networkParameter) } @Test diff --git a/node/src/test/kotlin/net/corda/node/utilities/ClockUtilsTest.kt b/node/src/test/kotlin/net/corda/node/utilities/ClockUtilsTest.kt index 783564a664..d8dbfac65f 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/ClockUtilsTest.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/ClockUtilsTest.kt @@ -8,6 +8,8 @@ import com.google.common.util.concurrent.SettableFuture import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.hours 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.testing.node.TestClock import org.junit.After @@ -25,13 +27,13 @@ import kotlin.test.fail class ClockUtilsTest { lateinit var realClock: Clock - lateinit var stoppedClock: Clock + lateinit var stoppedClock: CordaClock lateinit var executor: ExecutorService @Before fun setup() { realClock = Clock.systemUTC() - stoppedClock = Clock.fixed(realClock.instant(), realClock.zone) + stoppedClock = SimpleClock(Clock.fixed(realClock.instant(), realClock.zone)) executor = Executors.newSingleThreadExecutor() } diff --git a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt index 209153fdb8..13113fccf9 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt @@ -14,12 +14,14 @@ import net.corda.core.internal.createDirectories import net.corda.node.services.config.NodeConfiguration import net.corda.nodeapi.internal.crypto.* import net.corda.testing.ALICE_NAME +import net.corda.testing.internal.createDevNodeCaCertPath import net.corda.testing.internal.rigorousMock import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.After import org.junit.Before import org.junit.Test +import java.security.cert.CertPathValidatorException import java.security.cert.Certificate import java.security.cert.X509Certificate import kotlin.test.assertFalse @@ -29,16 +31,19 @@ class NetworkRegistrationHelperTest { private val fs = Jimfs.newFileSystem(unix()) private val requestId = SecureHash.randomSHA256().toString() 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 @Before 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() abstract class AbstractNodeConfiguration : NodeConfiguration config = rigorousMock().also { @@ -108,11 +113,13 @@ class NetworkRegistrationHelperTest { @Test 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() assertThatThrownBy { registrationHelper.buildKeystore() - }.isInstanceOf(WrongRootCertException::class.java) + }.isInstanceOf(CertPathValidatorException::class.java) } private fun createRegistrationHelper(): NetworkRegistrationHelper { @@ -123,15 +130,11 @@ class NetworkRegistrationHelperTest { return NetworkRegistrationHelper(config, certService) } - private fun saveTrustStoreWithRootCa(rootCa: X509Certificate) { - config.trustStoreFile.parent.createDirectories() + private fun saveTrustStoreWithRootCa(rootCert: X509Certificate) { + config.certificatesDirectory.createDirectories() 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) } } - - private fun createCaCert(name: CordaX500Name): X509Certificate { - return X509Utilities.createSelfSignedCACertificate(name, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)).cert - } } diff --git a/samples/irs-demo/web/src/main/resources/static/js/controllers/CreateDeal.js b/samples/irs-demo/web/src/main/resources/static/js/controllers/CreateDeal.js index 41b30a6172..15b7af2381 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/controllers/CreateDeal.js +++ b/samples/irs-demo/web/src/main/resources/static/js/controllers/CreateDeal.js @@ -21,7 +21,7 @@ define([ $scope.formError = resp.data; }, handleHttpFail); }; - $('input.percent').mask("9.999999%", {placeholder: "", autoclear: false}); + $('input.percent').mask("9.999999", {placeholder: "", autoclear: false}); $('#swapirscolumns').click(() => { let first = $('#irscolumns .irscolumn:eq( 0 )'); let last = $('#irscolumns .irscolumn:eq( 1 )'); diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt index 311a5c51b2..1b2967c953 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt @@ -8,7 +8,7 @@ import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.services.config.BFTSMaRtConfiguration import net.corda.node.services.config.NotaryConfig 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.ALICE_NAME import net.corda.testing.BOB_NAME @@ -62,7 +62,7 @@ class BFTNotaryCordform : CordformDefinition() { } override fun setup(context: CordformContext) { - IdentityGenerator.generateDistributedNotaryIdentity( + DevIdentityGenerator.generateDistributedNotaryIdentity( notaryNames.map { context.baseDirectory(it.toString()) }, clusterName, minCorrectReplicas(clusterSize) diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt index 59384a412f..abceabbe77 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt @@ -4,11 +4,10 @@ import net.corda.cordform.CordformContext import net.corda.cordform.CordformDefinition import net.corda.cordform.CordformNode import net.corda.core.identity.CordaX500Name -import net.corda.core.node.services.NotaryService import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.services.config.NotaryConfig 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.ALICE_NAME import net.corda.testing.BOB_NAME @@ -59,7 +58,7 @@ class RaftNotaryCordform : CordformDefinition() { } override fun setup(context: CordformContext) { - IdentityGenerator.generateDistributedNotaryIdentity( + DevIdentityGenerator.generateDistributedNotaryIdentity( notaryNames.map { context.baseDirectory(it.toString()) }, clusterName ) diff --git a/samples/simm-valuation-demo/build.gradle b/samples/simm-valuation-demo/build.gradle index 992ced76be..da6fb0439b 100644 --- a/samples/simm-valuation-demo/build.gradle +++ b/samples/simm-valuation-demo/build.gradle @@ -72,6 +72,9 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { notary = [validating : true] p2pPort 10002 cordapps = ["$project.group:finance:$corda_release_version"] + extraConfig = [ + jvmArgs : [ "-Xmx1g"] + ] } node { name "O=Bank A,L=London,C=GB" @@ -80,6 +83,9 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { rpcPort 10006 cordapps = ["$project.group:finance:$corda_release_version"] rpcUsers = ext.rpcUsers + extraConfig = [ + jvmArgs : [ "-Xmx1g"] + ] } node { name "O=Bank B,L=New York,C=US" @@ -88,6 +94,9 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { rpcPort 10009 cordapps = ["$project.group:finance:$corda_release_version"] rpcUsers = ext.rpcUsers + extraConfig = [ + jvmArgs : [ "-Xmx1g"] + ] } node { name "O=Bank C,L=Tokyo,C=JP" @@ -96,6 +105,9 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { rpcPort 10012 cordapps = ["$project.group:finance:$corda_release_version"] rpcUsers = ext.rpcUsers + extraConfig = [ + jvmArgs : [ "-Xmx1g"] + ] } } diff --git a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/SellerFlow.kt b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/SellerFlow.kt index 2a08170192..3e91639000 100644 --- a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/SellerFlow.kt +++ b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/SellerFlow.kt @@ -40,7 +40,8 @@ class SellerFlow(private val otherParty: Party, progressTracker.currentStep = SELF_ISSUING 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 diff --git a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt index eebe7dd2db..c1c29b9de7 100644 --- a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt +++ b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt @@ -2,6 +2,7 @@ package net.corda.testing.driver import net.corda.core.concurrent.CordaFuture import net.corda.core.identity.CordaX500Name +import net.corda.core.internal.CertRole import net.corda.core.internal.div import net.corda.core.internal.list import net.corda.core.internal.readLines @@ -47,12 +48,28 @@ class DriverTests : IntegrationTest() { @Test fun `simple node startup and shutdown`() { val handle = driver { - val regulator = startNode(providedName = DUMMY_REGULATOR_NAME) - nodeMustBeUp(regulator) + val node = startNode(providedName = DUMMY_REGULATOR_NAME) + nodeMustBeUp(node) } 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 fun `random free port allocation`() { val nodeHandle = driver(portAllocation = PortAllocation.RandomFree) { diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index 3797c75b64..e9808f518e 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -14,11 +14,11 @@ import net.corda.node.internal.StartedNode import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.VerifierType 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.genericDriver 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.ServerSocket import java.nio.file.Path diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index aff0c9abfd..430e5ecc9b 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -38,7 +38,7 @@ import net.corda.node.services.transactions.BFTSMaRt import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.utilities.AffinityExecutor 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.network.NetworkParametersCopier import net.corda.nodeapi.internal.network.NotaryInfo @@ -236,7 +236,7 @@ open class MockNetwork(private val cordappPackages: List, private fun generateNotaryIdentities(): List { 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) } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt index 5833840bb4..ca05b8a7b9 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt @@ -10,15 +10,12 @@ import net.corda.cordform.CordformContext import net.corda.cordform.CordformNode import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.firstOf -import net.corda.core.crypto.random63BitValue 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.copyTo -import net.corda.core.internal.createDirectories -import net.corda.core.internal.div import net.corda.core.messaging.CordaRPCOps import net.corda.core.node.services.NetworkMapCache +import net.corda.core.serialization.deserialize import net.corda.core.toFuture import net.corda.core.utilities.NetworkHostAndPort 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.utilities.registration.HTTPNetworkRegistrationService 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.config.User import net.corda.nodeapi.internal.config.parseAs @@ -68,7 +66,7 @@ import java.nio.file.StandardCopyOption import java.security.cert.X509Certificate import java.time.Duration import java.time.Instant -import java.time.ZoneOffset +import java.time.ZoneOffset.UTC import java.time.format.DateTimeFormatter import java.util.* import java.util.concurrent.Executors @@ -96,15 +94,16 @@ class DriverDSLImpl( private var _shutdownManager: ShutdownManager? = null override val shutdownManager get() = _shutdownManager!! 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. private val countObservables = mutableMapOf>() - private lateinit var _notaries: List - override val notaryHandles: List get() = _notaries - private var networkParameters: NetworkParametersCopier? = null + /** + * Future which completes when the network map is available, whether a local one or one from the CZ. This future acts + * 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 + private lateinit var _notaries: CordaFuture> + override val notaryHandles: List get() = _notaries.getOrThrow() class State { val processes = ArrayList() @@ -145,12 +144,12 @@ class DriverDSLImpl( _executorService?.shutdownNow() } - private fun establishRpc(config: NodeConfiguration, processDeathFuture: CordaFuture): CordaFuture { - val rpcAddress = config.rpcAddress!! + private fun establishRpc(config: NodeConfig, processDeathFuture: CordaFuture): CordaFuture { + val rpcAddress = config.corda.rpcAddress!! val client = CordaRPCClient(rpcAddress) val connectionFuture = poll(executorService, "RPC connection") { try { - client.start(config.rpcUsers[0].username, config.rpcUsers[0].password) + config.corda.rpcUsers[0].run { client.start(username, password) } } catch (e: Exception) { if (processDeathFuture.isDone) throw e log.error("Exception $e, Retrying RPC connection at $rpcAddress") @@ -178,65 +177,74 @@ class DriverDSLImpl( ): CordaFuture { val p2pAddress = portAllocation.nextHostAndPort() // TODO: Derive name from the full picked name, don't just wrap the common name - val name = providedName ?: CordaX500Name(organisation = "${oneOf(names).organisation}-${p2pAddress.port}", locality = "London", country = "GB") - + val name = providedName ?: CordaX500Name("${oneOf(names).organisation}-${p2pAddress.port}", "London", "GB") 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 { doneFuture(Unit) } return registrationFuture.flatMap { - val rpcAddress = portAllocation.nextHostAndPort() - val webAddress = portAllocation.nextHostAndPort() - val users = rpcUsers.map { it.copy(permissions = it.permissions + DRIVER_REQUIRED_PERMISSIONS) } - 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) + networkMapAvailability.flatMap { + // But starting the node proper does require the network map + startRegisteredNode(name, it, rpcUsers, verifierType, customOverrides, startInSameProcess, maximumHeapSize, p2pAddress) + } } } - private fun nodeRegistration(providedName: CordaX500Name, rootCert: X509Certificate, compatibilityZoneURL: URL): CordaFuture { + private fun startRegisteredNode(name: CordaX500Name, + localNetworkMap: LocalNetworkMap?, + rpcUsers: List, + verifierType: VerifierType, + customOverrides: Map, + startInSameProcess: Boolean? = null, + maximumHeapSize: String = "200m", + p2pAddress: NetworkHostAndPort = portAllocation.nextHostAndPort()): CordaFuture { + 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 { val baseDirectory = baseDirectory(providedName).createDirectories() - val config = ConfigHelper.loadConfig( + val config = NodeConfig(ConfigHelper.loadConfig( baseDirectory = baseDirectory, allowMissingConfig = true, configOverrides = configOf( "p2pAddress" to "localhost:1222", // required argument, not really used "compatibilityZoneURL" to compatibilityZoneURL.toString(), "myLegalName" to providedName.toString()) - ) - val configuration = config.parseAsNodeConfiguration() + )) - configuration.trustStoreFile.parent.createDirectories() - loadOrCreateKeyStore(configuration.trustStoreFile, configuration.trustStorePassword).also { - it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCert) - it.save(configuration.trustStoreFile, configuration.trustStorePassword) + config.corda.certificatesDirectory.createDirectories() + loadOrCreateKeyStore(config.corda.trustStoreFile, config.corda.trustStorePassword).apply { + addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCert) + save(config.corda.trustStoreFile, config.corda.trustStorePassword) } return if (startNodesInProcess) { - // This is a bit cheating, we're not starting a full node, we're just calling the code nodes call - // when registering. - NetworkRegistrationHelper(configuration, HTTPNetworkRegistrationService(compatibilityZoneURL)).buildKeystore() - doneFuture(Unit) + executorService.fork { + NetworkRegistrationHelper(config.corda, HTTPNetworkRegistrationService(compatibilityZoneURL)).buildKeystore() + config + } } else { - startOutOfProcessNodeRegistration(config, configuration) + startOutOfProcessMiniNode(config, "--initial-registration").map { config } } } @@ -247,7 +255,9 @@ class DriverDSLImpl( } internal fun startCordformNodes(cordforms: List): 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() val notaryInfos = ArrayList() @@ -266,23 +276,23 @@ class DriverDSLImpl( clusterNodes.put(ClusterType.NON_VALIDATING_BFT, name) } else { // 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) } } clusterNodes.asMap().forEach { type, nodeNames -> - val identity = IdentityGenerator.generateDistributedNotaryIdentity( + val identity = DevIdentityGenerator.generateDistributedNotaryIdentity( dirs = nodeNames.map { baseDirectory(it) }, notaryName = type.clusterName ) notaryInfos += NotaryInfo(identity, type.validating) } - networkParameters = NetworkParametersCopier(testNetworkParameters(notaryInfos)) + val localNetworkMap = LocalNetworkMap(notaryInfos) return cordforms.map { - val startedNode = startCordformNode(it) + val startedNode = startCordformNode(it, localNetworkMap) if (it.webAddress != null) { // Start a webserver if an address for it was specified startedNode.flatMap { startWebserver(it) } @@ -292,21 +302,21 @@ class DriverDSLImpl( }.transpose() } - private fun startCordformNode(cordform: CordformNode): CordaFuture { + private fun startCordformNode(cordform: CordformNode, localNetworkMap: LocalNetworkMap): CordaFuture { 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 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 notary = if (cordform.notary != null) mapOf("notary" to cordform.notary) else emptyMap() val rpcUsers = cordform.rpcUsers - val config = ConfigHelper.loadConfig( + val config = NodeConfig(ConfigHelper.loadConfig( baseDirectory = baseDirectory(name), allowMissingConfig = true, configOverrides = cordform.config + rpcAddress + notary + mapOf( "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 { @@ -340,31 +350,80 @@ class DriverDSLImpl( } _executorService = Executors.newScheduledThreadPool(2, ThreadFactoryBuilder().setNameFormat("driver-pool-thread-%d").build()) _shutdownManager = ShutdownManager(executorService) - 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 - nodeInfoFilesCopier = NodeInfoFilesCopier().also { - shutdownManager.registerShutdown(it::close) + val notaryInfosFuture = if (compatibilityZone == null) { + // If no CZ is specified then the driver does the generation of the network parameters and the copying of the + // node info files. + 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 { - return notarySpecs.map { spec -> - val identity = if (spec.cluster == null) { - IdentityGenerator.generateNodeIdentity(baseDirectory(spec.name), spec.name, compatibilityZone?.rootCert) - } else { - IdentityGenerator.generateDistributedNotaryIdentity( - dirs = generateNodeNames(spec).map { baseDirectory(it) }, - notaryName = spec.name, - customRootCert = compatibilityZone?.rootCert - ) + private fun startNotaryIdentityGeneration(): CordaFuture> { + return executorService.fork { + notarySpecs.map { spec -> + val identity = if (spec.cluster == null) { + DevIdentityGenerator.installKeyStoreWithNodeIdentity(baseDirectory(spec.name), spec.name) + } else { + DevIdentityGenerator.generateDistributedNotaryIdentity( + dirs = generateNodeNames(spec).map { baseDirectory(it) }, + notaryName = spec.name + ) + } + NotaryInfo(identity, spec.validating) + } + } + } + + private fun startAllNotaryRegistrations(rootCert: X509Certificate, compatibilityZoneURL: URL): CordaFuture> { + // 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 { + 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().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") } } - private fun startNotaries(): List>> { + private fun startNotaries(localNetworkMap: LocalNetworkMap?): List>> { return notarySpecs.map { when { - it.cluster == null -> startSingleNotary(it) - it.cluster is ClusterSpec.Raft -> startRaftNotaryCluster(it) + it.cluster == null -> startSingleNotary(it, localNetworkMap) + it.cluster is ClusterSpec.Raft -> startRaftNotaryCluster(it, localNetworkMap) 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() private fun NotaryConfig.toConfigMap(): Map = mapOf("notary" to toConfig().root().unwrapped()) - private fun startSingleNotary(spec: NotarySpec): CordaFuture> { - return startNode( - providedName = spec.name, - rpcUsers = spec.rpcUsers, - verifierType = spec.verifierType, + private fun startSingleNotary(spec: NotarySpec, localNetworkMap: LocalNetworkMap?): CordaFuture> { + return startRegisteredNode( + spec.name, + localNetworkMap, + spec.rpcUsers, + spec.verifierType, customOverrides = NotaryConfig(spec.validating).toConfigMap() ).map { listOf(it) } } - private fun startRaftNotaryCluster(spec: NotarySpec): CordaFuture> { + private fun startRaftNotaryCluster(spec: NotarySpec, localNetworkMap: LocalNetworkMap?): CordaFuture> { fun notaryConfig(nodeAddress: NetworkHostAndPort, clusterAddress: NetworkHostAndPort? = null): Map { val clusterAddresses = if (clusterAddress != null) listOf(clusterAddress) else emptyList() val config = NotaryConfig( @@ -408,20 +468,22 @@ class DriverDSLImpl( val clusterAddress = portAllocation.nextHostAndPort() // Start the first node that will bootstrap the cluster - val firstNodeFuture = startNode( - providedName = nodeNames[0], - rpcUsers = spec.rpcUsers, - verifierType = spec.verifierType, + val firstNodeFuture = startRegisteredNode( + nodeNames[0], + localNetworkMap, + spec.rpcUsers, + spec.verifierType, customOverrides = notaryConfig(clusterAddress) ) // All other nodes will join the cluster val restNodeFutures = nodeNames.drop(1).map { val nodeAddress = portAllocation.nextHostAndPort() - startNode( - providedName = it, - rpcUsers = spec.rpcUsers, - verifierType = spec.verifierType, + startRegisteredNode( + it, + localNetworkMap, + spec.rpcUsers, + spec.verifierType, customOverrides = notaryConfig(nodeAddress, clusterAddress) ) } @@ -436,8 +498,6 @@ class DriverDSLImpl( 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 networkMapCacheChangeObservable an observable returning the updates to the node network map. @@ -447,7 +507,7 @@ class DriverDSLImpl( private fun nodeCountObservable(initial: Int, networkMapCacheChangeObservable: Observable): ConnectableObservable { val count = AtomicInteger(initial) - return networkMapCacheChangeObservable.map { it -> + return networkMapCacheChangeObservable.map { when (it) { is NetworkMapCache.MapChange.Added -> count.incrementAndGet() is NetworkMapCache.MapChange.Removed -> count.decrementAndGet() @@ -482,31 +542,44 @@ class DriverDSLImpl( return future } - private fun startOutOfProcessNodeRegistration(config: Config, configuration: NodeConfiguration): CordaFuture { + /** + * 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 { val debugPort = if (isDebug) debugPortAllocation.nextPort() else null val monitorPort = if (jmxPolicy.startJmxHttpServer) jmxPolicy.jmxHttpServerPortAllocation?.nextPort() else null - val process = startOutOfProcessNode(configuration, config, quasarJarPath, debugPort, jolokiaJarPath, monitorPort, - systemProperties, cordappPackages, "200m", initialRegistration = true) + val process = startOutOfProcessNode( + 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 } } - private fun startNodeInternal(config: Config, + private fun startNodeInternal(config: NodeConfig, webAddress: NetworkHostAndPort, startInProcess: Boolean?, - maximumHeapSize: String): CordaFuture { - val configuration = config.parseAsNodeConfiguration() - val baseDirectory = configuration.baseDirectory.createDirectories() - nodeInfoFilesCopier?.addConfig(baseDirectory) - networkParameters?.install(baseDirectory) + maximumHeapSize: String, + localNetworkMap: LocalNetworkMap?): CordaFuture { + val baseDirectory = config.corda.baseDirectory.createDirectories() + localNetworkMap?.networkParametersCopier?.install(baseDirectory) + localNetworkMap?.nodeInfosCopier?.addConfig(baseDirectory) val onNodeExit: () -> Unit = { - nodeInfoFilesCopier?.removeConfig(baseDirectory) - countObservables.remove(configuration.myLegalName) + localNetworkMap?.nodeInfosCopier?.removeConfig(baseDirectory) + countObservables.remove(config.corda.myLegalName) } if (startInProcess ?: startNodesInProcess) { - val nodeAndThreadFuture = startInProcessNode(executorService, configuration, config, cordappPackages) + val nodeAndThreadFuture = startInProcessNode(executorService, config, cordappPackages) shutdownManager.registerShutdown( nodeAndThreadFuture.map { (node, thread) -> { @@ -516,16 +589,16 @@ class DriverDSLImpl( } ) return nodeAndThreadFuture.flatMap { (node, thread) -> - establishRpc(configuration, openFuture()).flatMap { rpc -> + establishRpc(config, openFuture()).flatMap { rpc -> 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 { val debugPort = if (isDebug) debugPortAllocation.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) { state.locked { processes += process @@ -533,22 +606,21 @@ class DriverDSLImpl( } else { shutdownManager.registerProcessShutdown(process) } - val p2pReadyFuture = addressMustBeBoundFuture(executorService, configuration.p2pAddress, process) + val p2pReadyFuture = addressMustBeBoundFuture(executorService, config.corda.p2pAddress, process) 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 } - 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: val networkMapFuture = executorService.fork { allNodesConnected(rpc) }.flatMap { it } firstOf(processDeathFuture, networkMapFuture) { if (it == processDeathFuture) { - throw ListenProcessDeathException(configuration.p2pAddress, process) + throw ListenProcessDeathException(config.corda.p2pAddress, process) } processDeathFuture.cancel(false) log.info("Node handle is ready. NodeInfo: ${rpc.nodeInfo()}, WebAddress: $webAddress") - NodeHandle.OutOfProcess(rpc.nodeInfo(), rpc, configuration, webAddress, debugPort, process, - onNodeExit) + NodeHandle.OutOfProcess(rpc.nodeInfo(), rpc, config.corda, webAddress, debugPort, process, onNodeExit) } } } @@ -561,6 +633,25 @@ class DriverDSLImpl( 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) { + 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 { internal val log = contextLogger() @@ -588,28 +679,26 @@ class DriverDSLImpl( private fun startInProcessNode( executorService: ScheduledExecutorService, - nodeConf: NodeConfiguration, - config: Config, + config: NodeConfig, cordappPackages: List ): CordaFuture, Thread>> { 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 - writeConfig(nodeConf.baseDirectory, "node.conf", config) + writeConfig(config.corda.baseDirectory, "node.conf", config.typesafe) // TODO pass the version in? - val node = InProcessNode(nodeConf, MOCK_VERSION_INFO, cordappPackages).start() - val nodeThread = thread(name = nodeConf.myLegalName.organisation) { + val node = InProcessNode(config.corda, MOCK_VERSION_INFO, cordappPackages).start() + val nodeThread = thread(name = config.corda.myLegalName.organisation) { node.internals.run() } node to nodeThread }.flatMap { nodeAndThread -> - addressMustBeBoundFuture(executorService, nodeConf.p2pAddress).map { nodeAndThread } + addressMustBeBoundFuture(executorService, config.corda.p2pAddress).map { nodeAndThread } } } private fun startOutOfProcessNode( - nodeConf: NodeConfiguration, - config: Config, + config: NodeConfig, quasarJarPath: String, debugPort: Int?, jolokiaJarPath: String, @@ -617,15 +706,17 @@ class DriverDSLImpl( overriddenSystemProperties: Map, cordappPackages: List, maximumHeapSize: String, - initialRegistration: Boolean + extraCmdLineFlag: String? ): 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 - writeConfig(nodeConf.baseDirectory, "node.conf", config) + writeConfig(config.corda.baseDirectory, "node.conf", config.typesafe) val systemProperties = mutableMapOf( - "name" to nodeConf.myLegalName, - "visualvm.display.name" to "corda-${nodeConf.myLegalName}", + "name" to config.corda.myLegalName, + "visualvm.display.name" to "corda-${config.corda.myLegalName}", "java.io.tmpdir" to System.getProperty("java.io.tmpdir"), // Inherit from parent process "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**;" + "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.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**;org.jolokia**;)" val extraJvmArguments = systemProperties.removeResolvedClasspath().map { "-D${it.key}=${it.value}" } + "-javaagent:$quasarJarPath=$excludePattern" val jolokiaAgent = monitorPort?.let { "-javaagent:$jolokiaJarPath=port=$monitorPort,host=localhost" } val loggingLevel = if (debugPort == null) "INFO" else "DEBUG" val arguments = mutableListOf( - "--base-directory=${nodeConf.baseDirectory}", + "--base-directory=${config.corda.baseDirectory}", "--logging-level=$loggingLevel", "--no-local-shell").also { - if (initialRegistration) { - it += "--initial-registration" + if (extraCmdLineFlag != null) { + it += extraCmdLineFlag } }.toList() @@ -663,8 +754,8 @@ class DriverDSLImpl( arguments = arguments, jdwpPort = debugPort, extraJvmArguments = extraJvmArguments + listOfNotNull(jolokiaAgent), - errorLogPath = nodeConf.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME / "error.log", - workingDirectory = nodeConf.baseDirectory, + errorLogPath = config.corda.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME / "error.log", + workingDirectory = config.corda.baseDirectory, maximumHeapSize = maximumHeapSize ) } @@ -831,11 +922,16 @@ fun 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 rootCert If specified then the node will register itself using [url] and expect the registration response - * to be rooted at this cert. + * @property publishNotaries Hook for a network map server to capture the generated [NotaryInfo] objects needed for + * 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) -> Unit, val rootCert: X509Certificate? = null) fun internalDriver( isDebug: Boolean = DriverParameters().isDebug, @@ -875,8 +971,7 @@ fun internalDriver( } fun getTimestampAsDirectoryName(): String { - // Add a random number in case 2 tests are started in the same instant. - return DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(ZoneOffset.UTC).format(Instant.now()) + random63BitValue() + return DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss.SSS").withZone(UTC).format(Instant.now()) } fun writeConfig(path: Path, filename: String, config: Config) { diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt index cf7cb48fa0..345eaf84b8 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt @@ -1,22 +1,22 @@ package net.corda.testing.node.internal.network import net.corda.core.crypto.* +import net.corda.core.identity.CordaX500Name import net.corda.core.internal.cert import net.corda.core.internal.toX509CertHolder import net.corda.core.node.NodeInfo import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize 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.NetworkMap import net.corda.nodeapi.internal.network.NetworkParameters 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 org.bouncycastle.asn1.x500.X500Name import org.eclipse.jetty.server.Server import org.eclipse.jetty.server.ServerConnector import org.eclipse.jetty.server.handler.HandlerCollection @@ -36,26 +36,36 @@ import javax.ws.rs.core.Response.ok class NetworkMapServer(cacheTimeout: Duration, 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 { companion object { - val stubNetworkParameter = NetworkParameters(1, emptyList(), 10485760, 40000, Instant.now(), 10) - private val serializedParameters = stubNetworkParameter.serialize() + private val stubNetworkParameters = NetworkParameters(1, emptyList(), 10485760, 40000, Instant.now(), 10) private fun networkMapKeyAndCert(rootCAKeyAndCert: CertificateAndKeyPair): CertificateAndKeyPair { val networkMapKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val networkMapCert = X509Utilities.createCertificate( - CertificateType.INTERMEDIATE_CA, + CertificateType.NETWORK_MAP, rootCAKeyAndCert.certificate, rootCAKeyAndCert.keyPair, - X500Name("CN=Corda Network Map,L=London"), + CordaX500Name("Corda Network Map", "R3 Ltd", "London","GB"), 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) } } 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 { server = Server(InetSocketAddress(hostAndPort.host, hostAndPort.port)).apply { @@ -95,13 +105,13 @@ class NetworkMapServer(cacheTimeout: Duration, } @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() - private val parametersHash = serializedParameters.hash - private val signedParameters = SignedData( + private val parametersHash by lazy { serializedParameters.hash } + private val signedParameters by lazy { SignedData( 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 @Path("publish") @@ -151,7 +161,7 @@ class NetworkMapServer(cacheTimeout: Duration, @GET @Path("my-hostname") fun getHostName(): Response { - return Response.ok("test.host.name").build() + return Response.ok(myHostNameValue).build() } } } diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt index 860af415ac..362672ce89 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt @@ -5,27 +5,24 @@ package net.corda.testing import net.corda.core.contracts.PartyAndReference 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.Party import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.cert import net.corda.core.internal.unspecifiedCountry -import net.corda.core.internal.x500Name import net.corda.core.node.NodeInfo import net.corda.core.utilities.NetworkHostAndPort -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.X509CertificateFactory 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 java.math.BigInteger -import java.nio.file.Files import java.security.KeyPair import java.security.PublicKey import java.util.concurrent.atomic.AtomicInteger @@ -78,29 +75,11 @@ fun getFreeLocalPorts(hostName: String, numberToAlloc: Int): List rigorousMock(clazz: Class): T = Mockito.mock(clazz) { 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 { + 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 { + val (rootCa, intermediateCa) = createDevIntermediateCaCertPath(rootCaName, intermediateCaName) + val nodeCa = createDevNodeCa(intermediateCa, legalName) + return Triple(rootCa, intermediateCa, nodeCa) +} diff --git a/tools/bootstrapper/build.gradle b/tools/bootstrapper/build.gradle index dc578b2c76..74a2c961d8 100644 --- a/tools/bootstrapper/build.gradle +++ b/tools/bootstrapper/build.gradle @@ -6,7 +6,10 @@ configurations { 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) { applicationClass 'net.corda.nodeapi.internal.network.NetworkBootstrapper' archiveName "network-bootstrapper.jar" @@ -16,6 +19,9 @@ task buildBootstrapperJar(type: FatCapsule, dependsOn: project(':node-api').comp minJavaVersion = '1.8.0' jvmArgs = ['-XX:+UseG1GC'] } + from(project(':node:capsule').tasks['buildCordaJAR']) { + rename 'corda-(.*)', 'corda.jar' + } applicationSource = files( project(':node-api').configurations.runtime, project(':node-api').jar diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt index ee6b38fdb5..fddcfdf19a 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt @@ -13,7 +13,7 @@ import net.corda.demobench.pty.R3Pty import net.corda.nodeapi.internal.network.NetworkParameters import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.nodeapi.internal.network.NotaryInfo -import net.corda.nodeapi.internal.IdentityGenerator +import net.corda.nodeapi.internal.DevIdentityGenerator import tornadofx.* import java.io.IOException 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. private fun getNotaryIdentity(config: NodeConfigWrapper): Party { - return IdentityGenerator.generateNodeIdentity(config.nodeDir, config.nodeConfig.myLegalName) + return DevIdentityGenerator.installKeyStoreWithNodeIdentity(config.nodeDir, config.nodeConfig.myLegalName) } fun reset() {