diff --git a/docs/source/corda-configuration-file.rst b/docs/source/corda-configuration-file.rst index d8f927b639..d77ba22dd9 100644 --- a/docs/source/corda-configuration-file.rst +++ b/docs/source/corda-configuration-file.rst @@ -124,21 +124,25 @@ absolute path to the node's base directory. :maxRestartCount: Maximum number of times the flow will restart before resulting in an error. :backoffBase: The base of the exponential backoff, `t_{wait} = timeout * backoffBase^{retryCount}`. -:rpcAddress: The address of the RPC system on which RPC requests can be made to the node. If not provided then the node will run without RPC. This is now deprecated in favour of the ``rpcSettings`` block. +:rpcAddress: (Deprecated) The address of the RPC system on which RPC requests can be made to the node. If not provided then the node will run without RPC. This is now deprecated in favour of the ``rpcSettings`` block. -:rpcSettings: Options for the RPC server. +:rpcSettings: Options for the RPC server exposed by the Node. - :useSsl: (optional) boolean, indicates whether the node should require clients to use SSL for RPC connections, defaulted to ``false``. + :address: host and port for the RPC server binding. + :adminAddress: host and port for the RPC admin binding (this is the endpoint that the node process will connect to). :standAloneBroker: (optional) boolean, indicates whether the node will connect to a standalone broker for RPC, defaulted to ``false``. - :address: (optional) host and port for the RPC server binding, if any. - :adminAddress: (optional) host and port for the RPC admin binding (only required when ``useSsl`` is ``false``, because the node connects to Artemis using SSL to ensure admin privileges are not accessible outside the node). - :ssl: (optional) SSL settings for the RPC server. + :useSsl: (optional) boolean, indicates whether or not the node should require clients to use SSL for RPC connections, defaulted to ``false``. + :ssl: (mandatory if ``useSsl=true``) SSL settings for the RPC server. + + :keyStorePath: Absolute path to the key store containing the RPC SSL certificate. + :keyStorePassword: Password for the key store. + + .. note:: The RPC SSL certificate is used by RPC clients to authenticate the connection. + The Node operator must provide RPC clients with a truststore containing the certificate they can trust. + We advise Node operators to not use the P2P keystore for RPC. + The node ships with a command line argument "--just-generate-rpc-ssl-settings", which generates a secure keystore + and truststore that can be used to secure the RPC connection. You can use this if you have no special requirements. - :keyStorePassword: password for the key store. - :trustStorePassword: password for the trust store. - :certificatesDirectory: directory in which the stores will be searched, unless absolute paths are provided. - :sslKeystore: absolute path to the ssl key store, defaulted to ``certificatesDirectory / "sslkeystore.jks"``. - :trustStoreFile: absolute path to the trust store, defaulted to ``certificatesDirectory / "truststore.jks"``. :security: Contains various nested fields controlling user authentication/authorization, in particular for RPC accesses. See :doc:`clientrpc` for details. @@ -307,76 +311,16 @@ Simple notary configuration file: devMode : false compatibilityZoneURL : "https://cz.corda.net" -An example ``web-server.conf`` file is as follow: - -.. parsed-literal:: - - myLegalName : "O=Notary Service,OU=corda,L=London,C=GB" - keyStorePassword : "cordacadevpass" - trustStorePassword : "trustpass" - rpcSettings = { - useSsl = false - standAloneBroker = false - address : "my-corda-node:10003" - adminAddress : "my-corda-node:10004" - } - rpcUsers : [{ username=user1, password=letmein, permissions=[ StartFlow.net.corda.protocols.CashProtocol ] }] - -Configuring a node where the Corda Comatability Zone's registration and Network Map services exist on different URLs +Configuring a node where the Corda Compatibility Zone's registration and Network Map services exist on different URLs .. literalinclude:: example-code/src/main/resources/example-node-with-networkservices.conf -Fields ------- - -The available config fields are listed below. ``baseDirectory`` is available as a substitution value, containing the absolute -path to the node's base directory. - -:myLegalName: The legal identity of the node acts as a human readable alias to the node's public key and several demos use - this to lookup the NodeInfo. - -:keyStorePassword: The password to unlock the KeyStore file (``/certificates/sslkeystore.jks``) containing the - node certificate and private key. - - .. note:: This is the non-secret value for the development certificates automatically generated during the first node run. - Longer term these keys will be managed in secure hardware devices. - -:trustStorePassword: The password to unlock the Trust store file (``/certificates/truststore.jks``) containing - the Corda network root certificate. This is the non-secret value for the development certificates automatically - generated during the first node run. - - .. note:: Longer term these keys will be managed in secure hardware devices. - -:rpcSettings: Options for the RPC server. - - :useSsl: (optional) boolean, indicates whether the node should require clients to use SSL for RPC connections, defaulted to ``false``. - :standAloneBroker: (optional) boolean, indicates whether the node will connect to a standalone broker for RPC, defaulted to ``false``. - :address: (optional) host and port for the RPC server binding, if any. - :adminAddress: (optional) host and port for the RPC admin binding (only required when ``useSsl`` is ``false``, because the node connects to Artemis using SSL to ensure admin privileges are not accessible outside the node). - :ssl: (optional) SSL settings for the RPC client. - - :keyStorePassword: password for the key store. - :trustStorePassword: password for the trust store. - :certificatesDirectory: directory in which the stores will be searched, unless absolute paths are provided. - :sslKeystore: absolute path to the ssl key store, defaulted to ``certificatesDirectory / "sslkeystore.jks"``. - :trustStoreFile: absolute path to the trust store, defaulted to ``certificatesDirectory / "truststore.jks"``. - :trustStoreFile: absolute path to the trust store, defaulted to ``certificatesDirectory / "truststore.jks"``. - -:rpcUsers: A list of users who are authorised to access the RPC system. Each user in the list is a config object with the - following fields: - - :username: Username consisting only of word characters (a-z, A-Z, 0-9 and _) - :password: The password - :permissions: A list of permissions for starting flows via RPC. To give the user the permission to start the flow - ``foo.bar.FlowClass``, add the string ``StartFlow.foo.bar.FlowClass`` to the list. If the list - contains the string ``ALL``, the user can start any flow via RPC. This value is intended for administrator - users and for development. - Fields Override --------------- -JVM options or environmental variables prefixed ``corda.`` can override ``node.conf`` fields. -Provided system properties also can set value for absent fields in ``node.conf``. -Example adding/overriding keyStore password when starting Corda node: +JVM options or environmental variables prefixed with ``corda.`` can override ``node.conf`` fields. +Provided system properties can also set values for absent fields in ``node.conf``. + +This is an example of adding/overriding the keyStore password : .. sourcecode:: shell 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 7eefa57a90..b4e9e6cf36 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 @@ -58,7 +58,7 @@ object X509Utilities { const val CORDA_CLIENT_TLS = "cordaclienttls" const val CORDA_CLIENT_CA = "cordaclientca" - private val DEFAULT_VALIDITY_WINDOW = Pair(0.millis, 3650.days) + val DEFAULT_VALIDITY_WINDOW = Pair(0.millis, 3650.days) /** * Helper function to return the latest out of an instant and an optional date. diff --git a/node/src/integration-test/kotlin/net/corda/node/services/rpc/RpcSslTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/rpc/RpcSslTest.kt index a44724fb1e..9f5d46ed27 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/rpc/RpcSslTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/rpc/RpcSslTest.kt @@ -17,6 +17,9 @@ import net.corda.core.utilities.getOrThrow import net.corda.node.services.Permissions.Companion.all import net.corda.nodeapi.BrokerRpcSslOptions import net.corda.core.messaging.ClientRpcSslOptions +import net.corda.node.utilities.createKeyPairAndSelfSignedTLSCertificate +import net.corda.node.utilities.saveToKeyStore +import net.corda.node.utilities.saveToTrustStore import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_RPC_USER import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME @@ -28,9 +31,6 @@ import net.corda.testing.driver.internal.RandomFree import net.corda.testing.internal.IntegrationTest import net.corda.testing.internal.IntegrationTestSchemas import net.corda.testing.internal.toDatabaseSchemaName -import net.corda.testing.internal.createKeyPairAndSelfSignedCertificate -import net.corda.testing.internal.saveToKeyStore -import net.corda.testing.internal.saveToTrustStore import net.corda.testing.internal.useSslRpcOverrides import net.corda.testing.node.User import org.apache.activemq.artemis.api.core.ActiveMQException @@ -41,6 +41,7 @@ import org.junit.ClassRule import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder +import javax.security.auth.x500.X500Principal class RpcSslTest : IntegrationTest() { companion object { @@ -54,6 +55,7 @@ class RpcSslTest : IntegrationTest() { @JvmField val tempFolder = TemporaryFolder() + val testName = X500Principal("CN=Test,O=R3 Ltd,L=London,C=GB") @Test fun `RPC client using ssl is able to run a command`() { @@ -61,7 +63,7 @@ class RpcSslTest : IntegrationTest() { var successfulLogin = false var failedLogin = false - val (keyPair, cert) = createKeyPairAndSelfSignedCertificate() + val (keyPair, cert) = createKeyPairAndSelfSignedTLSCertificate(testName) val keyStorePath = saveToKeyStore(tempFolder.root.toPath() / "keystore.jks", keyPair, cert) val brokerSslOptions = BrokerRpcSslOptions(keyStorePath, "password") @@ -98,11 +100,11 @@ class RpcSslTest : IntegrationTest() { val user = User("mark", "dadada", setOf(all())) var successful = false - val (keyPair, cert) = createKeyPairAndSelfSignedCertificate() + val (keyPair, cert) = createKeyPairAndSelfSignedTLSCertificate(testName) val keyStorePath = saveToKeyStore(tempFolder.root.toPath() / "keystore.jks", keyPair, cert) val brokerSslOptions = BrokerRpcSslOptions(keyStorePath, "password") - val (_, cert1) = createKeyPairAndSelfSignedCertificate() + val (_, cert1) = createKeyPairAndSelfSignedTLSCertificate(testName) val trustStorePath = saveToTrustStore(tempFolder.root.toPath() / "truststore.jks", cert1) val clientSslOptions = ClientRpcSslOptions(trustStorePath, "password") @@ -140,7 +142,7 @@ class RpcSslTest : IntegrationTest() { @Test fun `The system RPC user can not connect to the rpc broker without the node's key`() { - val (keyPair, cert) = createKeyPairAndSelfSignedCertificate() + val (keyPair, cert) = createKeyPairAndSelfSignedTLSCertificate(testName) val keyStorePath = saveToKeyStore(tempFolder.root.toPath() / "keystore.jks", keyPair, cert) val brokerSslOptions = BrokerRpcSslOptions(keyStorePath, "password") val trustStorePath = saveToTrustStore(tempFolder.root.toPath() / "truststore.jks", cert) diff --git a/node/src/main/kotlin/net/corda/node/NodeArgsParser.kt b/node/src/main/kotlin/net/corda/node/NodeArgsParser.kt index 6d2c4c3471..fb6a793a0e 100644 --- a/node/src/main/kotlin/net/corda/node/NodeArgsParser.kt +++ b/node/src/main/kotlin/net/corda/node/NodeArgsParser.kt @@ -64,6 +64,8 @@ class NodeArgsParser : AbstractArgsParser() { private val isVersionArg = optionParser.accepts("version", "Print the version and exit") private val justGenerateNodeInfoArg = optionParser.accepts("just-generate-node-info", "Perform the node start-up task necessary to generate its nodeInfo, save it to disk, then quit") + private val justGenerateRpcSslCertsArg = optionParser.accepts("just-generate-rpc-ssl-settings", + "Generate the ssl keystore and truststore for a secure RPC connection.") private val bootstrapRaftClusterArg = optionParser.accepts("bootstrap-raft-cluster", "Bootstraps Raft cluster. The node forms a single node cluster (ignoring otherwise configured peer addresses), acting as a seed for other nodes to join the cluster.") private val clearNetworkMapCache = optionParser.accepts("clear-network-map-cache", "Clears local copy of network map, on node startup it will be restored from server or file system.") @@ -85,6 +87,7 @@ class NodeArgsParser : AbstractArgsParser() { val noLocalShell = optionSet.has(noLocalShellArg) val sshdServer = optionSet.has(sshdServerArg) val justGenerateNodeInfo = optionSet.has(justGenerateNodeInfoArg) + val justGenerateRpcSslCerts = optionSet.has(justGenerateRpcSslCertsArg) val bootstrapRaftCluster = optionSet.has(bootstrapRaftClusterArg) val networkRootTrustStorePath = optionSet.valueOf(networkRootTrustStorePathArg) val networkRootTrustStorePassword = optionSet.valueOf(networkRootTrustStorePasswordArg) @@ -109,6 +112,7 @@ class NodeArgsParser : AbstractArgsParser() { noLocalShell, sshdServer, justGenerateNodeInfo, + justGenerateRpcSslCerts, bootstrapRaftCluster, unknownConfigKeysPolicy, devMode, @@ -127,6 +131,7 @@ data class CmdLineOptions(val baseDirectory: Path, val noLocalShell: Boolean, val sshdServer: Boolean, val justGenerateNodeInfo: Boolean, + val justGenerateRpcSslCerts: Boolean, val bootstrapRaftCluster: Boolean, val unknownConfigKeysPolicy: UnknownConfigKeysPolicy, val devMode: Boolean, 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 a226e1c44c..c3b271e4e9 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -261,7 +261,7 @@ open class Node(configuration: NodeConfiguration, val rpcBrokerDirectory: Path = baseDirectory / "brokers" / "rpc" with(rpcOptions) { rpcBroker = if (useSsl) { - ArtemisRpcBroker.withSsl(configuration, this.address, adminAddress, sslConfig, securityManager, MAX_RPC_MESSAGE_SIZE, jmxMonitoringHttpPort != null, rpcBrokerDirectory, shouldStartLocalShell()) + ArtemisRpcBroker.withSsl(configuration, this.address, adminAddress, sslConfig!!, securityManager, MAX_RPC_MESSAGE_SIZE, jmxMonitoringHttpPort != null, rpcBrokerDirectory, shouldStartLocalShell()) } else { ArtemisRpcBroker.withoutSsl(configuration, this.address, adminAddress, securityManager, MAX_RPC_MESSAGE_SIZE, jmxMonitoringHttpPort != null, rpcBrokerDirectory, shouldStartLocalShell()) } 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 ee5a2f3a75..0d782bbfc3 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -16,11 +16,8 @@ import com.typesafe.config.ConfigRenderOptions import io.netty.channel.unix.Errors import net.corda.core.cordapp.Cordapp import net.corda.core.crypto.Crypto -import net.corda.core.internal.Emoji +import net.corda.core.internal.* import net.corda.core.internal.concurrent.thenMatch -import net.corda.core.internal.createDirectories -import net.corda.core.internal.div -import net.corda.core.internal.randomOrNull import net.corda.core.utilities.Try import net.corda.core.utilities.loggerFor import net.corda.node.CmdLineOptions @@ -34,8 +31,11 @@ import net.corda.node.services.config.NodeConfigurationImpl import net.corda.node.services.config.shouldStartLocalShell import net.corda.node.services.config.shouldStartSSHDaemon import net.corda.node.services.transactions.bftSMaRtSerialFilter +import net.corda.node.utilities.createKeyPairAndSelfSignedTLSCertificate import net.corda.node.utilities.registration.HTTPNetworkRegistrationService import net.corda.node.utilities.registration.NodeRegistrationHelper +import net.corda.node.utilities.saveToKeyStore +import net.corda.node.utilities.saveToTrustStore import net.corda.node.utilities.registration.UnableToRegisterNodeWithDoormanException import net.corda.nodeapi.internal.addShutdownHook import net.corda.nodeapi.internal.config.UnknownConfigurationKeysException @@ -47,12 +47,14 @@ import org.fusesource.jansi.Ansi import org.fusesource.jansi.AnsiConsole import org.slf4j.bridge.SLF4JBridgeHandler import sun.misc.VMSupport +import java.io.Console import java.io.RandomAccessFile import java.lang.management.ManagementFactory import java.net.InetAddress import java.nio.file.Path import java.nio.file.Paths import java.util.* +import kotlin.system.exitProcess /** This class is responsible for starting a Node from command line arguments. */ open class NodeStartup(val args: Array) { @@ -188,6 +190,70 @@ open class NodeStartup(val args: Array) { node.generateAndSaveNodeInfo() return } + if (cmdlineOptions.justGenerateRpcSslCerts) { + val (keyPair, cert) = createKeyPairAndSelfSignedTLSCertificate(conf.myLegalName.x500Principal) + + val keyStorePath = conf.baseDirectory / "certificates" / "rpcsslkeystore.jks" + val trustStorePath = conf.baseDirectory / "certificates" / "export" / "rpcssltruststore.jks" + + if (keyStorePath.exists() || trustStorePath.exists()) { + println("Found existing RPC SSL keystores. Command was already run. Exiting..") + exitProcess(0) + } + + val console: Console? = System.console() + + when (console) { + // In this case, the JVM is not connected to the console so we need to exit + null -> { + println("Not connected to console. Exiting") + exitProcess(1) + } + // Otherwise we can proceed normally + else -> { + while (true) { + val keystorePassword1 = console.readPassword("Enter the keystore password => ") + val keystorePassword2 = console.readPassword("Re-enter the keystore password => ") + if (!keystorePassword1.contentEquals(keystorePassword2)) { + println("The keystore passwords don't match.") + continue + } + saveToKeyStore(keyStorePath, keyPair, cert, String(keystorePassword1), "rpcssl") + println("The keystore was saved to: $keyStorePath .") + break + } + + while (true) { + val trustStorePassword1 = console.readPassword("Enter the truststore password => ") + val trustStorePassword2 = console.readPassword("Re-enter the truststore password => ") + if (!trustStorePassword1.contentEquals(trustStorePassword2)) { + println("The truststore passwords don't match.") + continue + } + + saveToTrustStore(trustStorePath, cert, String(trustStorePassword1), "rpcssl") + println("The truststore was saved to: $trustStorePath .") + println("You need to distribute this file along with the password in a secure way to all RPC clients.") + break + } + + val dollar = '$' + println(""" + | + |The SSL certificates were generated successfully. + | + |Add this snippet to the "rpcSettings" section of your node.conf: + | useSsl=true + | ssl { + | keyStorePath=$dollar{baseDirectory}/certificates/rpcsslkeystore.jks + | keyStorePassword=the_above_password + | } + |""".trimMargin()) + } + } + return + } + val startedNode = node.start() logLoadedCorDapps(startedNode.services.cordappProvider.cordapps) startedNode.internals.nodeReadyFuture.thenMatch({ diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index 7ce32bdb9f..98f5a6fb79 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -286,7 +286,7 @@ data class NodeConfigurationImpl( override val rpcOptions: NodeRpcOptions get() { - return actualRpcSettings.asOptions(BrokerRpcSslOptions(baseDirectory / "certificates" / "nodekeystore.jks", keyStorePassword)) + return actualRpcSettings.asOptions() } private fun validateTlsCertCrlConfig(): List { @@ -332,10 +332,11 @@ data class NodeConfigurationImpl( private fun validateRpcSettings(options: NodeRpcSettings): List { val errors = mutableListOf() - if (options.address != null) { - if (!options.useSsl && options.adminAddress == null) { - errors += "'rpcSettings.adminAddress': missing. Property is mandatory when 'rpcSettings.useSsl' is false (default)." - } + if (options.adminAddress == null) { + errors += "'rpcSettings.adminAddress': missing" + } + if (options.useSsl && options.ssl == null) { + errors += "'rpcSettings.ssl': missing (rpcSettings.useSsl was set to true)." } return errors } @@ -434,13 +435,13 @@ data class NodeRpcSettings( val useSsl: Boolean = false, val ssl: BrokerRpcSslOptions? ) { - fun asOptions(fallbackSslOptions: BrokerRpcSslOptions): NodeRpcOptions { + fun asOptions(): NodeRpcOptions { return object : NodeRpcOptions { override val address = this@NodeRpcSettings.address!! override val adminAddress = this@NodeRpcSettings.adminAddress!! override val standAloneBroker = this@NodeRpcSettings.standAloneBroker override val useSsl = this@NodeRpcSettings.useSsl - override val sslConfig = this@NodeRpcSettings.ssl ?: fallbackSslOptions + override val sslConfig = this@NodeRpcSettings.ssl override fun toString(): String { return "address: $address, adminAddress: $adminAddress, standAloneBroker: $standAloneBroker, useSsl: $useSsl, sslConfig: $sslConfig" diff --git a/node/src/main/kotlin/net/corda/node/services/config/rpc/NodeRpcOptions.kt b/node/src/main/kotlin/net/corda/node/services/config/rpc/NodeRpcOptions.kt index a992c4b6e5..4a582a2b8d 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/rpc/NodeRpcOptions.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/rpc/NodeRpcOptions.kt @@ -18,5 +18,5 @@ interface NodeRpcOptions { val adminAddress: NetworkHostAndPort val standAloneBroker: Boolean val useSsl: Boolean - val sslConfig: BrokerRpcSslOptions + val sslConfig: BrokerRpcSslOptions? } \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/utilities/CertificatesUtils.kt b/node/src/main/kotlin/net/corda/node/utilities/CertificatesUtils.kt new file mode 100644 index 0000000000..6b773cda82 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/utilities/CertificatesUtils.kt @@ -0,0 +1,36 @@ +package net.corda.node.utilities + +import net.corda.core.crypto.Crypto +import net.corda.nodeapi.internal.crypto.* +import java.nio.file.Path +import java.security.KeyPair +import java.security.cert.X509Certificate +import java.time.Duration +import javax.security.auth.x500.X500Principal + +fun createKeyPairAndSelfSignedTLSCertificate(x500Principal: X500Principal): Pair { + val rpcKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val selfSignCert = createSelfSignedTLSCertificate(x500Principal, rpcKeyPair) + return Pair(rpcKeyPair, selfSignCert) +} + +fun createSelfSignedTLSCertificate(subject: X500Principal, + keyPair: KeyPair, + validityWindow: Pair = X509Utilities.DEFAULT_VALIDITY_WINDOW): X509Certificate { + val window = X509Utilities.getCertificateValidityWindow(validityWindow.first, validityWindow.second) + return X509Utilities.createCertificate(CertificateType.TLS, subject, keyPair, subject, keyPair.public, window) +} + +fun saveToKeyStore(keyStorePath: Path, rpcKeyPair: KeyPair, selfSignCert: X509Certificate, password: String = "password", alias: String = "Key"): Path { + val keyStore = loadOrCreateKeyStore(keyStorePath, password) + keyStore.addOrReplaceKey(alias, rpcKeyPair.private, password.toCharArray(), arrayOf(selfSignCert)) + keyStore.save(keyStorePath, password) + return keyStorePath +} + +fun saveToTrustStore(trustStorePath: Path, selfSignCert: X509Certificate, password: String = "password", alias: String = "Key"): Path { + val trustStore = loadOrCreateKeyStore(trustStorePath, password) + trustStore.addOrReplaceCertificate(alias, selfSignCert) + trustStore.save(trustStorePath, password) + return trustStorePath +} \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/NodeArgsParserTest.kt b/node/src/test/kotlin/net/corda/node/NodeArgsParserTest.kt index f2e216816c..87ab7669bd 100644 --- a/node/src/test/kotlin/net/corda/node/NodeArgsParserTest.kt +++ b/node/src/test/kotlin/net/corda/node/NodeArgsParserTest.kt @@ -52,6 +52,7 @@ class NodeArgsParserTest { noLocalShell = false, sshdServer = false, justGenerateNodeInfo = false, + justGenerateRpcSslCerts = false, bootstrapRaftCluster = false, unknownConfigKeysPolicy = UnknownConfigKeysPolicy.FAIL, devMode = false, diff --git a/node/src/test/kotlin/net/corda/node/services/rpc/ArtemisRpcTests.kt b/node/src/test/kotlin/net/corda/node/services/rpc/ArtemisRpcTests.kt index 903b3e6739..71ae041f81 100644 --- a/node/src/test/kotlin/net/corda/node/services/rpc/ArtemisRpcTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/rpc/ArtemisRpcTests.kt @@ -24,6 +24,9 @@ import net.corda.node.internal.security.RPCSecurityManagerImpl import net.corda.node.services.Permissions.Companion.all import net.corda.node.services.messaging.InternalRPCMessagingClient import net.corda.node.services.messaging.RPCServerConfiguration +import net.corda.node.utilities.createKeyPairAndSelfSignedTLSCertificate +import net.corda.node.utilities.saveToKeyStore +import net.corda.node.utilities.saveToTrustStore import net.corda.nodeapi.ArtemisTcpTransport.Companion.rpcConnectorTcpTransport import net.corda.nodeapi.BrokerRpcSslOptions import net.corda.nodeapi.internal.config.SSLConfiguration @@ -31,10 +34,7 @@ import net.corda.nodeapi.internal.config.User import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.driver.PortAllocation import net.corda.testing.driver.internal.RandomFree -import net.corda.testing.internal.createKeyPairAndSelfSignedCertificate import net.corda.testing.internal.createNodeSslConfig -import net.corda.testing.internal.saveToKeyStore -import net.corda.testing.internal.saveToTrustStore import org.apache.activemq.artemis.api.core.ActiveMQConnectionTimedOutException import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl import org.assertj.core.api.Assertions.assertThat @@ -43,6 +43,7 @@ import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder import java.nio.file.Path +import javax.security.auth.x500.X500Principal class ArtemisRpcTests { private val ports: PortAllocation = RandomFree @@ -59,9 +60,11 @@ class ArtemisRpcTests { @JvmField val tempFolder = TemporaryFolder() + val testName = X500Principal("CN=Test,O=R3 Ltd,L=London,C=GB") + @Test fun rpc_with_ssl_enabled() { - val (rpcKeyPair, selfSignCert) = createKeyPairAndSelfSignedCertificate() + val (rpcKeyPair, selfSignCert) = createKeyPairAndSelfSignedTLSCertificate(testName) val keyStorePath = saveToKeyStore(tempFile("rpcKeystore.jks"), rpcKeyPair, selfSignCert) val brokerSslOptions = BrokerRpcSslOptions(keyStorePath, "password") val trustStorePath = saveToTrustStore(tempFile("rpcTruststore.jks"), selfSignCert) @@ -76,7 +79,7 @@ class ArtemisRpcTests { @Test fun rpc_with_no_ssl_on_client_side_and_ssl_on_server_side() { - val (rpcKeyPair, selfSignCert) = createKeyPairAndSelfSignedCertificate() + val (rpcKeyPair, selfSignCert) = createKeyPairAndSelfSignedTLSCertificate(testName) val keyStorePath = saveToKeyStore(tempFile("rpcKeystore.jks"), rpcKeyPair, selfSignCert) val brokerSslOptions = BrokerRpcSslOptions(keyStorePath, "password") // here client sslOptions are passed null (as in, do not use SSL) @@ -87,12 +90,12 @@ class ArtemisRpcTests { @Test fun rpc_client_certificate_untrusted_to_server() { - val (rpcKeyPair, selfSignCert) = createKeyPairAndSelfSignedCertificate() + val (rpcKeyPair, selfSignCert) = createKeyPairAndSelfSignedTLSCertificate(testName) val keyStorePath = saveToKeyStore(tempFile("rpcKeystore.jks"), rpcKeyPair, selfSignCert) // create another keypair and certificate and add that to the client truststore // the ssl connection should not - val (_, selfSignCert1) = createKeyPairAndSelfSignedCertificate() + val (_, selfSignCert1) = createKeyPairAndSelfSignedTLSCertificate(testName) val trustStorePath = saveToTrustStore(tempFile("rpcTruststore.jks"), selfSignCert1) val brokerSslOptions = BrokerRpcSslOptions(keyStorePath, "password") diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt index 26da293652..43375904e5 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt @@ -28,7 +28,6 @@ import net.corda.serialization.internal.amqp.AMQP_ENABLED import java.nio.file.Files import java.nio.file.Path import java.security.KeyPair -import java.security.cert.X509Certificate import javax.security.auth.x500.X500Principal @Suppress("unused") @@ -150,24 +149,3 @@ fun createNodeSslConfig(path: Path, name: CordaX500Name = CordaX500Name("MegaCor return sslConfig } - -fun createKeyPairAndSelfSignedCertificate(): Pair { - val rpcKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val testName = X500Principal("CN=Test,O=R3 Ltd,L=London,C=GB") - val selfSignCert = X509Utilities.createSelfSignedCACertificate(testName, rpcKeyPair) - return Pair(rpcKeyPair, selfSignCert) -} - -fun saveToKeyStore(keyStorePath: Path, rpcKeyPair: KeyPair, selfSignCert: X509Certificate, password: String = "password"): Path { - val keyStore = loadOrCreateKeyStore(keyStorePath, password) - keyStore.addOrReplaceKey("Key", rpcKeyPair.private, password.toCharArray(), arrayOf(selfSignCert)) - keyStore.save(keyStorePath, password) - return keyStorePath -} - -fun saveToTrustStore(trustStorePath: Path, selfSignCert: X509Certificate, password: String = "password"): Path { - val trustStore = loadOrCreateKeyStore(trustStorePath, password) - trustStore.addOrReplaceCertificate("Key", selfSignCert) - trustStore.save(trustStorePath, password) - return trustStorePath -} \ No newline at end of file diff --git a/tools/shell/src/integration-test/kotlin/net/corda/tools/shell/InteractiveShellIntegrationTest.kt b/tools/shell/src/integration-test/kotlin/net/corda/tools/shell/InteractiveShellIntegrationTest.kt index fe7a86446d..cb9dae45ce 100644 --- a/tools/shell/src/integration-test/kotlin/net/corda/tools/shell/InteractiveShellIntegrationTest.kt +++ b/tools/shell/src/integration-test/kotlin/net/corda/tools/shell/InteractiveShellIntegrationTest.kt @@ -22,6 +22,9 @@ import net.corda.node.services.Permissions.Companion.all import net.corda.node.services.config.shell.toShellConfig import net.corda.nodeapi.BrokerRpcSslOptions import net.corda.core.messaging.ClientRpcSslOptions +import net.corda.node.utilities.createKeyPairAndSelfSignedTLSCertificate +import net.corda.node.utilities.saveToKeyStore +import net.corda.node.utilities.saveToTrustStore import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.DUMMY_BANK_A_NAME @@ -33,9 +36,6 @@ import net.corda.testing.driver.internal.RandomFree import net.corda.testing.internal.IntegrationTest import net.corda.testing.internal.IntegrationTestSchemas import net.corda.testing.internal.toDatabaseSchemaName -import net.corda.testing.internal.createKeyPairAndSelfSignedCertificate -import net.corda.testing.internal.saveToKeyStore -import net.corda.testing.internal.saveToTrustStore import net.corda.testing.internal.useSslRpcOverrides import net.corda.testing.node.User import org.apache.activemq.artemis.api.core.ActiveMQSecurityException @@ -47,6 +47,7 @@ import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder +import javax.security.auth.x500.X500Principal import kotlin.test.assertTrue class InteractiveShellIntegrationTest : IntegrationTest() { @@ -61,6 +62,8 @@ class InteractiveShellIntegrationTest : IntegrationTest() { @JvmField val tempFolder = TemporaryFolder() + val testName = X500Principal("CN=Test,O=R3 Ltd,L=London,C=GB") + @Test fun `shell should not log in with invalid credentials`() { val user = User("u", "p", setOf()) @@ -98,7 +101,7 @@ class InteractiveShellIntegrationTest : IntegrationTest() { val user = User("mark", "dadada", setOf(all())) var successful = false - val (keyPair, cert) = createKeyPairAndSelfSignedCertificate() + val (keyPair, cert) = createKeyPairAndSelfSignedTLSCertificate(testName) val keyStorePath = saveToKeyStore(tempFolder.root.toPath() / "keystore.jks", keyPair, cert) val brokerSslOptions = BrokerRpcSslOptions(keyStorePath, "password") @@ -125,11 +128,11 @@ class InteractiveShellIntegrationTest : IntegrationTest() { @Test fun `shell shoud not log in with invalid truststore`() { val user = User("mark", "dadada", setOf("ALL")) - val (keyPair, cert) = createKeyPairAndSelfSignedCertificate() + val (keyPair, cert) = createKeyPairAndSelfSignedTLSCertificate(testName) val keyStorePath = saveToKeyStore(tempFolder.root.toPath() / "keystore.jks", keyPair, cert) val brokerSslOptions = BrokerRpcSslOptions(keyStorePath, "password") - val (_, cert1) = createKeyPairAndSelfSignedCertificate() + val (_, cert1) = createKeyPairAndSelfSignedTLSCertificate(testName) val trustStorePath = saveToTrustStore(tempFolder.root.toPath() / "truststore.jks", cert1) val clientSslOptions = ClientRpcSslOptions(trustStorePath, "password") @@ -209,7 +212,7 @@ class InteractiveShellIntegrationTest : IntegrationTest() { Permissions.invokeRpc(CordaRPCOps::registeredFlows), Permissions.invokeRpc(CordaRPCOps::nodeInfo)/*all()*/)) - val (keyPair, cert) = createKeyPairAndSelfSignedCertificate() + val (keyPair, cert) = createKeyPairAndSelfSignedTLSCertificate(testName) val keyStorePath = saveToKeyStore(tempFolder.root.toPath() / "keystore.jks", keyPair, cert) val brokerSslOptions = BrokerRpcSslOptions(keyStorePath, "password") val trustStorePath = saveToTrustStore(tempFolder.root.toPath() / "truststore.jks", cert)