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
This commit is contained in:
Patrick Kuo 2018-01-24 16:17:32 +00:00 committed by GitHub
parent 45b23737c7
commit 5be4df918e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 169 additions and 115 deletions

View File

@ -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.

View File

@ -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,
@ -58,7 +59,8 @@ data class NetworkMapConfig(val cacheTimeout: Long,
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<NetworkManagementServerParameters>()
// 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
}

View File

@ -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<CertPathAndKey, LocalSigner>? {
if (parameters.keystorePath == null) return null
@ -174,7 +73,8 @@ fun main(args: Array<String>) {
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!"),

View File

@ -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)
}

View File

@ -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<X509Certificate> {
networkRootTrustStoreDirectory: Path,
networkRootTrustStorePassword: String): Array<X509Certificate> {
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)
}

View File

@ -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<OptionException> {
parseParameters("--trust-store-password")
}
// Should fail if password is provided in mode other then root keygen.
assertFailsWith<IllegalArgumentException> {
parseParameters("--config-file", validConfigPath, "--trust-store-password", "testPassword")
}
// trust store password is optional.
assertNull(parseParameters("--config-file", validConfigPath).trustStorePassword)
}
}