diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 67675cf9a5..d5523e52b5 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -57,6 +57,8 @@
+
+
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 f2c0a45918..ccae80d47e 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt
@@ -63,21 +63,21 @@ import javax.crypto.spec.SecretKeySpec
*/
object Crypto {
/**
- * RSA_SHA256 signature scheme using SHA256 as hash algorithm and MGF1 (with SHA256) as mask generation function.
+ * RSA_SHA256 signature scheme using SHA256 as hash algorithm.
* Note: Recommended key size >= 3072 bits.
*/
@JvmField
val RSA_SHA256 = SignatureScheme(
1,
"RSA_SHA256",
- AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, null),
- emptyList(),
+ AlgorithmIdentifier(PKCSObjectIdentifiers.sha256WithRSAEncryption, null),
+ listOf(AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, null)),
BouncyCastleProvider.PROVIDER_NAME,
"RSA",
- "SHA256WITHRSAANDMGF1",
+ "SHA256WITHRSAEncryption",
null,
3072,
- "RSA_SHA256 signature scheme using SHA256 as hash algorithm and MGF1 (with SHA256) as mask generation function."
+ "RSA_SHA256 signature scheme using SHA256 as hash algorithm."
)
/** ECDSA signature scheme using the secp256k1 Koblitz curve. */
@@ -117,7 +117,7 @@ object Crypto {
"EDDSA_ED25519_SHA512",
// OID taken from https://tools.ietf.org/html/draft-ietf-curdle-pkix-00
AlgorithmIdentifier(ASN1ObjectIdentifier("1.3.101.112"), null),
- emptyList(),
+ emptyList(), // Both keys and the signature scheme use the same OID in i2p library.
// We added EdDSA to bouncy castle for certificate signing.
BouncyCastleProvider.PROVIDER_NAME,
"1.3.101.112",
diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt
index 8c9fba60de..a10a3d3fe2 100644
--- a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt
+++ b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt
@@ -348,6 +348,12 @@ abstract class FlowLogic {
}
}
+ /**
+ * Returns a pair of the current progress step index (as integer) in steps tree of current [progressTracker], and an observable
+ * of its upcoming changes.
+ *
+ * @return Returns null if this flow has no progress tracker.
+ */
fun trackStepsTreeIndex(): DataFeed? {
// TODO this is not threadsafe, needs an atomic get-step-and-subscribe
return progressTracker?.let {
@@ -355,6 +361,12 @@ abstract class FlowLogic {
}
}
+ /**
+ * Returns a pair of the current steps tree of current [progressTracker] as pairs of zero-based depth and stringified step
+ * label and observable of upcoming changes to the structure.
+ *
+ * @return Returns null if this flow has no progress tracker.
+ */
fun trackStepsTree(): DataFeed>, List>>? {
// TODO this is not threadsafe, needs an atomic get-step-and-subscribe
return progressTracker?.let {
diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt
index 562c3f916d..63582c164b 100644
--- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt
+++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt
@@ -304,6 +304,6 @@ fun TransactionBuilder.toWireTransaction(services: ServicesForResolution, serial
fun TransactionBuilder.toLedgerTransaction(services: ServiceHub, serializationContext: SerializationContext) = toLedgerTransactionWithContext(services, serializationContext)
/** Convenience method to get the package name of a class literal. */
-val KClass<*>.packageName get() = java.`package`.name
+val KClass<*>.packageName: String get() = java.`package`.name
fun URL.openHttpConnection(): HttpURLConnection = openConnection() as HttpURLConnection
diff --git a/core/src/main/kotlin/net/corda/core/internal/schemas/NodeInfoSchema.kt b/core/src/main/kotlin/net/corda/core/internal/schemas/NodeInfoSchema.kt
index b2e73bb359..9cb84357b5 100644
--- a/core/src/main/kotlin/net/corda/core/internal/schemas/NodeInfoSchema.kt
+++ b/core/src/main/kotlin/net/corda/core/internal/schemas/NodeInfoSchema.kt
@@ -37,7 +37,7 @@ object NodeInfoSchemaV1 : MappedSchema(
@Column(name = "legal_identities_certs")
@ManyToMany(cascade = arrayOf(CascadeType.ALL))
- @JoinTable(name = "link_nodeinfo_party",
+ @JoinTable(name = "node_link_nodeinfo_party",
joinColumns = arrayOf(JoinColumn(name = "node_info_id")),
inverseJoinColumns = arrayOf(JoinColumn(name = "party_name")))
val legalIdentitiesAndCerts: List,
diff --git a/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt b/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt
index 90ba7e2596..84d734db29 100644
--- a/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt
+++ b/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt
@@ -8,13 +8,17 @@ import rx.Observable
/**
* [FlowHandle] is a serialisable handle for the started flow, parameterised by the type of the flow's return value.
- *
- * @property id The started state machine's ID.
- * @property returnValue A [CordaFuture] of the flow's return value.
*/
@DoNotImplement
interface FlowHandle : AutoCloseable {
+ /**
+ * The started state machine's ID.
+ */
val id: StateMachineRunId
+
+ /**
+ * A [CordaFuture] of the flow's return value.
+ */
val returnValue: CordaFuture
/**
@@ -25,15 +29,23 @@ interface FlowHandle : AutoCloseable {
/**
* [FlowProgressHandle] is a serialisable handle for the started flow, parameterised by the type of the flow's return value.
- *
- * @property progress The stream of progress tracker events.
*/
interface FlowProgressHandle : FlowHandle {
+ /**
+ * The stream of progress tracker events.
+ */
val progress: Observable
+ /**
+ * [DataFeed] of current step in the steps tree, see [ProgressTracker]
+ */
val stepsTreeIndexFeed: DataFeed?
+ /**
+ * [DataFeed] of current steps tree, see [ProgressTracker]
+ */
val stepsTreeFeed: DataFeed>, List>>?
+
/**
* Use this function for flows whose returnValue and progress are not going to be used or tracked, so as to free up
* server resources.
diff --git a/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt b/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt
index 22661eeb7b..8ce440ead3 100644
--- a/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt
+++ b/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt
@@ -99,6 +99,7 @@ class ProgressTracker(vararg steps: Step) {
field = value
}
+ /** The zero-bases index of the current step in a [allStepsLabels] list */
var stepsTreeIndex: Int = -1
private set(value) {
field = value
@@ -226,6 +227,10 @@ class ProgressTracker(vararg steps: Step) {
*/
val allSteps: List> get() = _allStepsCache
+ /**
+ * A list of all steps label in this ProgressTracker and the children, with the indent level provided starting at zero.
+ * Note that UNSTARTED is never counted, and DONE is only counted at the calling level.
+ */
val allStepsLabels: List> get() = _allStepsLabels()
private var curChangeSubscription: Subscription? = null
@@ -245,8 +250,14 @@ class ProgressTracker(vararg steps: Step) {
*/
val changes: Observable get() = _changes
+ /**
+ * An observable stream of changes to the [allStepsLabels]
+ */
val stepsTreeChanges: Observable>> get() = _stepsTreeChanges
+ /**
+ * An observable stream of changes to the [stepsTreeIndex]
+ */
val stepsTreeIndexChanges: Observable get() = _stepsTreeIndexChanges
/** Returns true if the progress tracker has ended, either by reaching the [DONE] step or prematurely with an error */
diff --git a/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt b/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt
index 60b5306cb6..88724674f1 100644
--- a/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt
+++ b/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt
@@ -8,7 +8,7 @@ import net.corda.core.internal.div
import net.corda.core.serialization.serialize
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.toBase58String
-import net.corda.node.utilities.*
+import net.corda.nodeapi.internal.crypto.*
import net.corda.testing.kryoSpecific
import net.corda.testing.SerializationEnvironmentRule
import org.junit.Rule
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 88854550a4..b3b1f332e2 100644
--- a/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt
+++ b/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt
@@ -3,7 +3,7 @@ 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.node.utilities.*
+import net.corda.nodeapi.internal.crypto.*
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralSubtree
@@ -50,7 +50,7 @@ class X509NameConstraintsTest {
val nameConstraints = NameConstraints(acceptableNames, arrayOf())
val pathValidator = CertPathValidator.getInstance("PKIX")
- val certFactory = CertificateFactory.getInstance("X509")
+ val certFactory = X509CertificateFactory().delegate
assertFailsWith(CertPathValidatorException::class) {
val (keystore, trustStore) = makeKeyStores(X500Name("CN=Bank B"), nameConstraints)
@@ -85,7 +85,7 @@ class X509NameConstraintsTest {
.map { GeneralSubtree(GeneralName(X500Name(it))) }.toTypedArray()
val nameConstraints = NameConstraints(acceptableNames, arrayOf())
- val certFactory = CertificateFactory.getInstance("X509")
+ val certFactory = X509CertificateFactory().delegate
Crypto.ECDSA_SECP256R1_SHA256
val pathValidator = CertPathValidator.getInstance("PKIX", BouncyCastleProvider.PROVIDER_NAME)
diff --git a/core/src/test/kotlin/net/corda/core/identity/PartyAndCertificateTest.kt b/core/src/test/kotlin/net/corda/core/identity/PartyAndCertificateTest.kt
index 0b839c08e8..bd6ef220a8 100644
--- a/core/src/test/kotlin/net/corda/core/identity/PartyAndCertificateTest.kt
+++ b/core/src/test/kotlin/net/corda/core/identity/PartyAndCertificateTest.kt
@@ -4,8 +4,8 @@ import net.corda.core.crypto.entropyToKeyPair
import net.corda.core.internal.read
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
-import net.corda.node.utilities.KEYSTORE_TYPE
-import net.corda.node.utilities.save
+import net.corda.nodeapi.internal.crypto.KEYSTORE_TYPE
+import net.corda.nodeapi.internal.crypto.save
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.getTestPartyAndCertificate
import org.assertj.core.api.Assertions.assertThat
diff --git a/core/src/test/kotlin/net/corda/core/utilities/ProgressTrackerTest.kt b/core/src/test/kotlin/net/corda/core/utilities/ProgressTrackerTest.kt
index 9bcf5c0fb5..4e15f122d9 100644
--- a/core/src/test/kotlin/net/corda/core/utilities/ProgressTrackerTest.kt
+++ b/core/src/test/kotlin/net/corda/core/utilities/ProgressTrackerTest.kt
@@ -98,7 +98,7 @@ class ProgressTrackerTest {
val allSteps = pt.allSteps
- //capture notifications
+ // Capture notifications.
val stepsIndexNotifications = LinkedList()
pt.stepsTreeIndexChanges.subscribe {
stepsIndexNotifications += it
@@ -113,7 +113,7 @@ class ProgressTrackerTest {
assertEquals(step, allSteps[pt.stepsTreeIndex].second)
}
- //travel tree
+ // Travel tree.
pt.currentStep = SimpleSteps.ONE
assertCurrentStepsTree(0, SimpleSteps.ONE)
@@ -126,7 +126,7 @@ class ProgressTrackerTest {
pt.currentStep = SimpleSteps.THREE
assertCurrentStepsTree(5, SimpleSteps.THREE)
- //assert no structure changes and proper steps propagation
+ // Assert no structure changes and proper steps propagation.
assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(0, 1, 3, 5))
assertThat(stepsTreeNotification).isEmpty()
}
@@ -135,13 +135,13 @@ class ProgressTrackerTest {
fun `structure changes are pushed down when progress trackers are added`() {
pt.setChildProgressTracker(SimpleSteps.TWO, pt2)
- //capture notifications
+ // Capture notifications.
val stepsIndexNotifications = LinkedList()
pt.stepsTreeIndexChanges.subscribe {
stepsIndexNotifications += it
}
- //put current state as a first change for simplicity when asserting
+ // Put current state as a first change for simplicity when asserting.
val stepsTreeNotification = mutableListOf(pt.allStepsLabels)
println(pt.allStepsLabels)
pt.stepsTreeChanges.subscribe {
@@ -164,7 +164,7 @@ class ProgressTrackerTest {
assertCurrentStepsTree(9, SimpleSteps.FOUR)
- //assert no structure changes and proper steps propagation
+ // Assert no structure changes and proper steps propagation.
assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(1, 6, 9))
assertThat(stepsTreeNotification).hasSize(2) // 1 change + 1 our initial state
}
@@ -173,13 +173,13 @@ class ProgressTrackerTest {
fun `structure changes are pushed down when progress trackers are removed`() {
pt.setChildProgressTracker(SimpleSteps.TWO, pt2)
- //capture notifications
+ // Capture notifications.
val stepsIndexNotifications = LinkedList()
pt.stepsTreeIndexChanges.subscribe {
stepsIndexNotifications += it
}
- //put current state as a first change for simplicity when asserting
+ // Put current state as a first change for simplicity when asserting.
val stepsTreeNotification = mutableListOf(pt.allStepsLabels)
pt.stepsTreeChanges.subscribe {
stepsTreeNotification += it
@@ -199,9 +199,9 @@ class ProgressTrackerTest {
assertCurrentStepsTree(2, BabySteps.UNOS)
- //assert no structure changes and proper steps propagation
+ // Assert no structure changes and proper steps propagation.
assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(1, 4, 2))
- assertThat(stepsTreeNotification).hasSize(2) // 1 change + 1 our initial state
+ assertThat(stepsTreeNotification).hasSize(2) // 1 change + 1 our initial state.
}
@Test
diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst
index a8986d15eb..8404bd64c0 100644
--- a/docs/source/changelog.rst
+++ b/docs/source/changelog.rst
@@ -91,6 +91,9 @@ UNRELEASED
* ``waitForAllNodesToFinish()`` method in ``DriverDSLExposedInterface`` has instead become a parameter on driver creation.
+* ``database.transactionIsolationLevel`` values now follow the ``java.sql.Connection`` int constants but without the
+ "TRANSACTION_" prefix, i.e. "NONE", "READ_UNCOMMITTED", etc.
+
.. _changelog_v1:
Release 1.0
diff --git a/docs/source/corda-configuration-file.rst b/docs/source/corda-configuration-file.rst
index 0531431cc5..3d346683ff 100644
--- a/docs/source/corda-configuration-file.rst
+++ b/docs/source/corda-configuration-file.rst
@@ -68,6 +68,13 @@ path to the node's base directory.
.. note:: Longer term these keys will be managed in secure hardware devices.
+:database: Database configuration:
+
+ :initDatabase: Boolean on whether to initialise the database or just validate the schema. Defaults to true.
+ :serverNameTablePrefix: Prefix string to apply to all the database tables. The default is no prefix.
+ :transactionIsolationLevel: Transaction isolation level as defined by the ``TRANSACTION_`` constants in
+ ``java.sql.Connection``, but without the "TRANSACTION_" prefix. Defaults to REPEATABLE_READ.
+
:dataSourceProperties: This section is used to configure the jdbc connection and database driver used for the nodes persistence.
Currently the defaults in ``/node/src/main/resources/reference.conf`` are as shown in the first example. This is currently
the only configuration that has been tested, although in the future full support for other storage layers will be validated.
diff --git a/docs/source/corda-networks-index.rst b/docs/source/corda-networks-index.rst
index abdcf7c0b5..9315a2de09 100644
--- a/docs/source/corda-networks-index.rst
+++ b/docs/source/corda-networks-index.rst
@@ -6,4 +6,5 @@ Corda networks
setting-up-a-corda-network
permissioning
- versioning
\ No newline at end of file
+ network-map
+ versioning
diff --git a/docs/source/network-map.rst b/docs/source/network-map.rst
index d8ee82a2cf..34356bad4f 100644
--- a/docs/source/network-map.rst
+++ b/docs/source/network-map.rst
@@ -1,6 +1,14 @@
Network Map
===========
+The network map stores a collection of ``NodeInfo`` objects, each representing another node with which the node can interact.
+There two sources from which a Corda node can retrieve ``NodeInfo`` objects:
+
+1. the REST protocol with the network map service, which also provides a publishing API,
+
+2. the ``additional-node-infos`` directory.
+
+
Protocol Design
---------------
The node info publishing protocol:
@@ -35,4 +43,15 @@ Network Map service REST API:
| GET | /api/network-map/parameters/{hash}| Retrieve ``NetworkParameters`` object with the same hash. |
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
-TODO: Access control of the network map will be added in the future.
\ No newline at end of file
+TODO: Access control of the network map will be added in the future.
+
+
+The ``additional-node-infos`` directory
+---------------------------------------
+Each Corda node reads, and continuously polls, the files contained in a directory named ``additional-node-infos`` inside the node base directory.
+
+Nodes expect to find a serialized ``SignedData`` object, the same object which is sent to network map server.
+
+Whenever a node starts it writes on disk a file containing its own ``NodeInfo``, this file is called ``nodeInfo-XXX`` where ``XXX`` is a long string.
+
+Hence if an operator wants node A to see node B they can pick B's ``NodeInfo`` file from B base directory and drop it into A's ``additional-node-infos`` directory.
diff --git a/docs/source/permissioning.rst b/docs/source/permissioning.rst
index 637b78ab50..dcffd0bb49 100644
--- a/docs/source/permissioning.rst
+++ b/docs/source/permissioning.rst
@@ -1,104 +1,124 @@
-Network permissioning (Doorman)
-===============================
+Network Permissioning
+=====================
-The keystore located in ``/certificates/sslkeystore.jks`` is required to connect to the Corda network securely.
-In development mode (when ``devMode = true``, see ":doc:`corda-configuration-file`" for more information) a pre-configured
-keystore will be used if the keystore does not exist. This is to ensure developers can get the nodes working as quickly
-as possible.
+.. contents::
-However this is not secure for the real network. This documentation will explain the procedure of obtaining a signed
-certificate for TestNet.
+Corda networks are *permissioned*. To connect to a network, a node needs three keystores in its
+``/certificates/`` folder:
-Initial Registration
---------------------
+* ``truststore.jks``, which stores trusted public keys and certificates (in our case, those of the network root CA)
+* ``nodekeystore.jks``, which stores the node’s identity keypairs and certificates
+* ``sslkeystore.jks``, which stores the node’s TLS keypairs and certificates
-The certificate signing request will be created based on node information obtained from the node configuration.
-The following information from the node configuration file is needed to generate the request.
+In development mode (i.e. when ``devMode = true``, see :doc:`corda-configuration-file` for more information),
+pre-configured keystores are used if the required keystores do not exist. This ensures that developers can get the
+nodes working as quickly as possible.
-:myLegalName: Your company's legal name as an X.500 string. X.500 allows differentiation between entities with the same
- name as the legal name needs to be unique on the network. If another node has already been permissioned with this
- name then the permissioning server will automatically reject the request. The request will also be rejected if it
- violates legal name rules, see `Legal Name Constraints`_ for more information.
+However, these pre-configured keystores are not secure. Production deployments require a secure certificate authority.
+Most production deployments will use an existing certificate authority or construct one using software that will be
+made available in the coming months. Until then, the documentation below can be used to create your own certificate
+authority.
-:emailAddress: e.g. "admin@company.com"
+Network structure
+-----------------
+A Corda network has three types of certificate authorities (CAs):
-:compatibilityZoneURL: Corda compatibility zone network management service root URL.
+* The **root network CA**
+* The **intermediate network CA**
-A new pair of private and public keys generated by the Corda node will be used to create the request.
+ * The intermediate network CA is used instead of the root network CA for day-to-day
+ key signing to reduce the risk of the root network CA's private key being compromised
-The utility will submit the request to the doorman server and poll for a result periodically to retrieve the certificates.
-Once the request has been approved and the certificates downloaded from the server, the node will create the keystore and trust store using the certificates and the generated private key.
+* The **node CAs**
-.. note:: You can exit the utility at any time if the approval process is taking longer than expected. The request process will resume on restart.
+ * Each node serves as its own CA in issuing the child certificates that it uses to sign its identity
+ keys, anonymous keys and TLS certificates
-This process only is needed when the node connects to the network for the first time, or when the certificate expires.
+Keypair and certificate formats
+-------------------------------
+You can use any standard key tools or Corda's ``X509Utilities`` (which uses Bouncy Castle) to create the required
+public/private keypairs and certificates. The keypairs and certificates should obey the following restrictions:
-Legal Name Constraints
-----------------------
-The legal name is the unique identifier in the Corda network, so constraints have been set out to prevent encoding attacks and visual spoofing.
+* The certificates must follow the `X.509 standard `_
-The legal name validator (see ``LegalNameValidator.kt``) is used to enforce rules on Corda's legal names, it is intended to be used by the network operator and Corda node during the node registration process.
-It has two functions, a function to normalize legal names, and a function to validate legal names.
+ * We recommend X.509 v3 for forward compatibility
-The normalize function performs the following transformations:
+* The TLS certificates must follow the `TLS v1.2 standard `_
-* Remove leading and trailing whitespaces.
+Creating the root and intermediate network CAs
+----------------------------------------------
-* Replace multiple whitespaces with a single space.
+Creating the root network CA's keystore and truststore
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-* Normalize the string according to `NFKC normalization form `_.
+1. Create a new keypair
-The validation function will validate the input string using the following rules:
+ * This will be used as the root network CA's keypair
-* No blacklisted words like "node", "server".
+2. Create a self-signed certificate for the keypair. The basic constraints extension must be set to ``true``
-* Restrict names to Latin scripts for now to avoid right-to-left issues, debugging issues when we can't pronounce names over the phone, and character confusability attacks.
+ * This will be used as the root network CA's certificate
-* Should start with a capital letter.
+3. Create a new keystore and store the root network CA's keypair and certificate in it for later use
-* No commas or equals signs.
+ * This keystore will be used by the root network CA to sign the intermediate network CA's certificate
-* No dollars or quote marks, although we may relax the quote mark constraint in future to handle Irish company names.
+4. Create a new Java keystore named ``truststore.jks`` and store the root network CA's certificate in it using the
+ alias ``cordarootca``
-Starting the Registration
--------------------------
+ * This keystore will be provisioned to the individual nodes later
-You will need to specify the working directory of your Corda node using ``--base-dir`` flag. This is defaulted to current directory if left blank.
-You can also specify the location of ``node.conf`` with ``--config-file`` flag if it's not in the working directory.
+.. warning:: The root network CA's private key should be protected and kept safe.
-**To start the registration**::
+Creating the intermediate network CA's keystore
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- java -jar corda.jar --initial-registration --base-dir <> --config-file <>
+1. Create a new keypair
-A ``certificates`` folder containing the keystore and trust store will be created in the base directory when the process is completed.
+ * This will be used as the intermediate network CA's keypair
-.. warning:: The keystore is protected by the keystore password from the node configuration file. The password should kept safe to protect the private key and certificate.
+2. Obtain a certificate for the keypair signed with the root network CA key. The basic constraints extension must be
+ set to ``true``
+ * This will be used as the intermediate network CA's certificate
-Protocol Design
----------------
-.. note:: This section is intended for developers who want to implement their own doorman service.
+3. Create a new keystore and store the intermediate network CA's keypair and certificate chain
+ (i.e. the intermediate network CA certificate *and* the root network CA certificate) in it for later use
-The certificate signing protocol:
+ * This keystore will be used by the intermediate network CA to sign the nodes' identity certificates
-* Generate a keypair, save it to disk.
+Creating the node CA keystores and TLS keystores
+------------------------------------------------
-* Generate a CSR using Bouncy Castle or the java crypto APIs containing myLegalName from the config file. We should also have an admin email address in the config file and CSR so we know who to email if anything goes wrong. Sign it with the private key.
+Creating the node CA keystores
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-* HTTPS POST the CSR to the doorman. It creates the server-side records of this request, allocates an ID for it, and then sends back an HTTP redirect to another URL that contains that request ID (which should be sufficiently large that it's not predictable or brute forceable).
+1. For each node, create a new keypair
-* Store that URL to disk.
+2. Obtain a certificate for the keypair signed with the intermediate network CA key. The basic constraints extension must be
+ set to ``true``
-* Server goes into a slow polling loop, in which every 10 minutes or so it fetches the URL it was given in the redirect. Mostly it will get 204 No Content. Eventually it will get 200 OK and download the signed certificate in binary form, which it can then stash in its local keystore file.
+3. Create a new Java keystore named ``nodekeystore.jks`` and store the keypair in it using the alias ``cordaclientca``
-The initial registration process uses the following web api to communicate with the doorman service:
+ * The node will store this keystore locally to sign its identity keys and anonymous keys
-+----------------+------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
-| Request method | Path | Description |
-+================+==============================+========================================================================================================================================================+
-| POST | /api/certificate | Create new certificate request record and stored for further approval process, server will response with a request ID if the request has been accepted.|
-+----------------+------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
-| GET | /api/certificate/{requestId} | Retrieve certificates for requestId, the server will return HTTP 204 if request is not yet approved or HTTP 401 if it has been rejected. |
-+----------------+------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
+Creating the node TLS keystores
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-See ``NetworkRegistrationHelper`` and ``X509Utilities`` for examples of certificate signing request creation and certificate signing using Bouncy Castle.
+1. For each node, create a new keypair
+
+2. Create a certificate for the keypair signed with the node CA key. The basic constraints extension must be set to
+ ``false``
+
+3. Create a new Java keystore named ``sslkeystore.jks`` and store the key and certificates in it using the alias
+ ``cordaclienttls``
+
+ * The node will store this keystore locally to sign its TLS certificates
+
+Installing the certificates on the nodes
+----------------------------------------
+For each node, copy the following files to the node's certificate directory (``/certificates/``):
+
+1. The node's ``nodekeystore.jks`` keystore
+2. The node's ``sslkeystore.jks`` keystore
+3. The root network CA's ``truststore.jks`` keystore
diff --git a/docs/source/shell.rst b/docs/source/shell.rst
index fe74e44f29..2fc11829b2 100644
--- a/docs/source/shell.rst
+++ b/docs/source/shell.rst
@@ -35,9 +35,9 @@ Shell can also be accessible via SSH. By default SSH server is *disabled*. To en
Authentication and authorization
--------------------------------
-SSH require user to login first - using the same users as RPC system. In fact, shell serves as a proxy to RPC and communicates
-with node using RPC calls. This also means that RPC permissions are enforced. No permissions are required to allow the connection
-and login in.
+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.
@@ -51,7 +51,7 @@ errors.
Connecting
----------
-Linux and MacOS computers usually come with SSH client preinstalled. On Windows it usually require extra download.
+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.
diff --git a/docs/source/tutorial-test-dsl.rst b/docs/source/tutorial-test-dsl.rst
index ce66251a5c..6771f077cb 100644
--- a/docs/source/tutorial-test-dsl.rst
+++ b/docs/source/tutorial-test-dsl.rst
@@ -35,14 +35,17 @@ We start with the empty ledger:
.. sourcecode:: java
- import static net.corda.core.testing.JavaTestHelpers.*;
- import static net.corda.core.contracts.JavaTestHelpers.*;
+ import org.junit.Test;
- @Test
- public void emptyLedger() {
- ledger(l -> {
- return Unit.INSTANCE; // We need to return this explicitly
- });
+ import static net.corda.testing.NodeTestUtils.ledger;
+
+ public class CommercialPaperTest {
+ @Test
+ public void emptyLedger() {
+ ledger(l -> {
+ return null;
+ });
+ }
}
The DSL keyword ``ledger`` takes a closure that can build up several transactions and may verify their overall
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/config/ConfigUtilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/config/ConfigUtilities.kt
index cd1118928c..19987de81c 100644
--- a/node-api/src/main/kotlin/net/corda/nodeapi/config/ConfigUtilities.kt
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/config/ConfigUtilities.kt
@@ -131,7 +131,13 @@ private fun Config.defaultToOldPath(property: KProperty<*>): String {
private fun parseEnum(enumType: Class<*>, name: String): Enum<*> = enumBridge(uncheckedCast(enumType), name) // Any enum will do
-private fun > enumBridge(clazz: Class, name: String): T = java.lang.Enum.valueOf(clazz, name)
+private fun > enumBridge(clazz: Class, name: String): T {
+ try {
+ return java.lang.Enum.valueOf(clazz, name)
+ } catch (e: IllegalArgumentException) {
+ throw IllegalArgumentException("$name is not one of { ${clazz.enumConstants.joinToString()} }")
+ }
+}
/**
* Convert the receiver object into a [Config]. This does the inverse action of [parseAs].
diff --git a/node/src/main/kotlin/net/corda/node/utilities/ContentSignerBuilder.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/ContentSignerBuilder.kt
similarity index 97%
rename from node/src/main/kotlin/net/corda/node/utilities/ContentSignerBuilder.kt
rename to node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/ContentSignerBuilder.kt
index f2a365bf89..bfcf1f6631 100644
--- a/node/src/main/kotlin/net/corda/node/utilities/ContentSignerBuilder.kt
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/ContentSignerBuilder.kt
@@ -1,4 +1,4 @@
-package net.corda.node.utilities
+package net.corda.nodeapi.internal.crypto
import net.corda.core.crypto.SignatureScheme
import org.bouncycastle.asn1.x509.AlgorithmIdentifier
diff --git a/node/src/main/kotlin/net/corda/node/utilities/KeyStoreUtilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/KeyStoreUtilities.kt
similarity index 77%
rename from node/src/main/kotlin/net/corda/node/utilities/KeyStoreUtilities.kt
rename to node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/KeyStoreUtilities.kt
index 0257302d6d..b49493ded2 100644
--- a/node/src/main/kotlin/net/corda/node/utilities/KeyStoreUtilities.kt
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/KeyStoreUtilities.kt
@@ -1,9 +1,8 @@
@file:JvmName("KeyStoreUtilities")
-package net.corda.node.utilities
+package net.corda.nodeapi.internal.crypto
import net.corda.core.crypto.Crypto
-import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.*
import org.bouncycastle.cert.X509CertificateHolder
import java.io.IOException
@@ -11,9 +10,7 @@ import java.io.InputStream
import java.io.OutputStream
import java.nio.file.Path
import java.security.*
-import java.security.cert.CertPath
import java.security.cert.Certificate
-import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
const val KEYSTORE_TYPE = "JKS"
@@ -169,44 +166,3 @@ fun KeyStore.getSupportedKey(alias: String, keyPassword: String): PrivateKey {
val key = getKey(alias, keyPass) as PrivateKey
return Crypto.toSupportedPrivateKey(key)
}
-
-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)
- // Assume key password = store password.
- val clientCA = certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
- // Create new keys and store in keystore.
- val cert = X509Utilities.createCertificate(CertificateType.IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, pubKey)
- val certPath = CertificateFactory.getInstance("X509").generateCertPath(listOf(cert.cert) + clientCertPath)
- require(certPath.certificates.isNotEmpty()) { "Certificate path cannot be empty" }
- // TODO: X509Utilities.validateCertificateChain()
- return certPath
- }
-
- fun signAndSaveNewKeyPair(serviceName: CordaX500Name, privateKeyAlias: String, keyPair: KeyPair) {
- val certPath = createCertificate(serviceName, keyPair.public)
- // Assume key password = store password.
- keyStore.addOrReplaceKey(privateKeyAlias, keyPair.private, storePassword.toCharArray(), certPath.certificates.toTypedArray())
- keyStore.save(storePath, storePassword)
- }
-
- fun savePublicKey(serviceName: CordaX500Name, pubKeyAlias: String, pubKey: PublicKey) {
- val certPath = createCertificate(serviceName, pubKey)
- // Assume key password = store password.
- keyStore.addOrReplaceCertificate(pubKeyAlias, certPath.certificates.first())
- keyStore.save(storePath, storePassword)
- }
-
- // Delegate methods to keystore. Sadly keystore doesn't have an interface.
- fun containsAlias(alias: String) = keyStore.containsAlias(alias)
-
- fun getX509Certificate(alias: String) = keyStore.getX509Certificate(alias)
-
- fun getCertificateChain(alias: String): Array = keyStore.getCertificateChain(alias)
-
- fun getCertificate(alias: String): Certificate = keyStore.getCertificate(alias)
-
- fun certificateAndKeyPair(alias: String): CertificateAndKeyPair = keyStore.getCertificateAndKeyPair(alias, storePassword)
-}
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
new file mode 100644
index 0000000000..3e0d526a58
--- /dev/null
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/KeyStoreWrapper.kt
@@ -0,0 +1,52 @@
+package net.corda.nodeapi.internal.crypto
+
+import net.corda.core.identity.CordaX500Name
+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
+import java.security.cert.CertificateFactory
+
+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)
+ // Assume key password = store password.
+ val clientCA = certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
+ // Create new keys and store in keystore.
+ val cert = X509Utilities.createCertificate(CertificateType.IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, pubKey)
+ val certPath = X509CertificateFactory().delegate.generateCertPath(listOf(cert.cert) + clientCertPath)
+ require(certPath.certificates.isNotEmpty()) { "Certificate path cannot be empty" }
+ // TODO: X509Utilities.validateCertificateChain()
+ return certPath
+ }
+
+ fun signAndSaveNewKeyPair(serviceName: CordaX500Name, privateKeyAlias: String, keyPair: KeyPair) {
+ val certPath = createCertificate(serviceName, keyPair.public)
+ // Assume key password = store password.
+ keyStore.addOrReplaceKey(privateKeyAlias, keyPair.private, storePassword.toCharArray(), certPath.certificates.toTypedArray())
+ keyStore.save(storePath, storePassword)
+ }
+
+ fun savePublicKey(serviceName: CordaX500Name, pubKeyAlias: String, pubKey: PublicKey) {
+ val certPath = createCertificate(serviceName, pubKey)
+ // Assume key password = store password.
+ keyStore.addOrReplaceCertificate(pubKeyAlias, certPath.certificates.first())
+ keyStore.save(storePath, storePassword)
+ }
+
+ // Delegate methods to keystore. Sadly keystore doesn't have an interface.
+ fun containsAlias(alias: String) = keyStore.containsAlias(alias)
+
+ fun getX509Certificate(alias: String) = keyStore.getX509Certificate(alias)
+
+ fun getCertificateChain(alias: String): Array = keyStore.getCertificateChain(alias)
+
+ fun getCertificate(alias: String): Certificate = keyStore.getCertificate(alias)
+
+ fun certificateAndKeyPair(alias: String): CertificateAndKeyPair = keyStore.getCertificateAndKeyPair(alias, storePassword)
+}
\ No newline at end of file
diff --git a/node/src/main/kotlin/net/corda/node/utilities/X509Utilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt
similarity index 76%
rename from node/src/main/kotlin/net/corda/node/utilities/X509Utilities.kt
rename to node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt
index 4dfa07ec09..def4ee9879 100644
--- a/node/src/main/kotlin/net/corda/node/utilities/X509Utilities.kt
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt
@@ -1,9 +1,11 @@
-package net.corda.node.utilities
+package net.corda.nodeapi.internal.crypto
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignatureScheme
import net.corda.core.crypto.random63BitValue
import net.corda.core.identity.CordaX500Name
+import net.corda.core.internal.read
+import net.corda.core.internal.write
import net.corda.core.internal.x500Name
import net.corda.core.utilities.days
import net.corda.core.utilities.millis
@@ -25,10 +27,10 @@ import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder
import org.bouncycastle.util.io.pem.PemReader
-import java.io.FileReader
import java.io.FileWriter
import java.io.InputStream
import java.math.BigInteger
+import java.nio.file.Files
import java.nio.file.Path
import java.security.KeyPair
import java.security.PublicKey
@@ -52,6 +54,7 @@ object X509Utilities {
const val CORDA_CLIENT_CA_CN = "Corda Client CA Certificate"
private val DEFAULT_VALIDITY_WINDOW = Pair(0.millis, 3650.days)
+
/**
* Helper function to return the latest out of an instant and an optional date.
*/
@@ -89,7 +92,9 @@ object X509Utilities {
* Create a de novo root self-signed X509 v3 CA cert.
*/
@JvmStatic
- fun createSelfSignedCACertificate(subject: CordaX500Name, keyPair: KeyPair, validityWindow: Pair = DEFAULT_VALIDITY_WINDOW): X509CertificateHolder {
+ fun createSelfSignedCACertificate(subject: CordaX500Name,
+ keyPair: KeyPair,
+ validityWindow: Pair = DEFAULT_VALIDITY_WINDOW): X509CertificateHolder {
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second)
return createCertificate(CertificateType.ROOT_CA, subject.x500Name, keyPair, subject.x500Name, keyPair.public, window)
}
@@ -114,8 +119,9 @@ object X509Utilities {
subject: CordaX500Name,
subjectPublicKey: PublicKey,
validityWindow: Pair = DEFAULT_VALIDITY_WINDOW,
- nameConstraints: NameConstraints? = null): X509CertificateHolder
- = createCertificate(certificateType, issuerCertificate, issuerKeyPair, subject.x500Name, subjectPublicKey, validityWindow, nameConstraints)
+ nameConstraints: NameConstraints? = null): X509CertificateHolder {
+ return createCertificate(certificateType, issuerCertificate, issuerKeyPair, subject.x500Name, subjectPublicKey, validityWindow, nameConstraints)
+ }
/**
* Create a X509 v3 certificate for use as a CA or for TLS. This does not require a [CordaX500Name] because the
@@ -145,10 +151,9 @@ object X509Utilities {
@Throws(CertPathValidatorException::class)
fun validateCertificateChain(trustedRoot: X509Certificate, vararg certificates: Certificate) {
require(certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" }
- val certFactory = CertificateFactory.getInstance("X509")
val params = PKIXParameters(setOf(TrustAnchor(trustedRoot, null)))
params.isRevocationEnabled = false
- val certPath = certFactory.generateCertPath(certificates.toList())
+ val certPath = X509CertificateFactory().delegate.generateCertPath(certificates.toList())
val pathValidator = CertPathValidator.getInstance("PKIX")
pathValidator.validate(certPath, params)
}
@@ -156,30 +161,29 @@ object X509Utilities {
/**
* Helper method to store a .pem/.cer format file copy of a certificate if required for import into a PC/Mac, or for inspection.
* @param x509Certificate certificate to save.
- * @param filename Target filename.
+ * @param file Target file.
*/
@JvmStatic
- fun saveCertificateAsPEMFile(x509Certificate: X509CertificateHolder, filename: Path) {
- FileWriter(filename.toFile()).use {
- JcaPEMWriter(it).use {
- it.writeObject(x509Certificate)
- }
+ fun saveCertificateAsPEMFile(x509Certificate: X509CertificateHolder, file: Path) {
+ JcaPEMWriter(file.toFile().writer()).use {
+ it.writeObject(x509Certificate)
}
}
/**
* Helper method to load back a .pem/.cer format file copy of a certificate.
- * @param filename Source filename.
+ * @param file Source file.
* @return The X509Certificate that was encoded in the file.
*/
@JvmStatic
- fun loadCertificateFromPEMFile(filename: Path): X509CertificateHolder {
- val reader = PemReader(FileReader(filename.toFile()))
- val pemObject = reader.readPemObject()
- val cert = X509CertificateHolder(pemObject.content)
- return cert.apply {
- isValidOn(Date())
+ fun loadCertificateFromPEMFile(file: Path): X509CertificateHolder {
+ val cert = file.read {
+ val reader = PemReader(it.reader())
+ val pemObject = reader.readPemObject()
+ X509CertificateHolder(pemObject.content)
}
+ cert.isValidOn(Date())
+ return cert
}
/**
@@ -243,13 +247,13 @@ object X509Utilities {
* @param validityWindow the time period the certificate is valid for.
* @param nameConstraints any name constraints to impose on certificates signed by the generated certificate.
*/
- internal fun createCertificate(certificateType: CertificateType,
- issuer: X500Name,
- issuerSigner: ContentSigner,
- subject: CordaX500Name,
- subjectPublicKey: PublicKey,
- validityWindow: Pair,
- nameConstraints: NameConstraints? = null): X509CertificateHolder {
+ fun createCertificate(certificateType: CertificateType,
+ issuer: X500Name,
+ issuerSigner: ContentSigner,
+ subject: CordaX500Name,
+ subjectPublicKey: PublicKey,
+ validityWindow: Pair,
+ nameConstraints: NameConstraints? = null): X509CertificateHolder {
val builder = createCertificate(certificateType, issuer, subject.x500Name, subjectPublicKey, validityWindow, nameConstraints)
return builder.build(issuerSigner).apply {
require(isValidOn(Date()))
@@ -266,11 +270,13 @@ object X509Utilities {
* @param validityWindow the time period the certificate is valid for.
* @param nameConstraints any name constraints to impose on certificates signed by the generated certificate.
*/
- internal fun createCertificate(certificateType: CertificateType, issuer: X500Name, issuerKeyPair: KeyPair,
- subject: X500Name, subjectPublicKey: PublicKey,
- validityWindow: Pair,
- nameConstraints: NameConstraints? = null): X509CertificateHolder {
-
+ fun createCertificate(certificateType: CertificateType,
+ issuer: X500Name,
+ issuerKeyPair: KeyPair,
+ subject: X500Name,
+ subjectPublicKey: PublicKey,
+ validityWindow: Pair,
+ nameConstraints: NameConstraints? = null): X509CertificateHolder {
val signatureScheme = Crypto.findSignatureScheme(issuerKeyPair.private)
val provider = Crypto.findProvider(signatureScheme.providerName)
val builder = createCertificate(certificateType, issuer, subject, subjectPublicKey, validityWindow, nameConstraints)
@@ -285,28 +291,71 @@ object X509Utilities {
/**
* Create certificate signing request using provided information.
*/
- internal fun createCertificateSigningRequest(subject: CordaX500Name, email: String, keyPair: KeyPair, signatureScheme: SignatureScheme): PKCS10CertificationRequest {
+ private fun createCertificateSigningRequest(subject: CordaX500Name,
+ email: String,
+ keyPair: KeyPair,
+ signatureScheme: SignatureScheme): PKCS10CertificationRequest {
val signer = ContentSignerBuilder.build(signatureScheme, keyPair.private, Crypto.findProvider(signatureScheme.providerName))
return JcaPKCS10CertificationRequestBuilder(subject.x500Name, keyPair.public).addAttribute(BCStyle.E, DERUTF8String(email)).build(signer)
}
- fun createCertificateSigningRequest(subject: CordaX500Name, email: String, keyPair: KeyPair) = createCertificateSigningRequest(subject, email, keyPair, DEFAULT_TLS_SIGNATURE_SCHEME)
+ fun createCertificateSigningRequest(subject: CordaX500Name, email: String, keyPair: KeyPair): PKCS10CertificationRequest {
+ return createCertificateSigningRequest(subject, email, keyPair, DEFAULT_TLS_SIGNATURE_SCHEME)
+ }
}
-
-class CertificateStream(val input: InputStream) {
- private val certificateFactory = CertificateFactory.getInstance("X.509")
-
- fun nextCertificate(): X509Certificate = certificateFactory.generateCertificate(input) as X509Certificate
+/**
+ * Wraps a [CertificateFactory] to remove boilerplate. It's unclear whether [CertificateFactory] is threadsafe so best
+ * so assume this class is not.
+ */
+class X509CertificateFactory {
+ val delegate: CertificateFactory = CertificateFactory.getInstance("X.509")
+ fun generateCertificate(input: InputStream): X509Certificate {
+ return delegate.generateCertificate(input) as X509Certificate
+ }
}
enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurposeId, val isCA: Boolean) {
- ROOT_CA(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true),
- INTERMEDIATE_CA(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true),
- CLIENT_CA(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true),
- TLS(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyEncipherment or KeyUsage.keyAgreement), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = false),
+ ROOT_CA(
+ KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign),
+ KeyPurposeId.id_kp_serverAuth,
+ KeyPurposeId.id_kp_clientAuth,
+ KeyPurposeId.anyExtendedKeyUsage,
+ isCA = true
+ ),
+
+ INTERMEDIATE_CA(
+ KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign),
+ KeyPurposeId.id_kp_serverAuth,
+ KeyPurposeId.id_kp_clientAuth,
+ KeyPurposeId.anyExtendedKeyUsage,
+ isCA = true
+ ),
+
+ CLIENT_CA(
+ KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign),
+ KeyPurposeId.id_kp_serverAuth,
+ KeyPurposeId.id_kp_clientAuth,
+ KeyPurposeId.anyExtendedKeyUsage,
+ isCA = true
+ ),
+
+ TLS(
+ KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyEncipherment or KeyUsage.keyAgreement),
+ KeyPurposeId.id_kp_serverAuth,
+ KeyPurposeId.id_kp_clientAuth,
+ KeyPurposeId.anyExtendedKeyUsage,
+ isCA = false
+ ),
+
// TODO: Identity certs should have only limited depth (i.e. 1) CA signing capability, with tight name constraints
- IDENTITY(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true)
+ IDENTITY(
+ KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign),
+ KeyPurposeId.id_kp_serverAuth,
+ KeyPurposeId.id_kp_clientAuth,
+ KeyPurposeId.anyExtendedKeyUsage,
+ isCA = true
+ )
}
data class CertificateAndKeyPair(val certificate: X509CertificateHolder, val keyPair: KeyPair)
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CertificateSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CertificateSerializer.kt
index a1e3c17b8f..2942153bba 100644
--- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CertificateSerializer.kt
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CertificateSerializer.kt
@@ -1,9 +1,9 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
+import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import net.corda.nodeapi.internal.serialization.amqp.*
import org.apache.qpid.proton.codec.Data
import java.lang.reflect.Type
-import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
object X509CertificateSerializer : CustomSerializer.Implements(X509Certificate::class.java) {
@@ -22,6 +22,6 @@ object X509CertificateSerializer : CustomSerializer.Implements(
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): X509Certificate {
val bits = input.readObject(obj, schema, ByteArray::class.java) as ByteArray
- return CertificateFactory.getInstance("X.509").generateCertificate(bits.inputStream()) as X509Certificate
+ return X509CertificateFactory().generateCertificate(bits.inputStream())
}
}
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt
index d3927dd47d..18b598919c 100644
--- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt
@@ -22,6 +22,7 @@ import net.corda.core.serialization.SerializedBytes
import net.corda.core.toFuture
import net.corda.core.toObservable
import net.corda.core.transactions.*
+import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import net.corda.nodeapi.internal.serialization.CordaClassResolver
import net.corda.nodeapi.internal.serialization.serializationContextKey
import org.slf4j.Logger
@@ -486,8 +487,7 @@ object CertPathSerializer : Serializer() {
@ThreadSafe
object X509CertificateSerializer : Serializer() {
override fun read(kryo: Kryo, input: Input, type: Class): X509Certificate {
- val factory = CertificateFactory.getInstance("X.509")
- return factory.generateCertificate(input.readBytesWithLength().inputStream()) as X509Certificate
+ return X509CertificateFactory().generateCertificate(input.readBytesWithLength().inputStream())
}
override fun write(kryo: Kryo, output: Output, obj: X509Certificate) {
diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/config/ConfigParsingTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/config/ConfigParsingTest.kt
index 452e25ce78..5efdeaf439 100644
--- a/node-api/src/test/kotlin/net/corda/nodeapi/config/ConfigParsingTest.kt
+++ b/node-api/src/test/kotlin/net/corda/nodeapi/config/ConfigParsingTest.kt
@@ -8,6 +8,7 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.div
import net.corda.core.utilities.NetworkHostAndPort
import org.assertj.core.api.Assertions.assertThat
+import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test
import java.net.URL
import java.nio.file.Path
@@ -47,6 +48,14 @@ class ConfigParsingTest {
testPropertyType(TestEnum.Value2, TestEnum.Value1, valuesToString = true)
}
+ @Test
+ fun `unknown Enum`() {
+ val config = config("value" to "UnknownValue")
+ assertThatThrownBy { config.parseAs() }
+ .hasMessageContaining(TestEnum.Value1.name)
+ .hasMessageContaining(TestEnum.Value2.name)
+ }
+
@Test
fun `LocalDate`() {
testPropertyType(LocalDate.now(), LocalDate.now().plusDays(1), valuesToString = true)
diff --git a/node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt b/node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt
index e7346a1932..96c701f761 100644
--- a/node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt
+++ b/node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt
@@ -139,8 +139,8 @@ class SSHServerTest {
val response = String(Streams.readAll(channel.inputStream))
- //There are ANSI control characters involved, so we want to avoid direct byte to byte matching
- assertThat(response.lines()).filteredOn( { it.contains("✓") && it.contains("Done")}).hasSize(1)
+ // There are ANSI control characters involved, so we want to avoid direct byte to byte matching.
+ assertThat(response.lines()).filteredOn( { it.contains("Done")}).hasSize(1)
}
}
diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt
index 2d7b09cf1d..bd579f4f17 100644
--- a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt
+++ b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt
@@ -2,11 +2,11 @@ package net.corda.services.messaging
import net.corda.core.crypto.Crypto
import net.corda.core.internal.*
-import net.corda.node.utilities.*
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
import net.corda.nodeapi.RPCApi
import net.corda.nodeapi.config.SSLConfiguration
+import net.corda.nodeapi.internal.crypto.*
import net.corda.testing.MEGA_CORP
import net.corda.testing.MINI_CORP
import net.corda.testing.messaging.SimpleMQClient
diff --git a/node/src/main/java/net/corda/node/shell/FlowShellCommand.java b/node/src/main/java/net/corda/node/shell/FlowShellCommand.java
index d3ed752531..f857a7eb62 100644
--- a/node/src/main/java/net/corda/node/shell/FlowShellCommand.java
+++ b/node/src/main/java/net/corda/node/shell/FlowShellCommand.java
@@ -4,7 +4,7 @@ package net.corda.node.shell;
import net.corda.core.messaging.CordaRPCOps;
import net.corda.node.utilities.ANSIProgressRenderer;
-import net.corda.node.utilities.CRaSHNSIProgressRenderer;
+import net.corda.node.utilities.CRaSHANSIProgressRenderer;
import org.crsh.cli.*;
import org.crsh.command.*;
import org.crsh.text.*;
@@ -12,7 +12,6 @@ import org.crsh.text.ui.TableElement;
import java.util.*;
-import static net.corda.node.services.messaging.RPCServerKt.CURRENT_RPC_CONTEXT;
import static net.corda.node.shell.InteractiveShell.*;
@Man(
@@ -49,7 +48,7 @@ public class FlowShellCommand extends InteractiveShellCommand {
return;
}
String inp = input == null ? "" : String.join(" ", input).trim();
- runFlowByNameFragment(name, inp, out, rpcOps, ansiProgressRenderer != null ? ansiProgressRenderer : new CRaSHNSIProgressRenderer(out) );
+ runFlowByNameFragment(name, inp, out, rpcOps, ansiProgressRenderer != null ? ansiProgressRenderer : new CRaSHANSIProgressRenderer(out) );
}
@Command
diff --git a/node/src/main/java/net/corda/node/shell/RunShellCommand.java b/node/src/main/java/net/corda/node/shell/RunShellCommand.java
index 108b567a9b..6875a5cdb8 100644
--- a/node/src/main/java/net/corda/node/shell/RunShellCommand.java
+++ b/node/src/main/java/net/corda/node/shell/RunShellCommand.java
@@ -30,7 +30,7 @@ public class RunShellCommand extends InteractiveShellCommand {
return null;
}
- return InteractiveShell.runRPCFromString(command, out, context);
+ return InteractiveShell.runRPCFromString(command, out, context, ops());
}
private void emitHelp(InvocationContext