Merge pull request #3114 from corda/cherrypick-corda-1317

CORDA-1317 - Add cert role to CSR and doorman issue cert according to the cert role
This commit is contained in:
Katelyn Baker 2018-05-18 14:28:41 +01:00 committed by GitHub
commit 839cd04fdc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 183 additions and 82 deletions

View File

@ -2,10 +2,7 @@
package net.corda.nodeapi.internal.config package net.corda.nodeapi.internal.config
import com.typesafe.config.Config import com.typesafe.config.*
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigUtil
import com.typesafe.config.ConfigValueFactory
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.noneOrSingle import net.corda.core.internal.noneOrSingle
import net.corda.core.internal.uncheckedCast 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)) NetworkHostAndPort::class -> NetworkHostAndPort.parse(getString(path))
Path::class -> Paths.get(getString(path)) Path::class -> Paths.get(getString(path))
URL::class -> URL(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() Properties::class -> getConfig(path).toProperties()
Config::class -> getConfig(path) Config::class -> getConfig(path)
else -> if (typeClass.java.isEnum) { else -> if (typeClass.java.isEnum) {

View File

@ -259,13 +259,17 @@ object X509Utilities {
private fun createCertificateSigningRequest(subject: X500Principal, private fun createCertificateSigningRequest(subject: X500Principal,
email: String, email: String,
keyPair: KeyPair, keyPair: KeyPair,
signatureScheme: SignatureScheme): PKCS10CertificationRequest { signatureScheme: SignatureScheme,
certRole: CertRole): PKCS10CertificationRequest {
val signer = ContentSignerBuilder.build(signatureScheme, keyPair.private, Crypto.findProvider(signatureScheme.providerName)) 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 { fun createCertificateSigningRequest(subject: X500Principal, email: String, keyPair: KeyPair, certRole: CertRole = CertRole.NODE_CA): PKCS10CertificationRequest {
return createCertificateSigningRequest(subject, email, keyPair, DEFAULT_TLS_SIGNATURE_SCHEME) return createCertificateSigningRequest(subject, email, keyPair, DEFAULT_TLS_SIGNATURE_SCHEME, certRole)
} }
fun buildCertPath(first: X509Certificate, remaining: List<X509Certificate>): CertPath { fun buildCertPath(first: X509Certificate, remaining: List<X509Certificate>): 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]. * Convert a [X509Certificate] into Bouncycastle's [X509CertificateHolder].
* *
* NOTE: To avoid unnecessary copying use [X509Certificate] where possible. * NOTE: To avoid unnecessary copying use [X509Certificate] where possible.
*/ */
fun X509Certificate.toBc() = X509CertificateHolder(encoded) fun X509Certificate.toBc() = X509CertificateHolder(encoded)
fun X509CertificateHolder.toJca(): X509Certificate = X509CertificateFactory().generateCertificate(encoded.inputStream()) fun X509CertificateHolder.toJca(): X509Certificate = X509CertificateFactory().generateCertificate(encoded.inputStream())
val CertPath.x509Certificates: List<X509Certificate> get() { val CertPath.x509Certificates: List<X509Certificate>
require(type == "X.509") { "Not an X.509 cert path: $this" } get() {
// We're not mapping the list to avoid creating a new one. require(type == "X.509") { "Not an X.509 cert path: $this" }
return uncheckedCast(certificates) // 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" } val Certificate.x509: X509Certificate get() = requireNotNull(this as? X509Certificate) { "Not an X.509 certificate: $this" }

View File

@ -87,10 +87,15 @@ class ConfigParsingTest {
@Test @Test
fun CordaX500Name() { fun CordaX500Name() {
val name1 = CordaX500Name(organisation = "Mock Party", locality = "London", country = "GB")
testPropertyType<CordaX500NameData, CordaX500NameListData, CordaX500Name>( testPropertyType<CordaX500NameData, CordaX500NameListData, CordaX500Name>(
CordaX500Name(organisation = "Mock Party", locality = "London", country = "GB"), name1,
CordaX500Name(organisation = "Mock Party 2", locality = "London", country = "GB"), CordaX500Name(organisation = "Mock Party 2", locality = "London", country = "GB"),
valuesToString = true) 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<CordaX500NameData>().value).isEqualTo(name1)
} }
@Test @Test
@ -273,6 +278,7 @@ class ConfigParsingTest {
data class OldData( data class OldData(
@OldConfig("oldValue") @OldConfig("oldValue")
val newValue: String) val newValue: String)
data class DataWithCompanion(val value: Int) { data class DataWithCompanion(val value: Int) {
companion object { companion object {
@Suppress("unused") @Suppress("unused")

View File

@ -1,8 +1,12 @@
package net.corda.node package net.corda.node
import com.typesafe.config.ConfigFactory
import joptsimple.OptionParser import joptsimple.OptionParser
import joptsimple.util.EnumConverter 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.div
import net.corda.core.internal.exists
import net.corda.node.services.config.ConfigHelper import net.corda.node.services.config.ConfigHelper
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.parseAsNodeConfiguration import net.corda.node.services.config.parseAsNodeConfiguration
@ -33,9 +37,11 @@ class ArgsParser {
private val sshdServerArg = optionParser.accepts("sshd", "Enables SSHD server for node administration.") 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 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 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() .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() .withRequiredArg()
private val isVersionArg = optionParser.accepts("version", "Print the version and exit") private val isVersionArg = optionParser.accepts("version", "Print the version and exit")
private val justGenerateNodeInfoArg = optionParser.accepts("just-generate-node-info", private val justGenerateNodeInfoArg = optionParser.accepts("just-generate-node-info",
@ -59,16 +65,23 @@ class ArgsParser {
val sshdServer = optionSet.has(sshdServerArg) val sshdServer = optionSet.has(sshdServerArg)
val justGenerateNodeInfo = optionSet.has(justGenerateNodeInfoArg) val justGenerateNodeInfo = optionSet.has(justGenerateNodeInfoArg)
val bootstrapRaftCluster = optionSet.has(bootstrapRaftClusterArg) val bootstrapRaftCluster = optionSet.has(bootstrapRaftClusterArg)
val networkRootTruststorePath = optionSet.valueOf(networkRootTruststorePathArg)?.let { Paths.get(it).normalize().toAbsolutePath() } val networkRootTrustStorePath = optionSet.valueOf(networkRootTrustStorePathArg)
val networkRootTruststorePassword = optionSet.valueOf(networkRootTruststorePasswordArg) 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, return CmdLineOptions(baseDirectory,
configFile, configFile,
help, help,
loggingLevel, loggingLevel,
logToConsole, logToConsole,
isRegistration, registrationConfig,
networkRootTruststorePath,
networkRootTruststorePassword,
isVersion, isVersion,
noLocalShell, noLocalShell,
sshdServer, sshdServer,
@ -79,26 +92,28 @@ class ArgsParser {
fun printHelp(sink: PrintStream) = optionParser.printHelpOn(sink) fun printHelp(sink: PrintStream) = optionParser.printHelpOn(sink)
} }
data class NodeRegistrationOption(val networkRootTrustStorePath: Path, val networkRootTrustStorePassword: String)
data class CmdLineOptions(val baseDirectory: Path, data class CmdLineOptions(val baseDirectory: Path,
val configFile: Path, val configFile: Path,
val help: Boolean, val help: Boolean,
val loggingLevel: Level, val loggingLevel: Level,
val logToConsole: Boolean, val logToConsole: Boolean,
val isRegistration: Boolean, val nodeRegistrationConfig: NodeRegistrationOption?,
val networkRootTruststorePath: Path?,
val networkRootTruststorePassword: String?,
val isVersion: Boolean, val isVersion: Boolean,
val noLocalShell: Boolean, val noLocalShell: Boolean,
val sshdServer: Boolean, val sshdServer: Boolean,
val justGenerateNodeInfo: Boolean, val justGenerateNodeInfo: Boolean,
val bootstrapRaftCluster: Boolean) { val bootstrapRaftCluster: Boolean) {
fun loadConfig(): NodeConfiguration { fun loadConfig(): NodeConfiguration {
val config = ConfigHelper.loadConfig(baseDirectory, configFile).parseAsNodeConfiguration() val config = ConfigHelper.loadConfig(baseDirectory, configFile, configOverrides = ConfigFactory.parseMap(
if (isRegistration) { mapOf("noLocalShell" to this.noLocalShell)
)).parseAsNodeConfiguration()
if (nodeRegistrationConfig != null) {
requireNotNull(config.compatibilityZoneURL) { "Compatibility Zone Url must be provided in registration mode." } 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 return config
} }
} }

View File

@ -98,9 +98,9 @@ open class NodeStartup(val args: Array<String>) {
try { try {
banJavaSerialisation(conf) banJavaSerialisation(conf)
preNetworkRegistration(conf) preNetworkRegistration(conf)
if (shouldRegisterWithNetwork(cmdlineOptions, conf)) { if (cmdlineOptions.nodeRegistrationConfig != null) {
// Null checks for [compatibilityZoneURL], [rootTruststorePath] and [rootTruststorePassword] has been done in [CmdLineOptions.loadConfig] // 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 return true
} }
logStartupInfo(versionInfo, cmdlineOptions, conf) logStartupInfo(versionInfo, cmdlineOptions, conf)
@ -183,12 +183,7 @@ open class NodeStartup(val args: Array<String>) {
logger.info("Starting as node on ${conf.p2pAddress}") logger.info("Starting as node on ${conf.p2pAddress}")
} }
private fun shouldRegisterWithNetwork(cmdlineOptions: CmdLineOptions, conf: NodeConfiguration): Boolean { open protected fun registerWithNetwork(conf: NodeConfiguration, nodeRegistrationConfig: NodeRegistrationOption) {
val compatibilityZoneURL = conf.compatibilityZoneURL
return !(!cmdlineOptions.isRegistration || compatibilityZoneURL == null)
}
open protected fun registerWithNetwork(conf: NodeConfiguration, networkRootTruststorePath: Path, networkRootTruststorePassword: String) {
val compatibilityZoneURL = conf.compatibilityZoneURL!! val compatibilityZoneURL = conf.compatibilityZoneURL!!
println() println()
println("******************************************************************") println("******************************************************************")
@ -196,7 +191,7 @@ open class NodeStartup(val args: Array<String>) {
println("* Registering as a new participant with Corda network *") println("* Registering as a new participant with Corda network *")
println("* *") println("* *")
println("******************************************************************") println("******************************************************************")
NetworkRegistrationHelper(conf, HTTPNetworkRegistrationService(compatibilityZoneURL), networkRootTruststorePath, networkRootTruststorePassword).buildKeystore() NetworkRegistrationHelper(conf, HTTPNetworkRegistrationService(compatibilityZoneURL), nodeRegistrationConfig).buildKeystore()
} }
open protected fun loadConfigFile(cmdlineOptions: CmdLineOptions): NodeConfiguration = cmdlineOptions.loadConfig() open protected fun loadConfigFile(cmdlineOptions: CmdLineOptions): NodeConfiguration = cmdlineOptions.loadConfig()

View File

@ -3,7 +3,10 @@ package net.corda.node.utilities.registration
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.* import net.corda.core.internal.*
import net.corda.node.NodeRegistrationOption
import net.corda.node.services.config.NodeConfiguration 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.CertificateType
import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.crypto.X509Utilities 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 * Helper for managing the node registration process, which checks for any existing certificates and requests them if
* needed. * 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, private val certService: NetworkRegistrationService,
networkRootTrustStorePath: Path, private val networkRootTrustStorePath: Path,
networkRootTruststorePassword: String) { 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 { private companion object {
const val SELF_SIGNED_PRIVATE_KEY = "Self Signed Private Key" 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. " + "$networkRootTrustStorePath does not exist. This file must contain the root CA cert of your compatibility zone. " +
"Please contact your CZ operator." "Please contact your CZ operator."
} }
rootTrustStore = X509KeyStore.fromFile(networkRootTrustStorePath, networkRootTruststorePassword) rootTrustStore = X509KeyStore.fromFile(networkRootTrustStorePath, networkRootTrustStorePassword)
rootCert = rootTrustStore.getCertificate(CORDA_ROOT_CA) 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. // 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) { if (SELF_SIGNED_PRIVATE_KEY !in nodeKeyStore) {
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) 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. // Save to the key store.
nodeKeyStore.setPrivateKey(SELF_SIGNED_PRIVATE_KEY, keyPair.private, listOf(selfSignCert), keyPassword = privateKeyPassword) nodeKeyStore.setPrivateKey(SELF_SIGNED_PRIVATE_KEY, keyPair.private, listOf(selfSignCert), keyPassword = privateKeyPassword)
nodeKeyStore.save() nodeKeyStore.save()
@ -87,36 +98,59 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration,
throw certificateRequestException throw certificateRequestException
} }
val nodeCaCert = certificates[0] val certificate = certificates.first()
val nodeCaSubject = try { val nodeCaSubject = try {
CordaX500Name.build(nodeCaCert.subjectX500Principal) CordaX500Name.build(certificate.subjectX500Principal)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
throw CertificateRequestException("Received node CA cert has invalid subject name: ${e.message}") 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") throw CertificateRequestException("Subject of received node CA cert doesn't match with node legal name: $nodeCaSubject")
} }
val nodeCaCertRole = try { val nodeCaCertRole = try {
CertRole.extract(nodeCaCert) CertRole.extract(certificate)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
throw CertificateRequestException("Unable to extract cert role from received node CA cert: ${e.message}") 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. // 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) X509Utilities.validateCertificateChain(rootCert, certificates)
println("Certificate signing request approved, storing private key with the certificate chain.") 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. // Save root certificates to trust store.
config.loadTrustStore(createNew = true).update { config.loadTrustStore(createNew = true).update {
println("Generating trust store for corda node.") println("Generating trust store for corda node.")
@ -124,20 +158,6 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration,
setCertificate(CORDA_ROOT_CA, certificates.last()) setCertificate(CORDA_ROOT_CA, certificates.last())
} }
println("Node trust store stored in ${config.trustStoreFile}.") 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. // All done, clean up temp files.
requestIdStore.deleteIfExists() requestIdStore.deleteIfExists()
} }
@ -169,15 +189,15 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration,
private fun submitOrResumeCertificateSigningRequest(keyPair: KeyPair): String { private fun submitOrResumeCertificateSigningRequest(keyPair: KeyPair): String {
// Retrieve request id from file if exists, else post a request to server. // Retrieve request id from file if exists, else post a request to server.
return if (!requestIdStore.exists()) { 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() val writer = StringWriter()
JcaPEMWriter(writer).use { JcaPEMWriter(writer).use {
it.writeObject(PemObject("CERTIFICATE REQUEST", request.encoded)) 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("Certificate signing request with the following information will be submitted to the Corda certificate signing server.")
println() println()
println("Legal Name: ${config.myLegalName}") println("Legal Name: $myLegalName")
println("Email: ${config.emailAddress}") println("Email: $emailAddress")
println() println()
println("Public Key: ${keyPair.public}") println("Public Key: ${keyPair.public}")
println() println()

View File

@ -2,12 +2,14 @@ package net.corda.node
import joptsimple.OptionException import joptsimple.OptionException
import net.corda.core.internal.div 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.assertThat
import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.Test import org.junit.Test
import org.slf4j.event.Level import org.slf4j.event.Level
import java.nio.file.Paths import java.nio.file.Paths
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNotNull
class ArgsParserTest { class ArgsParserTest {
private val parser = ArgsParser() private val parser = ArgsParser()
@ -21,14 +23,12 @@ class ArgsParserTest {
help = false, help = false,
logToConsole = false, logToConsole = false,
loggingLevel = Level.INFO, loggingLevel = Level.INFO,
isRegistration = false, nodeRegistrationConfig = null,
isVersion = false, isVersion = false,
noLocalShell = false, noLocalShell = false,
sshdServer = false, sshdServer = false,
justGenerateNodeInfo = false, justGenerateNodeInfo = false,
bootstrapRaftCluster = false, bootstrapRaftCluster = false))
networkRootTruststorePassword = null,
networkRootTruststorePath = null))
} }
@Test @Test
@ -113,11 +113,17 @@ class ArgsParserTest {
@Test @Test
fun `initial-registration`() { 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") val cmdLineOptions = parser.parse("--initial-registration", "--network-root-truststore", "$truststorePath", "--network-root-truststore-password", "password-test")
assertThat(cmdLineOptions.isRegistration).isTrue() assertNotNull(cmdLineOptions.nodeRegistrationConfig)
assertEquals(truststorePath.toAbsolutePath(), cmdLineOptions.networkRootTruststorePath) assertEquals(truststorePath.toAbsolutePath(), cmdLineOptions.nodeRegistrationConfig?.networkRootTrustStorePath)
assertEquals("password-test", cmdLineOptions.networkRootTruststorePassword) assertEquals("password-test", cmdLineOptions.nodeRegistrationConfig?.networkRootTrustStorePassword)
} }
@Test @Test

View File

@ -13,7 +13,9 @@ import net.corda.core.internal.createDirectories
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.core.internal.x500Name import net.corda.core.internal.x500Name
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.node.NodeRegistrationOption
import net.corda.node.services.config.NodeConfiguration 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.CertificateType
import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
@ -141,6 +143,38 @@ class NetworkRegistrationHelperTest {
}.isInstanceOf(CertPathValidatorException::class.java) }.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, private fun createNodeCaCertPath(type: CertificateType = CertificateType.NODE_CA,
legalName: CordaX500Name = nodeLegalName): List<X509Certificate> { legalName: CordaX500Name = nodeLegalName): List<X509Certificate> {
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath() val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
@ -156,12 +190,25 @@ class NetworkRegistrationHelperTest {
return listOf(nodeCaCert, intermediateCa.certificate, rootCa.certificate) return listOf(nodeCaCert, intermediateCa.certificate, rootCa.certificate)
} }
private fun createServiceIdentityCertPath(type: CertificateType = CertificateType.SERVICE_IDENTITY,
legalName: CordaX500Name = nodeLegalName): List<X509Certificate> {
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<X509Certificate>): NetworkRegistrationHelper { private fun createRegistrationHelper(response: List<X509Certificate>): NetworkRegistrationHelper {
val certService = rigorousMock<NetworkRegistrationService>().also { val certService = rigorousMock<NetworkRegistrationService>().also {
doReturn(requestId).whenever(it).submitRequest(any()) doReturn(requestId).whenever(it).submitRequest(any())
doReturn(CertificateResponse(5.seconds, response)).whenever(it).retrieveCertificates(eq(requestId)) 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) { private fun saveNetworkTrustStore(rootCert: X509Certificate) {

View File

@ -23,6 +23,7 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.millis import net.corda.core.utilities.millis
import net.corda.node.NodeRegistrationOption
import net.corda.node.internal.Node import net.corda.node.internal.Node
import net.corda.node.internal.NodeStartup import net.corda.node.internal.NodeStartup
import net.corda.node.internal.StartedNode import net.corda.node.internal.StartedNode
@ -265,7 +266,7 @@ class DriverDSLImpl(
return if (startNodesInProcess) { return if (startNodesInProcess) {
executorService.fork { executorService.fork {
NetworkRegistrationHelper(config.corda, HTTPNetworkRegistrationService(compatibilityZoneURL), rootTruststorePath, rootTruststorePassword).buildKeystore() NetworkRegistrationHelper(config.corda, HTTPNetworkRegistrationService(compatibilityZoneURL), NodeRegistrationOption(rootTruststorePath, rootTruststorePassword)).buildKeystore()
config config
} }
} else { } else {