mirror of
https://github.com/corda/corda.git
synced 2025-01-18 10:46:38 +00:00
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:
parent
8eb976d035
commit
5ed60ab60d
@ -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,
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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}.")
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user