[CORDA-2866] Prevent node startup if legal identity key is lost but node key isn't (#5090)

This commit is contained in:
JamesHR3 2019-05-07 11:49:29 +01:00 committed by Shams Asari
parent 6a10b4cff6
commit c533792f3f
8 changed files with 68 additions and 26 deletions

View File

@ -38,7 +38,7 @@ object DevIdentityGenerator {
val p2pSslConfig = SslConfiguration.mutual(p2pKeyStore, p2pTrustStore)
certificatesDirectory.createDirectories()
val nodeKeyStore = signingCertStore.get(true).also { it.registerDevSigningCertificates(legalName) }
val nodeKeyStore = signingCertStore.get(true).also { it.installDevNodeCaCertPath(legalName) }
p2pSslConfig.keyStore.get(true).also { it.registerDevP2pCertificates(legalName) }
val identity = nodeKeyStore.storeLegalIdentity("$NODE_IDENTITY_ALIAS_PREFIX-private-key")

View File

@ -26,14 +26,14 @@ import javax.security.auth.x500.X500Principal
* the given legal name), and the SSL key store will store the TLS cert which is a sub-cert of the node CA.
*/
fun CertificateStore.registerDevSigningCertificates(legalName: CordaX500Name,
rootCert: X509Certificate = DEV_ROOT_CA.certificate,
intermediateCa: CertificateAndKeyPair = DEV_INTERMEDIATE_CA,
devNodeCa: CertificateAndKeyPair = createDevNodeCa(intermediateCa, legalName)) {
fun CertificateStore.installDevNodeCaCertPath(legalName: CordaX500Name,
rootCert: X509Certificate = DEV_ROOT_CA.certificate,
intermediateCa: CertificateAndKeyPair = DEV_INTERMEDIATE_CA,
devNodeCa: CertificateAndKeyPair = createDevNodeCa(intermediateCa, legalName)) {
update {
setPrivateKey(X509Utilities.CORDA_CLIENT_CA, devNodeCa.keyPair.private, listOf(devNodeCa.certificate, intermediateCa.certificate, rootCert),
this@registerDevSigningCertificates.entryPassword)
this@installDevNodeCaCertPath.entryPassword)
}
}

View File

