From 77a138a04e4c0a92b7b3ee27b585d07b669806f9 Mon Sep 17 00:00:00 2001 From: Patrick Kuo Date: Fri, 23 Feb 2018 13:38:09 +0000 Subject: [PATCH] CORDA-1317 - Add cert role to CSR and doorman issue cert according to the cert role (#2620) Cherrypick from master * ENT-1443 Add cert role to CSR and doorman issue cert according to the cert role (#431) * Doorman and HSM create certificate base on requested cert role specified in the certificate signing request. (cherry picked from commit 94f7392) * remove R3 corda code --- .../internal/config/ConfigUtilities.kt | 12 +-- .../nodeapi/internal/crypto/X509Utilities.kt | 27 ++++-- .../internal/config/ConfigParsingTest.kt | 8 +- .../main/kotlin/net/corda/node/ArgsParser.kt | 42 ++++++--- .../net/corda/node/internal/NodeStartup.kt | 13 +-- .../registration/NetworkRegistrationHelper.kt | 88 ++++++++++++------- .../kotlin/net/corda/node/ArgsParserTest.kt | 22 +++-- .../NetworkRegistrationHelperTest.kt | 49 ++++++++++- .../testing/node/internal/DriverDSLImpl.kt | 3 +- 9 files changed, 182 insertions(+), 82 deletions(-) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt index 94997d34b8..544f1c1f7e 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt @@ -2,10 +2,7 @@ package net.corda.nodeapi.internal.config -import com.typesafe.config.Config -import com.typesafe.config.ConfigFactory -import com.typesafe.config.ConfigUtil -import com.typesafe.config.ConfigValueFactory +import com.typesafe.config.* import net.corda.core.identity.CordaX500Name import net.corda.core.internal.noneOrSingle import net.corda.core.internal.uncheckedCast @@ -78,7 +75,12 @@ private fun Config.getSingleValue(path: String, type: KType): Any? { NetworkHostAndPort::class -> NetworkHostAndPort.parse(getString(path)) Path::class -> Paths.get(getString(path)) URL::class -> URL(getString(path)) - CordaX500Name::class -> CordaX500Name.parse(getString(path)) + CordaX500Name::class -> { + when (getValue(path).valueType()) { + ConfigValueType.OBJECT -> getConfig(path).parseAs() + else -> CordaX500Name.parse(getString(path)) + } + } Properties::class -> getConfig(path).toProperties() Config::class -> getConfig(path) else -> if (typeClass.java.isEnum) { 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 0b545290df..fcefae9e01 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 @@ -259,13 +259,17 @@ object X509Utilities { private fun createCertificateSigningRequest(subject: X500Principal, email: String, keyPair: KeyPair, - signatureScheme: SignatureScheme): PKCS10CertificationRequest { + signatureScheme: SignatureScheme, + certRole: CertRole): PKCS10CertificationRequest { val signer = ContentSignerBuilder.build(signatureScheme, keyPair.private, Crypto.findProvider(signatureScheme.providerName)) - return JcaPKCS10CertificationRequestBuilder(subject, keyPair.public).addAttribute(BCStyle.E, DERUTF8String(email)).build(signer) + return JcaPKCS10CertificationRequestBuilder(subject, keyPair.public) + .addAttribute(BCStyle.E, DERUTF8String(email)) + .addAttribute(ASN1ObjectIdentifier(CordaOID.X509_EXTENSION_CORDA_ROLE), certRole) + .build(signer) } - fun createCertificateSigningRequest(subject: X500Principal, email: String, keyPair: KeyPair): PKCS10CertificationRequest { - return createCertificateSigningRequest(subject, email, keyPair, DEFAULT_TLS_SIGNATURE_SCHEME) + fun createCertificateSigningRequest(subject: X500Principal, email: String, keyPair: KeyPair, certRole: CertRole = CertRole.NODE_CA): PKCS10CertificationRequest { + return createCertificateSigningRequest(subject, email, keyPair, DEFAULT_TLS_SIGNATURE_SCHEME, certRole) } fun buildCertPath(first: X509Certificate, remaining: List): CertPath { @@ -284,19 +288,24 @@ object X509Utilities { } } +// Assuming cert type to role is 1:1 +val CertRole.certificateType: CertificateType get() = CertificateType.values().first { it.role == this } + /** * Convert a [X509Certificate] into Bouncycastle's [X509CertificateHolder]. * * NOTE: To avoid unnecessary copying use [X509Certificate] where possible. */ fun X509Certificate.toBc() = X509CertificateHolder(encoded) + fun X509CertificateHolder.toJca(): X509Certificate = X509CertificateFactory().generateCertificate(encoded.inputStream()) -val CertPath.x509Certificates: List get() { - require(type == "X.509") { "Not an X.509 cert path: $this" } - // We're not mapping the list to avoid creating a new one. - return uncheckedCast(certificates) -} +val CertPath.x509Certificates: List + get() { + require(type == "X.509") { "Not an X.509 cert path: $this" } + // We're not mapping the list to avoid creating a new one. + return uncheckedCast(certificates) + } val Certificate.x509: X509Certificate get() = requireNotNull(this as? X509Certificate) { "Not an X.509 certificate: $this" } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/config/ConfigParsingTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/config/ConfigParsingTest.kt index a6fa2ca169..7fbc481321 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/config/ConfigParsingTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/config/ConfigParsingTest.kt @@ -87,10 +87,15 @@ class ConfigParsingTest { @Test fun CordaX500Name() { + val name1 = CordaX500Name(organisation = "Mock Party", locality = "London", country = "GB") testPropertyType( - CordaX500Name(organisation = "Mock Party", locality = "London", country = "GB"), + name1, CordaX500Name(organisation = "Mock Party 2", locality = "London", country = "GB"), valuesToString = true) + + // Test with config object. + val config = config("value" to mapOf("organisation" to "Mock Party", "locality" to "London", "country" to "GB")) + assertThat(config.parseAs().value).isEqualTo(name1) } @Test @@ -273,6 +278,7 @@ class ConfigParsingTest { data class OldData( @OldConfig("oldValue") val newValue: String) + data class DataWithCompanion(val value: Int) { companion object { @Suppress("unused") diff --git a/node/src/main/kotlin/net/corda/node/ArgsParser.kt b/node/src/main/kotlin/net/corda/node/ArgsParser.kt index 8353dd7f1a..bb755ede1c 100644 --- a/node/src/main/kotlin/net/corda/node/ArgsParser.kt +++ b/node/src/main/kotlin/net/corda/node/ArgsParser.kt @@ -2,7 +2,10 @@ package net.corda.node import joptsimple.OptionParser import joptsimple.util.EnumConverter +import joptsimple.util.PathConverter +import net.corda.core.internal.CertRole import net.corda.core.internal.div +import net.corda.core.internal.exists import net.corda.node.services.config.ConfigHelper import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.parseAsNodeConfiguration @@ -33,9 +36,11 @@ class ArgsParser { private val sshdServerArg = optionParser.accepts("sshd", "Enables SSHD server for node administration.") private val noLocalShellArg = optionParser.accepts("no-local-shell", "Do not start the embedded shell locally.") private val isRegistrationArg = optionParser.accepts("initial-registration", "Start initial node registration with Corda network to obtain certificate from the permissioning server.") - private val networkRootTruststorePathArg = optionParser.accepts("network-root-truststore", "Network root trust store obtained from network operator.") + private val networkRootTrustStorePathArg = optionParser.accepts("network-root-truststore", "Network root trust store obtained from network operator.") .withRequiredArg() - private val networkRootTruststorePasswordArg = optionParser.accepts("network-root-truststore-password", "Network root trust store password obtained from network operator.") + .withValuesConvertedBy(PathConverter()) + .defaultsTo((Paths.get("certificates") / "network-root-truststore.jks")) + private val networkRootTrustStorePasswordArg = optionParser.accepts("network-root-truststore-password", "Network root trust store password obtained from network operator.") .withRequiredArg() private val isVersionArg = optionParser.accepts("version", "Print the version and exit") private val justGenerateNodeInfoArg = optionParser.accepts("just-generate-node-info", @@ -59,16 +64,23 @@ class ArgsParser { val sshdServer = optionSet.has(sshdServerArg) val justGenerateNodeInfo = optionSet.has(justGenerateNodeInfoArg) val bootstrapRaftCluster = optionSet.has(bootstrapRaftClusterArg) - val networkRootTruststorePath = optionSet.valueOf(networkRootTruststorePathArg)?.let { Paths.get(it).normalize().toAbsolutePath() } - val networkRootTruststorePassword = optionSet.valueOf(networkRootTruststorePasswordArg) + val networkRootTrustStorePath = optionSet.valueOf(networkRootTrustStorePathArg) + val networkRootTrustStorePassword = optionSet.valueOf(networkRootTrustStorePasswordArg) + + val registrationConfig = if (isRegistration) { + requireNotNull(networkRootTrustStorePassword) { "Network root trust store password must be provided in registration mode." } + require(networkRootTrustStorePath.exists()) { "Network root trust store path: '$networkRootTrustStorePath' doesn't exist" } + NodeRegistrationOption(networkRootTrustStorePath, networkRootTrustStorePassword) + } else { + null + } + return CmdLineOptions(baseDirectory, configFile, help, loggingLevel, logToConsole, - isRegistration, - networkRootTruststorePath, - networkRootTruststorePassword, + registrationConfig, isVersion, noLocalShell, sshdServer, @@ -79,26 +91,28 @@ class ArgsParser { fun printHelp(sink: PrintStream) = optionParser.printHelpOn(sink) } +data class NodeRegistrationOption(val networkRootTrustStorePath: Path, val networkRootTrustStorePassword: String) + data class CmdLineOptions(val baseDirectory: Path, val configFile: Path, val help: Boolean, val loggingLevel: Level, val logToConsole: Boolean, - val isRegistration: Boolean, - val networkRootTruststorePath: Path?, - val networkRootTruststorePassword: String?, + val nodeRegistrationConfig: NodeRegistrationOption?, val isVersion: Boolean, val noLocalShell: Boolean, val sshdServer: Boolean, val justGenerateNodeInfo: Boolean, val bootstrapRaftCluster: Boolean) { fun loadConfig(): NodeConfiguration { - val config = ConfigHelper.loadConfig(baseDirectory, configFile).parseAsNodeConfiguration() - if (isRegistration) { + val config = ConfigHelper.loadConfig(baseDirectory, configFile, configOverrides = ConfigFactory.parseMap( + mapOf("noLocalShell" to this.noLocalShell) + )).parseAsNodeConfiguration() + + if (nodeRegistrationConfig != null) { requireNotNull(config.compatibilityZoneURL) { "Compatibility Zone Url must be provided in registration mode." } - requireNotNull(networkRootTruststorePath) { "Network root trust store path must be provided in registration mode." } - requireNotNull(networkRootTruststorePassword) { "Network root trust store password must be provided in registration mode." } } + return config } } 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 8f1676e471..0792356376 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -98,9 +98,9 @@ open class NodeStartup(val args: Array) { try { banJavaSerialisation(conf) preNetworkRegistration(conf) - if (shouldRegisterWithNetwork(cmdlineOptions, conf)) { + if (cmdlineOptions.nodeRegistrationConfig != null) { // Null checks for [compatibilityZoneURL], [rootTruststorePath] and [rootTruststorePassword] has been done in [CmdLineOptions.loadConfig] - registerWithNetwork(conf, cmdlineOptions.networkRootTruststorePath!!, cmdlineOptions.networkRootTruststorePassword!!) + registerWithNetwork(conf, cmdlineOptions.nodeRegistrationConfig) return true } logStartupInfo(versionInfo, cmdlineOptions, conf) @@ -183,12 +183,7 @@ open class NodeStartup(val args: Array) { logger.info("Starting as node on ${conf.p2pAddress}") } - private fun shouldRegisterWithNetwork(cmdlineOptions: CmdLineOptions, conf: NodeConfiguration): Boolean { - val compatibilityZoneURL = conf.compatibilityZoneURL - return !(!cmdlineOptions.isRegistration || compatibilityZoneURL == null) - } - - open protected fun registerWithNetwork(conf: NodeConfiguration, networkRootTruststorePath: Path, networkRootTruststorePassword: String) { + open protected fun registerWithNetwork(conf: NodeConfiguration, nodeRegistrationConfig: NodeRegistrationOption) { val compatibilityZoneURL = conf.compatibilityZoneURL!! println() println("******************************************************************") @@ -196,7 +191,7 @@ open class NodeStartup(val args: Array) { println("* Registering as a new participant with Corda network *") println("* *") println("******************************************************************") - NetworkRegistrationHelper(conf, HTTPNetworkRegistrationService(compatibilityZoneURL), networkRootTruststorePath, networkRootTruststorePassword).buildKeystore() + NetworkRegistrationHelper(conf, HTTPNetworkRegistrationService(compatibilityZoneURL), nodeRegistrationConfig).buildKeystore() } open protected fun loadConfigFile(cmdlineOptions: CmdLineOptions): NodeConfiguration = cmdlineOptions.loadConfig() diff --git a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt index 287f4b41c9..1a253a701a 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt @@ -3,7 +3,10 @@ package net.corda.node.utilities.registration import net.corda.core.crypto.Crypto import net.corda.core.identity.CordaX500Name import net.corda.core.internal.* +import net.corda.node.NodeRegistrationOption import net.corda.node.services.config.NodeConfiguration +import net.corda.nodeapi.internal.DevIdentityGenerator +import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.crypto.X509Utilities @@ -22,10 +25,18 @@ import java.security.cert.X509Certificate * Helper for managing the node registration process, which checks for any existing certificates and requests them if * needed. */ -class NetworkRegistrationHelper(private val config: NodeConfiguration, +class NetworkRegistrationHelper(private val config: SSLConfiguration, + private val myLegalName: CordaX500Name, + private val emailAddress: String, private val certService: NetworkRegistrationService, - networkRootTrustStorePath: Path, - networkRootTruststorePassword: String) { + private val networkRootTrustStorePath: Path, + networkRootTrustStorePassword: String, + private val certRole: CertRole) { + + // Constructor for corda node, cert role is restricted to [CertRole.NODE_CA]. + constructor(config: NodeConfiguration, certService: NetworkRegistrationService, regConfig: NodeRegistrationOption) : + this(config, config.myLegalName, config.emailAddress, certService, regConfig.networkRootTrustStorePath, regConfig.networkRootTrustStorePassword, CertRole.NODE_CA) + private companion object { const val SELF_SIGNED_PRIVATE_KEY = "Self Signed Private Key" } @@ -41,7 +52,7 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, "$networkRootTrustStorePath does not exist. This file must contain the root CA cert of your compatibility zone. " + "Please contact your CZ operator." } - rootTrustStore = X509KeyStore.fromFile(networkRootTrustStorePath, networkRootTruststorePassword) + rootTrustStore = X509KeyStore.fromFile(networkRootTrustStorePath, networkRootTrustStorePassword) rootCert = rootTrustStore.getCertificate(CORDA_ROOT_CA) } @@ -68,7 +79,7 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, // We use the self sign certificate to store the key temporarily in the keystore while waiting for the request approval. if (SELF_SIGNED_PRIVATE_KEY !in nodeKeyStore) { val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val selfSignCert = X509Utilities.createSelfSignedCACertificate(config.myLegalName.x500Principal, keyPair) + val selfSignCert = X509Utilities.createSelfSignedCACertificate(myLegalName.x500Principal, keyPair) // Save to the key store. nodeKeyStore.setPrivateKey(SELF_SIGNED_PRIVATE_KEY, keyPair.private, listOf(selfSignCert), keyPassword = privateKeyPassword) nodeKeyStore.save() @@ -87,36 +98,59 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, throw certificateRequestException } - val nodeCaCert = certificates[0] + val certificate = certificates.first() val nodeCaSubject = try { - CordaX500Name.build(nodeCaCert.subjectX500Principal) + CordaX500Name.build(certificate.subjectX500Principal) } catch (e: IllegalArgumentException) { throw CertificateRequestException("Received node CA cert has invalid subject name: ${e.message}") } - if (nodeCaSubject != config.myLegalName) { + if (nodeCaSubject != myLegalName) { throw CertificateRequestException("Subject of received node CA cert doesn't match with node legal name: $nodeCaSubject") } val nodeCaCertRole = try { - CertRole.extract(nodeCaCert) + CertRole.extract(certificate) } catch (e: IllegalArgumentException) { throw CertificateRequestException("Unable to extract cert role from received node CA cert: ${e.message}") } - if (nodeCaCertRole != CertRole.NODE_CA) { - throw CertificateRequestException("Received node CA cert has invalid role: $nodeCaCertRole") - } // Validate certificate chain returned from the doorman with the root cert obtained via out-of-band process, to prevent MITM attack on doorman server. X509Utilities.validateCertificateChain(rootCert, certificates) println("Certificate signing request approved, storing private key with the certificate chain.") - // Save private key and certificate chain to the key store. - nodeKeyStore.setPrivateKey(CORDA_CLIENT_CA, keyPair.private, certificates, keyPassword = privateKeyPassword) - nodeKeyStore.internal.deleteEntry(SELF_SIGNED_PRIVATE_KEY) - nodeKeyStore.save() - println("Node private key and certificate stored in ${config.nodeKeystore}.") + when (nodeCaCertRole) { + CertRole.NODE_CA -> { + // Save private key and certificate chain to the key store. + nodeKeyStore.setPrivateKey(CORDA_CLIENT_CA, keyPair.private, certificates, keyPassword = privateKeyPassword) + nodeKeyStore.internal.deleteEntry(SELF_SIGNED_PRIVATE_KEY) + nodeKeyStore.save() + println("Node private key and certificate stored in ${config.nodeKeystore}.") + + config.loadSslKeyStore(createNew = true).update { + println("Generating SSL certificate for node messaging service.") + val sslKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val sslCert = X509Utilities.createCertificate( + CertificateType.TLS, + certificate, + keyPair, + myLegalName.x500Principal, + sslKeyPair.public) + setPrivateKey(CORDA_CLIENT_TLS, sslKeyPair.private, listOf(sslCert) + certificates) + } + println("SSL private key and certificate stored in ${config.sslKeystore}.") + } + // TODO: Fix this, this is not needed in corda node. + CertRole.SERVICE_IDENTITY -> { + // Only create keystore containing notary's key for service identity role. + nodeKeyStore.setPrivateKey("${DevIdentityGenerator.DISTRIBUTED_NOTARY_ALIAS_PREFIX}-private-key", keyPair.private, certificates, keyPassword = privateKeyPassword) + nodeKeyStore.internal.deleteEntry(SELF_SIGNED_PRIVATE_KEY) + nodeKeyStore.save() + println("Service identity private key and certificate stored in ${config.nodeKeystore}.") + } + else -> throw CertificateRequestException("Received node CA cert has invalid role: $nodeCaCertRole") + } // Save root certificates to trust store. config.loadTrustStore(createNew = true).update { println("Generating trust store for corda node.") @@ -124,20 +158,6 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, setCertificate(CORDA_ROOT_CA, certificates.last()) } println("Node trust store stored in ${config.trustStoreFile}.") - - config.loadSslKeyStore(createNew = true).update { - println("Generating SSL certificate for node messaging service.") - val sslKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val sslCert = X509Utilities.createCertificate( - CertificateType.TLS, - nodeCaCert, - keyPair, - config.myLegalName.x500Principal, - sslKeyPair.public) - setPrivateKey(CORDA_CLIENT_TLS, sslKeyPair.private, listOf(sslCert) + certificates) - } - println("SSL private key and certificate stored in ${config.sslKeystore}.") - // All done, clean up temp files. requestIdStore.deleteIfExists() } @@ -169,15 +189,15 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private fun submitOrResumeCertificateSigningRequest(keyPair: KeyPair): String { // Retrieve request id from file if exists, else post a request to server. return if (!requestIdStore.exists()) { - val request = X509Utilities.createCertificateSigningRequest(config.myLegalName.x500Principal, config.emailAddress, keyPair) + val request = X509Utilities.createCertificateSigningRequest(myLegalName.x500Principal, emailAddress, keyPair, certRole) val writer = StringWriter() JcaPEMWriter(writer).use { it.writeObject(PemObject("CERTIFICATE REQUEST", request.encoded)) } println("Certificate signing request with the following information will be submitted to the Corda certificate signing server.") println() - println("Legal Name: ${config.myLegalName}") - println("Email: ${config.emailAddress}") + println("Legal Name: $myLegalName") + println("Email: $emailAddress") println() println("Public Key: ${keyPair.public}") println() diff --git a/node/src/test/kotlin/net/corda/node/ArgsParserTest.kt b/node/src/test/kotlin/net/corda/node/ArgsParserTest.kt index 96f98d8c33..8ddb9707e9 100644 --- a/node/src/test/kotlin/net/corda/node/ArgsParserTest.kt +++ b/node/src/test/kotlin/net/corda/node/ArgsParserTest.kt @@ -2,12 +2,14 @@ package net.corda.node import joptsimple.OptionException import net.corda.core.internal.div +import net.corda.nodeapi.internal.crypto.X509KeyStore import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.junit.Test import org.slf4j.event.Level import java.nio.file.Paths import kotlin.test.assertEquals +import kotlin.test.assertNotNull class ArgsParserTest { private val parser = ArgsParser() @@ -21,14 +23,12 @@ class ArgsParserTest { help = false, logToConsole = false, loggingLevel = Level.INFO, - isRegistration = false, + nodeRegistrationConfig = null, isVersion = false, noLocalShell = false, sshdServer = false, justGenerateNodeInfo = false, - bootstrapRaftCluster = false, - networkRootTruststorePassword = null, - networkRootTruststorePath = null)) + bootstrapRaftCluster = false)) } @Test @@ -113,11 +113,17 @@ class ArgsParserTest { @Test fun `initial-registration`() { - val truststorePath = Paths.get("truststore") / "file.jks" + val truststorePath = workingDirectory / "truststore" / "file.jks" + assertThatExceptionOfType(IllegalArgumentException::class.java).isThrownBy { + parser.parse("--initial-registration", "--network-root-truststore", "$truststorePath", "--network-root-truststore-password", "password-test") + }.withMessageContaining("Network root trust store path").withMessageContaining("doesn't exist") + + X509KeyStore.fromFile(truststorePath, "dummy_password", createNew = true) + val cmdLineOptions = parser.parse("--initial-registration", "--network-root-truststore", "$truststorePath", "--network-root-truststore-password", "password-test") - assertThat(cmdLineOptions.isRegistration).isTrue() - assertEquals(truststorePath.toAbsolutePath(), cmdLineOptions.networkRootTruststorePath) - assertEquals("password-test", cmdLineOptions.networkRootTruststorePassword) + assertNotNull(cmdLineOptions.nodeRegistrationConfig) + assertEquals(truststorePath.toAbsolutePath(), cmdLineOptions.nodeRegistrationConfig?.networkRootTrustStorePath) + assertEquals("password-test", cmdLineOptions.nodeRegistrationConfig?.networkRootTrustStorePassword) } @Test diff --git a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt index 2371ab4437..d77e61bcae 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt @@ -13,7 +13,9 @@ import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.internal.x500Name import net.corda.core.utilities.seconds +import net.corda.node.NodeRegistrationOption import net.corda.node.services.config.NodeConfiguration +import net.corda.nodeapi.internal.DevIdentityGenerator import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.crypto.X509Utilities @@ -141,6 +143,38 @@ class NetworkRegistrationHelperTest { }.isInstanceOf(CertPathValidatorException::class.java) } + @Test + fun `create service identity cert`() { + assertThat(config.nodeKeystore).doesNotExist() + assertThat(config.sslKeystore).doesNotExist() + assertThat(config.trustStoreFile).doesNotExist() + + val serviceIdentityCertPath = createServiceIdentityCertPath() + + saveNetworkTrustStore(serviceIdentityCertPath.last()) + createRegistrationHelper(serviceIdentityCertPath).buildKeystore() + + val nodeKeystore = config.loadNodeKeyStore() + val trustStore = config.loadTrustStore() + assertThat(config.sslKeystore).doesNotExist() + + val serviceIdentityAlias = "${DevIdentityGenerator.DISTRIBUTED_NOTARY_ALIAS_PREFIX}-private-key" + + nodeKeystore.run { + assertFalse(contains(X509Utilities.CORDA_INTERMEDIATE_CA)) + assertFalse(contains(X509Utilities.CORDA_ROOT_CA)) + assertFalse(contains(X509Utilities.CORDA_CLIENT_TLS)) + assertFalse(contains(X509Utilities.CORDA_CLIENT_CA)) + assertThat(getCertificateChain(serviceIdentityAlias)).containsExactlyElementsOf(serviceIdentityCertPath) + } + + trustStore.run { + assertFalse(contains(X509Utilities.CORDA_CLIENT_CA)) + assertFalse(contains(X509Utilities.CORDA_INTERMEDIATE_CA)) + assertThat(getCertificate(X509Utilities.CORDA_ROOT_CA)).isEqualTo(serviceIdentityCertPath.last()) + } + } + private fun createNodeCaCertPath(type: CertificateType = CertificateType.NODE_CA, legalName: CordaX500Name = nodeLegalName): List { val (rootCa, intermediateCa) = createDevIntermediateCaCertPath() @@ -156,12 +190,25 @@ class NetworkRegistrationHelperTest { return listOf(nodeCaCert, intermediateCa.certificate, rootCa.certificate) } + private fun createServiceIdentityCertPath(type: CertificateType = CertificateType.SERVICE_IDENTITY, + legalName: CordaX500Name = nodeLegalName): List { + val (rootCa, intermediateCa) = createDevIntermediateCaCertPath() + val keyPair = Crypto.generateKeyPair() + val serviceIdentityCert = X509Utilities.createCertificate( + type, + intermediateCa.certificate, + intermediateCa.keyPair, + legalName.x500Principal, + keyPair.public) + return listOf(serviceIdentityCert, intermediateCa.certificate, rootCa.certificate) + } + private fun createRegistrationHelper(response: List): NetworkRegistrationHelper { val certService = rigorousMock().also { doReturn(requestId).whenever(it).submitRequest(any()) doReturn(CertificateResponse(5.seconds, response)).whenever(it).retrieveCertificates(eq(requestId)) } - return NetworkRegistrationHelper(config, certService, config.certificatesDirectory / networkRootTrustStoreFileName, networkRootTrustStorePassword) + return NetworkRegistrationHelper(config, certService, NodeRegistrationOption(config.certificatesDirectory / networkRootTrustStoreFileName, networkRootTrustStorePassword)) } private fun saveNetworkTrustStore(rootCert: X509Certificate) { diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt index 7782e740bb..2eeba3c88f 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt @@ -23,6 +23,7 @@ import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.millis +import net.corda.node.NodeRegistrationOption import net.corda.node.internal.Node import net.corda.node.internal.NodeStartup import net.corda.node.internal.StartedNode @@ -265,7 +266,7 @@ class DriverDSLImpl( return if (startNodesInProcess) { executorService.fork { - NetworkRegistrationHelper(config.corda, HTTPNetworkRegistrationService(compatibilityZoneURL), rootTruststorePath, rootTruststorePassword).buildKeystore() + NetworkRegistrationHelper(config.corda, HTTPNetworkRegistrationService(compatibilityZoneURL), NodeRegistrationOption(rootTruststorePath, rootTruststorePassword)).buildKeystore() config } } else {