Bug fix: registration tool doesn't register for service identity if keystore already contains node cert (#756)

* fix a bug where registration tool will refuse to register for a service identity if the keystore contain NODE_CA key already.
some refactoring
This commit is contained in:
Patrick Kuo 2018-05-02 09:54:28 +01:00 committed by GitHub
parent 8eb976d035
commit 5ed60ab60d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 130 additions and 94 deletions

View File

@ -18,12 +18,15 @@ import net.corda.core.internal.CertRole
import net.corda.core.internal.div
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
import net.corda.node.utilities.registration.NetworkRegistrationHelper
import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.config.parseAs
import java.net.URL
import java.nio.file.Path
import java.nio.file.Paths
const val NOTARY_PRIVATE_KEY_ALIAS = "${DevIdentityGenerator.DISTRIBUTED_NOTARY_ALIAS_PREFIX}-private-key"
fun RegistrationOption.runRegistration() {
println("**********************************************************")
println("* *")
@ -51,7 +54,9 @@ fun RegistrationOption.runRegistration() {
config.email,
HTTPNetworkRegistrationService(config.compatibilityZoneURL),
config.networkRootTrustStorePath,
config.networkRootTrustStorePassword ?: readPassword("Network trust root password:"), CertRole.SERVICE_IDENTITY).buildKeystore()
config.networkRootTrustStorePassword ?: readPassword("Network trust root password:"),
NOTARY_PRIVATE_KEY_ALIAS,
CertRole.SERVICE_IDENTITY).buildKeystore()
}
data class NotaryRegistrationConfig(val legalName: CordaX500Name,

View File

@ -33,7 +33,7 @@ import net.corda.core.utilities.seconds
import net.corda.node.NodeRegistrationOption
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
import net.corda.node.utilities.registration.NetworkRegistrationHelper
import net.corda.node.utilities.registration.NodeRegistrationHelper
import net.corda.nodeapi.internal.createDevNodeCa
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.nodeapi.internal.crypto.X509KeyStore
@ -163,7 +163,7 @@ class SigningServiceIntegrationTest : HsmBaseTest() {
doReturn(sslKeyStore).whenever(it).loadSslKeyStore(any())
}
val regConfig = NodeRegistrationOption(networkTrustStorePath, networkTrustStorePassword)
NetworkRegistrationHelper(config, HTTPNetworkRegistrationService(config.compatibilityZoneURL!!), regConfig).buildKeystore()
NodeRegistrationHelper(config, HTTPNetworkRegistrationService(config.compatibilityZoneURL!!), regConfig).buildKeystore()
verify(hsmSigner).sign(any())
}
}

View File

@ -25,7 +25,7 @@ import net.corda.node.services.config.shouldStartLocalShell
import net.corda.node.services.config.shouldStartSSHDaemon
import net.corda.node.services.transactions.bftSMaRtSerialFilter
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
import net.corda.node.utilities.registration.NetworkRegistrationHelper
import net.corda.node.utilities.registration.NodeRegistrationHelper
import net.corda.nodeapi.internal.addShutdownHook
import net.corda.nodeapi.internal.config.UnknownConfigurationKeysException
import net.corda.tools.shell.InteractiveShell
@ -211,7 +211,7 @@ open class NodeStartup(val args: Array<String>) {
println("* Registering as a new participant with Corda network *")
println("* *")
println("******************************************************************")
NetworkRegistrationHelper(conf, HTTPNetworkRegistrationService(compatibilityZoneURL), nodeRegistrationConfig).buildKeystore()
NodeRegistrationHelper(conf, HTTPNetworkRegistrationService(compatibilityZoneURL), nodeRegistrationConfig).buildKeystore()
}
open protected fun loadConfigFile(cmdlineOptions: CmdLineOptions): NodeConfiguration = cmdlineOptions.loadConfig()

View File