@ -1,6 +1,6 @@
package net.corda.nodeapi.internal.crypto
import net.corda.core.crypto.*
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.Crypto.COMPOSITE_KEY
import net.corda.core.crypto.Crypto.ECDSA_SECP256K1_SHA256
import net.corda.core.crypto.Crypto.ECDSA_SECP256R1_SHA256
@ -8,6 +8,8 @@ import net.corda.core.crypto.Crypto.EDDSA_ED25519_SHA512
import net.corda.core.crypto.Crypto.RSA_SHA256
import net.corda.core.crypto.Crypto.SPHINCS256_SHA256
import net.corda.core.crypto.Crypto.generateKeyPair
import net.corda.core.crypto.SignatureScheme
import net.corda.core.crypto.newSecureRandom
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.div
import net.corda.core.serialization.SerializationContext
@ -18,9 +20,9 @@ import net.corda.nodeapi.internal.config.MutualSslConfiguration
import net.corda.nodeapi.internal.createDevNodeCa
import net.corda.nodeapi.internal.crypto.X509Utilities.DEFAULT_IDENTITY_SIGNATURE_SCHEME
import net.corda.nodeapi.internal.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME
import net.corda.nodeapi.internal.installDevNodeCaCertPath
import net.corda.nodeapi.internal.protonwrapper.netty.init
import net.corda.nodeapi.internal.registerDevP2pCertificates
import net.corda.nodeapi.internal.registerDevSigningCertificates
import net.corda.serialization.internal.AllWhitelist
import net.corda.serialization.internal.SerializationContextImpl
import net.corda.serialization.internal.SerializationFactoryImpl
@ -28,8 +30,8 @@ import net.corda.serialization.internal.amqp.amqpMagic
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.stubs.CertificateStoreStubs
import net.corda.testing.internal.createDevIntermediateCaCertPath
import net.corda.testing.internal.stubs.CertificateStoreStubs
import net.i2p.crypto.eddsa.EdDSAPrivateKey
import org.assertj.core.api.Assertions.assertThat
import org.bouncycastle.asn1.x509.*
@ -232,7 +234,7 @@ class X509UtilitiesTest {
// Generate server cert and private key and populate another keystore suitable for SSL
val nodeCa = createDevNodeCa(intermediateCa, MEGA_CORP.name)
signingCertStore.get(createNew = true).also { it.registerDevSigningCertificates(MEGA_CORP.name, rootCa.certificate, intermediateCa, nodeCa) }
signingCertStore.get(createNew = true).also { it.installDevNodeCaCertPath(MEGA_CORP.name, rootCa.certificate, intermediateCa, nodeCa) }
p2pSslConfig.keyStore.get(createNew = true).also { it.registerDevP2pCertificates(MEGA_CORP.name, rootCa.certificate, intermediateCa, nodeCa) }
// Load back server certificate
val certStore = signingCertStore.get()

View File

@ -5,27 +5,31 @@ import net.corda.client.rpc.CordaRPCClient
import net.corda.core.CordaRuntimeException
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StartableByRPC
import net.corda.core.internal.div
import net.corda.core.internal.isRegularFile
import net.corda.core.internal.list
import net.corda.core.internal.readLines
import net.corda.core.internal.*
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.NodeStartup
import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.nodeapi.exceptions.InternalNodeException
import net.corda.nodeapi.internal.crypto.X509Utilities.NODE_IDENTITY_ALIAS_PREFIX
import net.corda.nodeapi.internal.installDevNodeCaCertPath
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.NodeParameters
import net.corda.testing.driver.driver
import net.corda.testing.internal.stubs.CertificateStoreStubs
import net.corda.testing.node.User
import net.corda.testing.node.internal.startNode
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test
import java.io.*
import java.io.ByteArrayOutputStream
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.io.Serializable
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class BootTests {
@Test
@ -64,6 +68,29 @@ class BootTests {
assertEquals(1, numberOfNodesThatLogged)
}
}
@Test
fun `node fails to start if legal identity is lost`() {
driver(DriverParameters(notarySpecs = emptyList(), inMemoryDB = false, startNodesInProcess = false)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val aliceCertDir = alice.baseDirectory / "certificates"
(aliceCertDir / "nodekeystore.jks").delete()
val cert = CertificateStoreStubs.Signing.withCertificatesDirectory(aliceCertDir).get(true)
// Creating a new certificate store does not populate that store with the node certificate path. If the node certificate path is
// missing, the node will fail to start but not because the legal identity is missing. To test that a missing legal identity
// prevents the node from starting, the node certificate path must be installed.
cert.installDevNodeCaCertPath(ALICE_NAME)
alice.stop()
// The node shouldn't start, and the logs should indicate that the failure is due to a missing identity key
assertThatThrownBy {
startNode(providedName = ALICE_NAME).getOrThrow()
}
val logFolder = alice.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME
val logFile = logFolder.list { it.filter { a -> a.isRegularFile() && a.fileName.toString().startsWith("node") }.findFirst().get() }
val lines = logFile.readLines { lines -> lines.filter { "$NODE_IDENTITY_ALIAS_PREFIX-private-key" in it }.toArray() }
assertTrue(lines.count() > 0)
}
}
}
@StartableByRPC

View File

