From 5be4df918ed1651e52b007bed37dcd8a69a2e874 Mon Sep 17 00:00:00 2001 From: Patrick Kuo Date: Wed, 24 Jan 2018 16:17:32 +0000 Subject: [PATCH] Make truststore password configurable during root store generation in doorman (#388) * Doorman will now ask for truststore password, or can be provided in cmd line arg. * rebase and changed keystore filename for HSM * make "network-root-truststore.jks" a const --- network-management/README.md | 5 + .../doorman/DoormanParameters.kt | 21 +++- .../r3/corda/networkmanage/doorman/Main.kt | 104 +--------------- .../doorman/NetworkManagementUtilities.kt | 117 ++++++++++++++++++ .../hsm/generator/KeyCertificateGenerator.kt | 17 +-- .../doorman/DoormanParametersTest.kt | 20 +++ 6 files changed, 169 insertions(+), 115 deletions(-) create mode 100644 network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementUtilities.kt diff --git a/network-management/README.md b/network-management/README.md index 28b0bbb704..0fa4518216 100644 --- a/network-management/README.md +++ b/network-management/README.md @@ -160,6 +160,11 @@ networkMapConfig { A trust store file containing the root trust certificate will be produced in the location `distribute-nodes / truststore.jks` (relative to `rootStorePath`). `truststore.jks` must be copied to the `certificates` directory of each node before they attempt to register. The trust store password is `trustpass`. + A trust store containing the root certificate will be created in the location `distribute-nodes / network-root-truststore.jks` + (relative to `rootStorePath`). The trust store's password can be set using command line argument `--trust-store-password`, + or the doorman's keygen utility will ask for password input if trust store password is not provided using this flag. + Path to the network root trust store and password must be provided to corda node via command line arguments before + they attempt to register. ### 2. Start Doorman service for notary registration Start the network management server with the doorman service for initial bootstrapping. Network map service (`networkMapConfig`) should be **disabled** at this point. diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/DoormanParameters.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/DoormanParameters.kt index 5c2aabae0e..0ee3264c50 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/DoormanParameters.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/DoormanParameters.kt @@ -22,6 +22,7 @@ data class NetworkManagementServerParameters(// TODO: Move local signing to sign val networkMapConfig: NetworkMapConfig?, val updateNetworkParameters: Path?, + val trustStorePassword: String?, // TODO Should be part of a localSigning sub-config val keystorePath: Path? = null, @@ -53,12 +54,13 @@ data class DoormanConfig(val approveAll: Boolean = false, val approveInterval: Long = NetworkManagementServerParameters.DEFAULT_APPROVE_INTERVAL.toMillis()) data class NetworkMapConfig(val cacheTimeout: Long, - // TODO: Move signing to signing server. + // TODO: Move signing to signing server. val signInterval: Long = NetworkManagementServerParameters.DEFAULT_SIGN_INTERVAL.toMillis()) enum class Mode { // TODO CA_KEYGEN now also generates the nework map cert, so it should be renamed. - DOORMAN, CA_KEYGEN, ROOT_KEYGEN + DOORMAN, + CA_KEYGEN, ROOT_KEYGEN } data class JiraConfig( @@ -82,6 +84,9 @@ fun parseParameters(vararg args: String): NetworkManagementServerParameters { accepts("mode", "Set the mode of this application") .withRequiredArg() .defaultsTo(Mode.DOORMAN.name) + accepts("trust-store-password", "Password for generated network root trust store. Only required when operating in root keygen mode.") + .withRequiredArg() + .describedAs("password") } // The config-file option is changed to configFile @@ -92,8 +97,14 @@ fun parseParameters(vararg args: String): NetworkManagementServerParameters { } require(configFile.isRegularFile()) { "Config file $configFile does not exist" } - return argConfig.withFallback(ConfigFactory.parseFile(configFile.toFile(), ConfigParseOptions.defaults().setAllowMissing(true))) + val config = argConfig.withFallback(ConfigFactory.parseFile(configFile.toFile(), ConfigParseOptions.defaults().setAllowMissing(true))) .resolve() - .parseAs() -} + .parseAs() + // Make sure trust store password is only specified in root keygen mode. + if (config.mode != Mode.ROOT_KEYGEN) { + require(config.trustStorePassword == null) { "Trust store password should not be specified in this mode." } + } + + return config +} diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt index 7d9fede12b..3b57cf7f0b 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt @@ -33,107 +33,6 @@ data class NetworkMapStartParams(val signer: LocalSigner?, val updateNetworkPara data class NetworkManagementServerStatus(var serverStartTime: Instant = Instant.now(), var lastRequestCheckTime: Instant? = null) -/** Read password from console, do a readLine instead if console is null (e.g. when debugging in IDE). */ -internal fun readPassword(fmt: String): String { - return if (System.console() != null) { - String(System.console().readPassword(fmt)) - } else { - print(fmt) - readLine() ?: "" - } -} - -// Keygen utilities. -fun generateRootKeyPair(rootStoreFile: Path, rootKeystorePass: String?, rootPrivateKeyPass: String?) { - println("Generating Root CA keypair and certificate.") - // Get password from console if not in config. - val rootKeystorePassword = rootKeystorePass ?: readPassword("Root Keystore Password: ") - // Ensure folder exists. - rootStoreFile.parent.createDirectories() - val rootStore = loadOrCreateKeyStore(rootStoreFile, rootKeystorePassword) - val rootPrivateKeyPassword = rootPrivateKeyPass ?: readPassword("Root Private Key Password: ") - - if (rootStore.containsAlias(X509Utilities.CORDA_ROOT_CA)) { - println("${X509Utilities.CORDA_ROOT_CA} already exists in keystore, process will now terminate.") - println(rootStore.getCertificate(X509Utilities.CORDA_ROOT_CA)) - exitProcess(1) - } - - val selfSignKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - // TODO Make the cert subject configurable - val selfSignCert = X509Utilities.createSelfSignedCACertificate( - CordaX500Name(commonName = "Corda Root CA", organisation = "R3 Ltd", locality = "London", country = "GB", organisationUnit = "Corda", state = null).x500Principal, - selfSignKey) - rootStore.addOrReplaceKey(X509Utilities.CORDA_ROOT_CA, selfSignKey.private, rootPrivateKeyPassword.toCharArray(), arrayOf(selfSignCert)) - rootStore.save(rootStoreFile, rootKeystorePassword) - - val nodeTrustStoreFile = (rootStoreFile.parent / "distribute-nodes").createDirectories() / "truststore.jks" - // TODO The password for trust store must be a config option - val nodeTrustStore = loadOrCreateKeyStore(nodeTrustStoreFile, "trustpass") - nodeTrustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, selfSignCert) - nodeTrustStore.save(nodeTrustStoreFile, "trustpass") - println("Trust store for distribution to nodes created in $nodeTrustStore") - - println("Root CA keypair and certificate stored in ${rootStoreFile.toAbsolutePath()}.") - println(selfSignCert) -} - -fun generateSigningKeyPairs(keystoreFile: Path, rootStoreFile: Path, rootKeystorePass: String?, rootPrivateKeyPass: String?, keystorePass: String?, caPrivateKeyPass: String?) { - println("Generating intermediate and network map key pairs and certificates using root key store $rootStoreFile.") - // Get password from console if not in config. - val rootKeystorePassword = rootKeystorePass ?: readPassword("Root key store password: ") - val rootPrivateKeyPassword = rootPrivateKeyPass ?: readPassword("Root private key password: ") - val rootKeyStore = loadKeyStore(rootStoreFile, rootKeystorePassword) - - val rootKeyPairAndCert = rootKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_ROOT_CA, rootPrivateKeyPassword) - - val keyStorePassword = keystorePass ?: readPassword("Key store Password: ") - val privateKeyPassword = caPrivateKeyPass ?: readPassword("Private key Password: ") - // Ensure folder exists. - keystoreFile.parent.createDirectories() - val keyStore = loadOrCreateKeyStore(keystoreFile, keyStorePassword) - - fun storeCertIfAbsent(alias: String, certificateType: CertificateType, subject: X500Principal, signatureScheme: SignatureScheme) { - if (keyStore.containsAlias(alias)) { - println("$alias already exists in keystore:") - println(keyStore.getCertificate(alias)) - return - } - - val keyPair = Crypto.generateKeyPair(signatureScheme) - val cert = X509Utilities.createCertificate( - certificateType, - rootKeyPairAndCert.certificate, - rootKeyPairAndCert.keyPair, - subject, - keyPair.public - ) - keyStore.addOrReplaceKey( - alias, - keyPair.private, - privateKeyPassword.toCharArray(), - arrayOf(cert, rootKeyPairAndCert.certificate) - ) - keyStore.save(keystoreFile, keyStorePassword) - - println("$certificateType key pair and certificate stored in $keystoreFile.") - println(cert) - } - - storeCertIfAbsent( - DEFAULT_CSR_CERTIFICATE_NAME, - CertificateType.INTERMEDIATE_CA, - X500Principal("CN=Corda Intermediate CA,OU=Corda,O=R3 Ltd,L=London,C=GB"), - X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - - storeCertIfAbsent( - CORDA_NETWORK_MAP, - CertificateType.NETWORK_MAP, - X500Principal("CN=Corda Network Map,OU=Corda,O=R3 Ltd,L=London,C=GB"), - Crypto.EDDSA_ED25519_SHA512) -} - - private fun processKeyStore(parameters: NetworkManagementServerParameters): Pair? { if (parameters.keystorePath == null) return null @@ -174,7 +73,8 @@ fun main(args: Array) { Mode.ROOT_KEYGEN -> generateRootKeyPair( rootStorePath ?: throw IllegalArgumentException("The 'rootStorePath' parameter must be specified when generating keys!"), rootKeystorePassword, - rootPrivateKeyPassword) + rootPrivateKeyPassword, + trustStorePassword) Mode.CA_KEYGEN -> generateSigningKeyPairs( keystorePath ?: throw IllegalArgumentException("The 'keystorePath' parameter must be specified when generating keys!"), rootStorePath ?: throw IllegalArgumentException("The 'rootStorePath' parameter must be specified when generating keys!"), diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementUtilities.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementUtilities.kt new file mode 100644 index 0000000000..d4aa4578e0 --- /dev/null +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementUtilities.kt @@ -0,0 +1,117 @@ +package com.r3.corda.networkmanage.doorman + +import com.r3.corda.networkmanage.common.utils.CORDA_NETWORK_MAP +import com.r3.corda.networkmanage.hsm.configuration.Parameters.Companion.DEFAULT_CSR_CERTIFICATE_NAME +import net.corda.core.crypto.Crypto +import net.corda.core.crypto.SignatureScheme +import net.corda.core.internal.createDirectories +import net.corda.core.internal.div +import net.corda.nodeapi.internal.crypto.* +import java.nio.file.Path +import javax.security.auth.x500.X500Principal +import kotlin.system.exitProcess + +private val cordaX500Name = "OU=Corda,O=R3 Ltd,L=London,C=GB" + +const val NETWORK_ROOT_TRUSTSTORE_FILENAME = "network-root-truststore.jks" + +/** Read password from console, do a readLine instead if console is null (e.g. when debugging in IDE). */ +internal fun readPassword(fmt: String): String { + return if (System.console() != null) { + String(System.console().readPassword(fmt)) + } else { + print(fmt) + readLine() ?: "" + } +} + +// Keygen utilities. +fun generateRootKeyPair(rootStoreFile: Path, rootKeystorePass: String?, rootPrivateKeyPass: String?, networkRootTrustPass: String?) { + println("Generating Root CA keypair and certificate.") + // Get password from console if not in config. + val rootKeystorePassword = rootKeystorePass ?: readPassword("Root Keystore Password: ") + // Ensure folder exists. + rootStoreFile.parent.createDirectories() + val rootStore = loadOrCreateKeyStore(rootStoreFile, rootKeystorePassword) + val rootPrivateKeyPassword = rootPrivateKeyPass ?: readPassword("Root Private Key Password: ") + + if (rootStore.containsAlias(X509Utilities.CORDA_ROOT_CA)) { + println("${X509Utilities.CORDA_ROOT_CA} already exists in keystore, process will now terminate.") + println(rootStore.getCertificate(X509Utilities.CORDA_ROOT_CA)) + exitProcess(1) + } + + val selfSignKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + // TODO Make the cert subject configurable + val selfSignCert = X509Utilities.createSelfSignedCACertificate( + X500Principal("CN=Corda Root CA,$cordaX500Name"), + selfSignKey) + rootStore.addOrReplaceKey(X509Utilities.CORDA_ROOT_CA, selfSignKey.private, rootPrivateKeyPassword.toCharArray(), arrayOf(selfSignCert)) + rootStore.save(rootStoreFile, rootKeystorePassword) + + val trustStorePath = (rootStoreFile.parent / "distribute-nodes").createDirectories() / NETWORK_ROOT_TRUSTSTORE_FILENAME + + val networkRootTrustPassword = networkRootTrustPass ?: readPassword("Network Root Trust Store Password: ") + val networkRootTrustStore = loadOrCreateKeyStore(trustStorePath, networkRootTrustPassword) + networkRootTrustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, selfSignCert) + networkRootTrustStore.save(trustStorePath, networkRootTrustPassword) + println("Trust store for distribution to nodes created in $networkRootTrustStore") + + println("Root CA keypair and certificate stored in ${rootStoreFile.toAbsolutePath()}.") + println(selfSignCert) +} + +fun generateSigningKeyPairs(keystoreFile: Path, rootStoreFile: Path, rootKeystorePass: String?, rootPrivateKeyPass: String?, keystorePass: String?, caPrivateKeyPass: String?) { + println("Generating intermediate and network map key pairs and certificates using root key store $rootStoreFile.") + // Get password from console if not in config. + val rootKeystorePassword = rootKeystorePass ?: readPassword("Root key store password: ") + val rootPrivateKeyPassword = rootPrivateKeyPass ?: readPassword("Root private key password: ") + val rootKeyStore = loadKeyStore(rootStoreFile, rootKeystorePassword) + + val rootKeyPairAndCert = rootKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_ROOT_CA, rootPrivateKeyPassword) + + val keyStorePassword = keystorePass ?: readPassword("Key store Password: ") + val privateKeyPassword = caPrivateKeyPass ?: readPassword("Private key Password: ") + // Ensure folder exists. + keystoreFile.parent.createDirectories() + val keyStore = loadOrCreateKeyStore(keystoreFile, keyStorePassword) + + fun storeCertIfAbsent(alias: String, certificateType: CertificateType, subject: X500Principal, signatureScheme: SignatureScheme) { + if (keyStore.containsAlias(alias)) { + println("$alias already exists in keystore:") + println(keyStore.getCertificate(alias)) + return + } + + val keyPair = Crypto.generateKeyPair(signatureScheme) + val cert = X509Utilities.createCertificate( + certificateType, + rootKeyPairAndCert.certificate, + rootKeyPairAndCert.keyPair, + subject, + keyPair.public + ) + keyStore.addOrReplaceKey( + alias, + keyPair.private, + privateKeyPassword.toCharArray(), + arrayOf(cert, rootKeyPairAndCert.certificate) + ) + keyStore.save(keystoreFile, keyStorePassword) + + println("$certificateType key pair and certificate stored in $keystoreFile.") + println(cert) + } + + storeCertIfAbsent( + DEFAULT_CSR_CERTIFICATE_NAME, + CertificateType.INTERMEDIATE_CA, + X500Principal("CN=Corda Intermediate CA,$cordaX500Name"), + X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + + storeCertIfAbsent( + CORDA_NETWORK_MAP, + CertificateType.NETWORK_MAP, + X500Principal("CN=Corda Network Map,$cordaX500Name"), + Crypto.EDDSA_ED25519_SHA512) +} diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/generator/KeyCertificateGenerator.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/generator/KeyCertificateGenerator.kt index d21474fda1..4475f46c5a 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/generator/KeyCertificateGenerator.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/generator/KeyCertificateGenerator.kt @@ -4,6 +4,7 @@ import CryptoServerCXI.CryptoServerCXI.KEY_ALGO_ECDSA import CryptoServerCXI.CryptoServerCXI.KeyAttributes import CryptoServerJCE.CryptoServerProvider import com.r3.corda.networkmanage.common.utils.CORDA_NETWORK_MAP +import com.r3.corda.networkmanage.doorman.NETWORK_ROOT_TRUSTSTORE_FILENAME import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.createIntermediateCert import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.createSelfSignedCACert import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.getAndInitializeKeyStore @@ -67,8 +68,8 @@ class KeyCertificateGenerator(private val parameters: GeneratorParameters) { private fun CertificateConfiguration.generateRootCert(provider: CryptoServerProvider, keyPair: KeyPair, - trustStoreDirectory: Path, - trustStorePassword: String): Array { + networkRootTrustStoreDirectory: Path, + networkRootTrustStorePassword: String): Array { val certificate = createSelfSignedCACert(ROOT_CA, CordaX500Name.parse(subject).x500Name, keyPair, @@ -76,12 +77,12 @@ class KeyCertificateGenerator(private val parameters: GeneratorParameters) { provider, crlDistributionUrl, crlIssuer).certificate - val trustStorePath = trustStoreDirectory / "truststore.jks" - val trustStore = loadOrCreateKeyStore(trustStorePath, trustStorePassword) - logger.info("Trust store for distribution to nodes created in $trustStore") - trustStore.addOrReplaceCertificate(CORDA_ROOT_CA, certificate) - logger.info("Certificate $CORDA_ROOT_CA has been added to $trustStore") - trustStore.save(trustStorePath, trustStorePassword) + val networkRootTruststorePath = networkRootTrustStoreDirectory / NETWORK_ROOT_TRUSTSTORE_FILENAME + val networkRootTruststore = loadOrCreateKeyStore(networkRootTruststorePath, networkRootTrustStorePassword) + logger.info("Trust store for distribution to nodes created in $networkRootTruststorePath") + networkRootTruststore.addOrReplaceCertificate(CORDA_ROOT_CA, certificate) + logger.info("Certificate $CORDA_ROOT_CA has been added to $networkRootTruststorePath") + networkRootTruststore.save(networkRootTruststorePath, networkRootTrustStorePassword) logger.info("Trust store has been persisted. Ready for distribution.") return arrayOf(certificate) } diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/DoormanParametersTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/DoormanParametersTest.kt index d44ff5304a..473097caa3 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/DoormanParametersTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/DoormanParametersTest.kt @@ -2,12 +2,14 @@ package com.r3.corda.networkmanage.doorman import com.r3.corda.networkmanage.common.utils.ShowHelpException import com.typesafe.config.ConfigException +import joptsimple.OptionException import org.assertj.core.api.Assertions.assertThat import org.junit.Test import java.io.File import java.lang.reflect.InvocationTargetException import kotlin.test.assertEquals import kotlin.test.assertFailsWith +import kotlin.test.assertNull class DoormanParametersTest { private val validOverrideNetworkConfigPath = File("network-parameters.conf").absolutePath @@ -53,4 +55,22 @@ class DoormanParametersTest { assertEquals("username", parameter.jiraConfig?.username) assertEquals("password", parameter.jiraConfig?.password) } + + @Test + fun `should parse trust store password correctly`() { + val parameter = parseParameters("--config-file", validConfigPath, "--mode", "ROOT_KEYGEN", "--trust-store-password", "testPassword") + assertEquals("testPassword", parameter.trustStorePassword) + + assertFailsWith { + parseParameters("--trust-store-password") + } + + // Should fail if password is provided in mode other then root keygen. + assertFailsWith { + parseParameters("--config-file", validConfigPath, "--trust-store-password", "testPassword") + } + + // trust store password is optional. + assertNull(parseParameters("--config-file", validConfigPath).trustStorePassword) + } }