@ -15,7 +15,6 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.*
import net.corda.node.NodeRegistrationOption
import net.corda.node.services.config.NodeConfiguration
import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509KeyStore
@ -35,19 +34,17 @@ import java.security.cert.X509Certificate
* Helper for managing the node registration process, which checks for any existing certificates and requests them if
* needed.
*/
class NetworkRegistrationHelper(private val config: SSLConfiguration,
private val myLegalName: CordaX500Name,
private val emailAddress: String,
private val certService: NetworkRegistrationService,
private val networkRootTrustStorePath: Path,
networkRootTrustStorePassword: String,
private val certRole: CertRole) {
// TODO: Use content signer instead of keypairs.
open class NetworkRegistrationHelper(private val config: SSLConfiguration,
private val myLegalName: CordaX500Name,
private val emailAddress: String,
private val certService: NetworkRegistrationService,
private val networkRootTrustStorePath: Path,
networkRootTrustStorePassword: String,
private val keyAlias: 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 {
companion object {
const val SELF_SIGNED_PRIVATE_KEY = "Self Signed Private Key"
}
@ -80,22 +77,13 @@ class NetworkRegistrationHelper(private val config: SSLConfiguration,
fun buildKeystore() {
config.certificatesDirectory.createDirectories()
val nodeKeyStore = config.loadNodeKeyStore(createNew = true)
if (CORDA_CLIENT_CA in nodeKeyStore) {
if (keyAlias in nodeKeyStore) {
println("Certificate already exists, Corda node will now terminate...")
return
}
// Create or load self signed keypair from the key store.
// We use the self sign certificate to store the key temporarily in the keystore while waiting for the request approval.
if (SELF_SIGNED_PRIVATE_KEY !in nodeKeyStore) {
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val selfSignCert = X509Utilities.createSelfSignedCACertificate(myLegalName.x500Principal, keyPair)
// Save to the key store.
nodeKeyStore.setPrivateKey(SELF_SIGNED_PRIVATE_KEY, keyPair.private, listOf(selfSignCert), keyPassword = privateKeyPassword)
nodeKeyStore.save()
}
val keyPair = nodeKeyStore.loadOrCreateKeyPair(SELF_SIGNED_PRIVATE_KEY)
val keyPair = nodeKeyStore.getCertificateAndKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword).keyPair
val requestId = submitOrResumeCertificateSigningRequest(keyPair)
val certificates = try {
@ -107,11 +95,18 @@ class NetworkRegistrationHelper(private val config: SSLConfiguration,
requestIdStore.deleteIfExists()
throw certificateRequestException
}
validateCertificates(certificates)
storePrivateKeyWithCertificates(nodeKeyStore, keyPair, certificates, keyAlias)
onSuccess(keyPair, certificates)
// All done, clean up temp files.
requestIdStore.deleteIfExists()
}
val certificate = certificates.first()
private fun validateCertificates(certificates: List<X509Certificate>) {
val nodeCACertificate = certificates.first()
val nodeCaSubject = try {
CordaX500Name.build(certificate.subjectX500Principal)
CordaX500Name.build(nodeCACertificate.subjectX500Principal)
} catch (e: IllegalArgumentException) {
throw CertificateRequestException("Received node CA cert has invalid subject name: ${e.message}")
}
@ -120,56 +115,39 @@ class NetworkRegistrationHelper(private val config: SSLConfiguration,
}
val nodeCaCertRole = try {
CertRole.extract(certificate)
CertRole.extract(nodeCACertificate)
} catch (e: IllegalArgumentException) {
throw CertificateRequestException("Unable to extract cert role from received node CA cert: ${e.message}")
}
if (certRole != nodeCaCertRole) {
throw CertificateRequestException("Received certificate contains invalid cert role, expected '$certRole', got '$nodeCaCertRole'.")
}
// Validate certificate chain returned from the doorman with the root cert obtained via out-of-band process, to prevent MITM attack on doorman server.
X509Utilities.validateCertificateChain(rootCert, certificates)
println("Certificate signing request approved, storing private key with the certificate chain.")
}
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}.")
private fun storePrivateKeyWithCertificates(nodeKeystore: X509KeyStore, keyPair: KeyPair, certificates: List<X509Certificate>, keyAlias: String) {
// Save private key and certificate chain to the key store.
nodeKeystore.setPrivateKey(keyAlias, keyPair.private, certificates, keyPassword = config.keyStorePassword)
nodeKeystore.internal.deleteEntry(SELF_SIGNED_PRIVATE_KEY)
nodeKeystore.save()
println("Private key '$keyAlias' 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")
private fun X509KeyStore.loadOrCreateKeyPair(alias: String): KeyPair {
// Create or load self signed keypair from the key store.
// We use the self sign certificate to store the key temporarily in the keystore while waiting for the request approval.
if (alias !in this) {
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val selfSignCert = X509Utilities.createSelfSignedCACertificate(myLegalName.x500Principal, keyPair)
// Save to the key store.
setPrivateKey(alias, keyPair.private, listOf(selfSignCert), keyPassword = privateKeyPassword)
save()
}
// Save root certificates to trust store.
config.loadTrustStore(createNew = true).update {
println("Generating trust store for corda node.")
// Assumes certificate chain always starts with client certificate and end with root certificate.
setCertificate(CORDA_ROOT_CA, certificates.last())
}
println("Node trust store stored in ${config.trustStoreFile}.")
// All done, clean up temp files.
requestIdStore.deleteIfExists()
return getCertificateAndKeyPair(alias, privateKeyPassword).keyPair
}
/**
@ -225,4 +203,47 @@ class NetworkRegistrationHelper(private val config: SSLConfiguration,
requestId
}
}
protected open fun onSuccess(nodeCAKeyPair: KeyPair, certificates: List<X509Certificate>) {}
}
class NodeRegistrationHelper(private val config: NodeConfiguration, certService: NetworkRegistrationService, regConfig: NodeRegistrationOption) :
NetworkRegistrationHelper(config,
config.myLegalName,
config.emailAddress,
certService,
regConfig.networkRootTrustStorePath,
regConfig.networkRootTrustStorePassword,
CORDA_CLIENT_CA,
CertRole.NODE_CA) {
override fun onSuccess(nodeCAKeyPair: KeyPair, certificates: List<X509Certificate>) {
createSSLKeystore(nodeCAKeyPair, certificates)
createTruststore(certificates.last())
}
private fun createSSLKeystore(nodeCAKeyPair: KeyPair, certificates: List<X509Certificate>) {
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,
certificates.first(),
nodeCAKeyPair,
config.myLegalName.x500Principal,
sslKeyPair.public)
setPrivateKey(CORDA_CLIENT_TLS, sslKeyPair.private, listOf(sslCert) + certificates)
}
println("SSL private key and certificate stored in ${config.sslKeystore}.")
}
private fun createTruststore(rootCertificate: X509Certificate) {
// Save root certificates to trust store.
config.loadTrustStore(createNew = true).update {
println("Generating trust store for corda node.")
// Assumes certificate chain always starts with client certificate and end with root certificate.
setCertificate(CORDA_ROOT_CA, rootCertificate)
}
println("Node trust store stored in ${config.trustStoreFile}.")
}
}

View File

@ -19,6 +19,7 @@ import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.CertRole
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.internal.x500Name
@ -162,11 +163,12 @@ class NetworkRegistrationHelperTest {
val serviceIdentityCertPath = createServiceIdentityCertPath()
saveNetworkTrustStore(serviceIdentityCertPath.last())
createRegistrationHelper(serviceIdentityCertPath).buildKeystore()
createRegistrationHelper(serviceIdentityCertPath, CertRole.SERVICE_IDENTITY).buildKeystore()
val nodeKeystore = config.loadNodeKeyStore()
val trustStore = config.loadTrustStore()
assertThat(config.sslKeystore).doesNotExist()
assertThat(config.trustStoreFile).doesNotExist()
val serviceIdentityAlias = "${DevIdentityGenerator.DISTRIBUTED_NOTARY_ALIAS_PREFIX}-private-key"
@ -177,12 +179,6 @@ class NetworkRegistrationHelperTest {
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,
@ -213,12 +209,25 @@ class NetworkRegistrationHelperTest {
return listOf(serviceIdentityCert, intermediateCa.certificate, rootCa.certificate)
}
private fun createRegistrationHelper(response: List<X509Certificate>): NetworkRegistrationHelper {
private fun createRegistrationHelper(response: List<X509Certificate>, certRole: CertRole = CertRole.NODE_CA): NetworkRegistrationHelper {
val certService = rigorousMock<NetworkRegistrationService>().also {
doReturn(requestId).whenever(it).submitRequest(any())
doReturn(CertificateResponse(5.seconds, response)).whenever(it).retrieveCertificates(eq(requestId))
}
return NetworkRegistrationHelper(config, certService, NodeRegistrationOption(config.certificatesDirectory / networkRootTrustStoreFileName, networkRootTrustStorePassword))
return when (certRole) {
CertRole.NODE_CA -> NodeRegistrationHelper(config, certService, NodeRegistrationOption(config.certificatesDirectory / networkRootTrustStoreFileName, networkRootTrustStorePassword))
CertRole.SERVICE_IDENTITY -> NetworkRegistrationHelper(
config,
config.myLegalName,
config.emailAddress,
certService,
config.certificatesDirectory / networkRootTrustStoreFileName,
networkRootTrustStorePassword,
"${DevIdentityGenerator.DISTRIBUTED_NOTARY_ALIAS_PREFIX}-private-key",
CertRole.SERVICE_IDENTITY)
else -> throw IllegalArgumentException("Unsupported cert role.")
}
}
private fun saveNetworkTrustStore(rootCert: X509Certificate) {

View File

@ -39,7 +39,7 @@ import net.corda.node.internal.StartedNode
import net.corda.node.services.Permissions
import net.corda.node.services.config.*
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
import net.corda.node.utilities.registration.NetworkRegistrationHelper
import net.corda.node.utilities.registration.NodeRegistrationHelper
import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.addShutdownHook
@ -257,7 +257,7 @@ class DriverDSLImpl(
return if (startNodesInProcess) {
executorService.fork {
NetworkRegistrationHelper(
NodeRegistrationHelper(
config.corda,
HTTPNetworkRegistrationService(compatibilityZoneURL),
NodeRegistrationOption(rootTruststorePath, rootTruststorePassword)
@ -855,8 +855,7 @@ class DriverDSLImpl(
val index = stackTrace.indexOfLast { it.className == "net.corda.testing.driver.Driver" }
// In this case we're dealing with the the RPCDriver or one of it's cousins which are internal and we don't care about them
if (index == -1) return emptyList()
val callerPackage = Class.forName(stackTrace[index + 1].className).`package` ?:
throw IllegalStateException("Function instantiating driver must be defined in a package.")
val callerPackage = Class.forName(stackTrace[index + 1].className).`package` ?: throw IllegalStateException("Function instantiating driver must be defined in a package.")
return listOf(callerPackage.name)
}
@ -904,16 +903,18 @@ private class NetworkVisibilityController {
val (snapshot, updates) = rpc.networkMapFeed()
visibleNodeCount = snapshot.size
checkIfAllVisible()
subscription = updates.subscribe { when (it) {
is NetworkMapCache.MapChange.Added -> {
visibleNodeCount++
checkIfAllVisible()
subscription = updates.subscribe {
when (it) {
is NetworkMapCache.MapChange.Added -> {
visibleNodeCount++
checkIfAllVisible()
}
is NetworkMapCache.MapChange.Removed -> {
visibleNodeCount--
checkIfAllVisible()
}
}
is NetworkMapCache.MapChange.Removed -> {
visibleNodeCount--
checkIfAllVisible()
}
} }
}
return future
}