@ -17,13 +17,13 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX
import net.corda.nodeapi.internal.ArtemisTcpTransport
import net.corda.nodeapi.internal.config.MutualSslConfiguration
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.installDevNodeCaCertPath
import net.corda.nodeapi.internal.protonwrapper.messages.MessageStatus
import net.corda.nodeapi.internal.protonwrapper.netty.AMQPClient
import net.corda.nodeapi.internal.protonwrapper.netty.AMQPConfiguration
import net.corda.nodeapi.internal.protonwrapper.netty.AMQPServer
import net.corda.nodeapi.internal.protonwrapper.netty.init
import net.corda.nodeapi.internal.registerDevP2pCertificates
import net.corda.nodeapi.internal.registerDevSigningCertificates
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.CHARLIE_NAME
@ -133,7 +133,7 @@ class ProtonWrapperTests {
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
// Generate server cert and private key and populate another keystore suitable for SSL
signingCertificateStore.get(true).also { it.registerDevSigningCertificates(ALICE_NAME, rootCa.certificate, intermediateCa) }
signingCertificateStore.get(true).also { it.installDevNodeCaCertPath(ALICE_NAME, rootCa.certificate, intermediateCa) }
sslConfig.keyStore.get(true).also { it.registerDevP2pCertificates(ALICE_NAME, rootCa.certificate, intermediateCa) }
sslConfig.createTrustStore(rootCa.certificate)

View File

@ -84,7 +84,7 @@ class HardRestartTest {
Thread.sleep(ms.toLong())
(b as OutOfProcess).process.destroyForcibly()
b.stop()
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:${b.rpcAddress.port}"))
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser))
}
CordaRPCClient(a.rpcAddress).use(demoUser.username, demoUser.password) {
val returnValue = it.proxy.startFlow(::Ping, b.nodeInfo.singleIdentity(), 1).returnValue
@ -122,7 +122,7 @@ class HardRestartTest {
Thread.sleep(ms.toLong())
(b as OutOfProcess).process.destroyForcibly()
b.stop()
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:${b.rpcAddress.port}"))
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser))
}
CordaRPCClient(a.rpcAddress).use(demoUser.username, demoUser.password) {
val returnValue = it.proxy.startFlow(::Ping, b.nodeInfo.singleIdentity(), 100).returnValue
@ -159,7 +159,7 @@ class HardRestartTest {
println("Sleeping $ms ms before kill")
Thread.sleep(ms.toLong())
b.stop()
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:${b.rpcAddress.port}"))
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser))
}
CordaRPCClient(a.rpcAddress).use(demoUser.username, demoUser.password) {
val returnValue = it.proxy.startFlow(::Ping, b.nodeInfo.singleIdentity(), 100).returnValue
@ -242,7 +242,7 @@ class HardRestartTest {
Thread.sleep(ms.toLong())
(b as OutOfProcess).process.destroyForcibly()
b.stop()
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:${b.rpcAddress.port}"))
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser))
}
val executor = Executors.newFixedThreadPool(8)
try {

View File

@ -55,7 +55,6 @@ import net.corda.node.services.events.ScheduledActivityObserver
import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.keys.BasicHSMKeyManagementService
import net.corda.node.services.keys.KeyManagementServiceInternal
import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService
import net.corda.node.services.messaging.DeduplicationHandler
import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.network.NetworkMapClient
@ -82,6 +81,7 @@ import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.DEFAULT_VALIDITY_WINDOW
import net.corda.nodeapi.internal.crypto.X509Utilities.DISTRIBUTED_NOTARY_ALIAS_PREFIX
import net.corda.nodeapi.internal.crypto.X509Utilities.NODE_IDENTITY_ALIAS_PREFIX
import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService
import net.corda.nodeapi.internal.persistence.*
import net.corda.tools.shell.InteractiveShell
import org.apache.activemq.artemis.utils.ReusableLatch
@ -286,8 +286,8 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
check(started == null) { "Node has already been started" }
log.info("Generating nodeInfo ...")
val trustRoot = initKeyStores()
val (identity, identityKeyPair) = obtainIdentity()
startDatabase()
val (identity, identityKeyPair) = obtainIdentity()
val nodeCa = configuration.signingCertificateStore.get()[CORDA_CLIENT_CA]
identityService.start(trustRoot, listOf(identity.certificate, nodeCa))
return database.use {
@ -838,6 +838,16 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
var signingCertificateStore = configuration.signingCertificateStore.get()
if (!cryptoService.containsKey(legalIdentityPrivateKeyAlias) && !signingCertificateStore.contains(legalIdentityPrivateKeyAlias)) {
// Directly use the X500 name to public key map, as the identity service requires the node identity to start correctly.
database.transaction {
val x500Map = PersistentIdentityService.createX500Map(cacheFactory)
require(configuration.myLegalName !in x500Map) {
// There is already a party in the identity store for this node, but the key has been lost. If this node starts up, it will
// publish it's new key to the network map, which Corda cannot currently handle. To prevent this, stop the node from starting.
"Private key for the node legal identity not found (alias $legalIdentityPrivateKeyAlias) but the corresponding public key" +
" for it exists in the database. This suggests the identity for this node has been lost. Shutting down to prevent network map issues."
}
}
log.info("$legalIdentityPrivateKeyAlias not found in key store, generating fresh key!")
createAndStoreLegalIdentity(legalIdentityPrivateKeyAlias)
signingCertificateStore = configuration.signingCertificateStore.get() // We need to resync after [createAndStoreLegalIdentity].

View File

@ -8,13 +8,16 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.internal.exists
import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService
import net.corda.nodeapi.internal.*
import net.corda.nodeapi.internal.DEV_CA_KEY_STORE_PASS
import net.corda.nodeapi.internal.config.FileBasedCertificateStoreSupplier
import net.corda.nodeapi.internal.config.MutualSslConfiguration
import net.corda.nodeapi.internal.config.toProperties
import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.cryptoservice.CryptoService
import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService
import net.corda.nodeapi.internal.installDevNodeCaCertPath
import net.corda.nodeapi.internal.loadDevCaTrustStore
import net.corda.nodeapi.internal.registerDevP2pCertificates
import org.slf4j.LoggerFactory
import java.nio.file.Path
@ -92,7 +95,7 @@ fun MutualSslConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500N
when (cryptoService) {
is BCCryptoService, null -> {
val signingKeyStore = FileBasedCertificateStoreSupplier(signingCertificateStore.path, signingCertificateStore.storePassword, signingCertificateStore.entryPassword).get(true)
.also { it.registerDevSigningCertificates(myLegalName) }
.also { it.installDevNodeCaCertPath(myLegalName) }
// Move distributed service composite key (generated by IdentityGenerator.generateToDisk) to keystore if exists.
val distributedServiceKeystore = certificatesDirectory / "distributedService.jks"