CORDA-4062: Bump platform version to 9 for safe identity key rotation (#6777)

This commit is contained in:
Denis Rekalov 2020-10-20 12:31:47 +03:00 committed by GitHub
parent 4193adf6fd
commit c9056f171b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 44 additions and 7 deletions

View File

@ -11,7 +11,7 @@ java8MinUpdateVersion=171
# When incrementing platformVersion make sure to update #
# net.corda.core.internal.CordaUtilsKt.PLATFORM_VERSION as well. #
# ***************************************************************#
platformVersion=8
platformVersion=9
guavaVersion=28.0-jre
# Quasar version to use with Java 8:
quasarVersion=0.7.13_r3

View File

@ -28,7 +28,7 @@ import java.util.jar.JarInputStream
// *Internal* Corda-specific utilities.
const val PLATFORM_VERSION = 8
const val PLATFORM_VERSION = 9
fun ServicesForResolution.ensureMinimumPlatformVersion(requiredMinPlatformVersion: Int, feature: String) {
checkMinimumPlatformVersion(networkParameters.minimumPlatformVersion, requiredMinPlatformVersion, feature)

View File

@ -16,4 +16,5 @@ object PlatformVersionSwitches {
const val LIMIT_KEYS_IN_SIGNATURE_CONSTRAINTS = 5
const val BATCH_DOWNLOAD_COUNTERPARTY_BACKCHAIN = 6
const val ENABLE_P2P_COMPRESSION = 7
const val CERTIFICATE_ROTATION = 9
}

View File

@ -1,5 +1,6 @@
package net.corda.node.services.identity
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.internal.div
import net.corda.core.utilities.OpaqueBytes
import net.corda.coretesting.internal.stubs.CertificateStoreStubs
@ -14,6 +15,7 @@ import net.corda.node.services.keys.KeyManagementServiceInternal
import net.corda.nodeapi.internal.DEV_CA_KEY_STORE_PASS
import net.corda.nodeapi.internal.crypto.X509Utilities.NODE_IDENTITY_KEY_ALIAS
import net.corda.nodeapi.internal.storeLegalIdentity
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.CHARLIE_NAME
@ -21,6 +23,7 @@ import net.corda.testing.node.internal.FINANCE_CORDAPPS
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.TestStartedNode
import net.corda.testing.node.internal.startFlow
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.After
import org.junit.Test
import java.nio.file.Path
@ -78,7 +81,10 @@ class CertificateRotationTest {
@Test(timeout = 300_000)
fun `restart with rotated key for one node`() {
mockNet = InternalMockNetwork(cordappsForAllNodes = FINANCE_CORDAPPS)
mockNet = InternalMockNetwork(
cordappsForAllNodes = FINANCE_CORDAPPS,
initialNetworkParameters = testNetworkParameters(minimumPlatformVersion = PLATFORM_VERSION)
)
val alice = mockNet.createPartyNode(ALICE_NAME)
val bob = mockNet.createPartyNode(BOB_NAME)
@ -111,9 +117,24 @@ class CertificateRotationTest {
assertEquals(1300.POUNDS, bob2.services.getCashBalance(GBP))
}
@Test(timeout = 300_000)
fun `fail to restart with rotated key and wrong minimum platform version`() {
mockNet = InternalMockNetwork(
cordappsForAllNodes = FINANCE_CORDAPPS,
initialNetworkParameters = testNetworkParameters(minimumPlatformVersion = 8)
)
val alice = mockNet.createPartyNode(ALICE_NAME)
assertThatThrownBy {
mockNet.restartNodeWithRotateIdentityKey(alice)
}.hasMessageContaining("Failed to change node legal identity key")
}
@Test(timeout = 300_000)
fun `backchain resolution with rotated issuer key`() {
mockNet = InternalMockNetwork(cordappsForAllNodes = FINANCE_CORDAPPS)
mockNet = InternalMockNetwork(
cordappsForAllNodes = FINANCE_CORDAPPS,
initialNetworkParameters = testNetworkParameters(minimumPlatformVersion = PLATFORM_VERSION)
)
val alice = mockNet.createPartyNode(ALICE_NAME)
val bob = mockNet.createPartyNode(BOB_NAME)

View File

@ -14,6 +14,7 @@ import net.corda.core.context.InvocationContext
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.newSecureRandom
import net.corda.core.crypto.toStringShort
import net.corda.core.flows.ContractUpgradeFlow
import net.corda.core.flows.FinalityFlow
import net.corda.core.flows.FlowLogic
@ -31,6 +32,7 @@ import net.corda.core.internal.FlowStateMachineHandle
import net.corda.core.internal.NODE_INFO_DIRECTORY
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.internal.NetworkParametersStorage
import net.corda.core.internal.PlatformVersionSwitches
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.concurrent.flatMap
import net.corda.core.internal.concurrent.map
@ -568,7 +570,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
identityService.start(trustRoots, keyStoreHandler.nodeIdentity, netParams.notaries.map { it.identity }, pkToIdCache)
val nodeInfoAndSigned = database.transaction {
updateNodeInfo(publish = true)
updateNodeInfo(publish = true, minimumPlatformVersion = netParams.minimumPlatformVersion)
}
val (nodeInfo, signedNodeInfo) = nodeInfoAndSigned
@ -693,7 +695,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
}
}
private fun updateNodeInfo(publish: Boolean): NodeInfoAndSigned {
private fun updateNodeInfo(publish: Boolean, minimumPlatformVersion: Int = Int.MAX_VALUE): NodeInfoAndSigned {
val potentialNodeInfo = NodeInfo(
myAddresses(),
setOf(keyStoreHandler.nodeIdentity, keyStoreHandler.notaryIdentity).filterNotNull(),
@ -709,6 +711,9 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
nodeInfoFromDb
} else {
log.info("Node-info has changed so submitting update. Old node-info was $nodeInfoFromDb")
if (minimumPlatformVersion < PlatformVersionSwitches.CERTIFICATE_ROTATION && nodeInfoFromDb != null) {
requireSameNodeIdentity(nodeInfoFromDb, potentialNodeInfo)
}
val newNodeInfo = potentialNodeInfo.copy(serial = platformClock.millis())
networkMapCache.addOrUpdateNode(newNodeInfo)
log.info("New node-info: $newNodeInfo")
@ -745,6 +750,16 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
}
}
private fun requireSameNodeIdentity(oldNodeInfo: NodeInfo, newNodeInfo: NodeInfo) {
val oldIdentity = oldNodeInfo.legalIdentities.first()
val newIdentity = newNodeInfo.legalIdentities.first()
require(oldIdentity == newIdentity || oldIdentity.name != newIdentity.name) {
"Failed to change node legal identity key from ${oldIdentity.owningKey.toStringShort()}"+
" to ${newIdentity.owningKey.toStringShort()}," +
" as it requires minimumPlatformVersion >= ${PlatformVersionSwitches.CERTIFICATE_ROTATION}."
}
}
// Publish node info on startup and start task that sends every day a heartbeat - republishes node info.
private fun tryPublishNodeInfoAsync(signedNodeInfo: SignedNodeInfo, networkMapClient: NetworkMapClient) {
// By default heartbeat interval should be set to 1 day, but for testing we may change it.

View File

@ -262,7 +262,7 @@ class FlowMetadataRecordingTest {
it.initialParameters.deserialize(context = SerializationDefaults.STORAGE_CONTEXT)
)
assertThat(it.launchingCordapp).contains("custom-cordapp")
assertEquals(8, it.platformVersion)
assertEquals(PLATFORM_VERSION, it.platformVersion)
assertEquals(nodeAHandle.nodeInfo.singleIdentity().name.toString(), it.startedBy)
assertEquals(context!!.trace.invocationId.timestamp.truncatedTo(ChronoUnit.MILLIS),
it.invocationInstant.truncatedTo(ChronoUnit.MILLIS))