mirror of
https://github.com/corda/corda.git
synced 2025-01-14 00:39:57 +00:00
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:
parent
45b23737c7
commit
5be4df918e
@ -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.
|
||||
|
@ -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<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
|
||||
}
|
||||
|
@ -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!"),
|
||||
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user