mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
CORDA-4062: Bump platform version to 9 for safe identity key rotation (#6777)
This commit is contained in:
parent
4193adf6fd
commit
c9056f171b
@ -11,7 +11,7 @@ java8MinUpdateVersion=171
|
|||||||
# When incrementing platformVersion make sure to update #
|
# When incrementing platformVersion make sure to update #
|
||||||
# net.corda.core.internal.CordaUtilsKt.PLATFORM_VERSION as well. #
|
# net.corda.core.internal.CordaUtilsKt.PLATFORM_VERSION as well. #
|
||||||
# ***************************************************************#
|
# ***************************************************************#
|
||||||
platformVersion=8
|
platformVersion=9
|
||||||
guavaVersion=28.0-jre
|
guavaVersion=28.0-jre
|
||||||
# Quasar version to use with Java 8:
|
# Quasar version to use with Java 8:
|
||||||
quasarVersion=0.7.13_r3
|
quasarVersion=0.7.13_r3
|
||||||
|
@ -28,7 +28,7 @@ import java.util.jar.JarInputStream
|
|||||||
|
|
||||||
// *Internal* Corda-specific utilities.
|
// *Internal* Corda-specific utilities.
|
||||||
|
|
||||||
const val PLATFORM_VERSION = 8
|
const val PLATFORM_VERSION = 9
|
||||||
|
|
||||||
fun ServicesForResolution.ensureMinimumPlatformVersion(requiredMinPlatformVersion: Int, feature: String) {
|
fun ServicesForResolution.ensureMinimumPlatformVersion(requiredMinPlatformVersion: Int, feature: String) {
|
||||||
checkMinimumPlatformVersion(networkParameters.minimumPlatformVersion, requiredMinPlatformVersion, feature)
|
checkMinimumPlatformVersion(networkParameters.minimumPlatformVersion, requiredMinPlatformVersion, feature)
|
||||||
|
@ -16,4 +16,5 @@ object PlatformVersionSwitches {
|
|||||||
const val LIMIT_KEYS_IN_SIGNATURE_CONSTRAINTS = 5
|
const val LIMIT_KEYS_IN_SIGNATURE_CONSTRAINTS = 5
|
||||||
const val BATCH_DOWNLOAD_COUNTERPARTY_BACKCHAIN = 6
|
const val BATCH_DOWNLOAD_COUNTERPARTY_BACKCHAIN = 6
|
||||||
const val ENABLE_P2P_COMPRESSION = 7
|
const val ENABLE_P2P_COMPRESSION = 7
|
||||||
|
const val CERTIFICATE_ROTATION = 9
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package net.corda.node.services.identity
|
package net.corda.node.services.identity
|
||||||
|
|
||||||
|
import net.corda.core.internal.PLATFORM_VERSION
|
||||||
import net.corda.core.internal.div
|
import net.corda.core.internal.div
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
import net.corda.coretesting.internal.stubs.CertificateStoreStubs
|
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.DEV_CA_KEY_STORE_PASS
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities.NODE_IDENTITY_KEY_ALIAS
|
import net.corda.nodeapi.internal.crypto.X509Utilities.NODE_IDENTITY_KEY_ALIAS
|
||||||
import net.corda.nodeapi.internal.storeLegalIdentity
|
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.ALICE_NAME
|
||||||
import net.corda.testing.core.BOB_NAME
|
import net.corda.testing.core.BOB_NAME
|
||||||
import net.corda.testing.core.CHARLIE_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.InternalMockNetwork
|
||||||
import net.corda.testing.node.internal.TestStartedNode
|
import net.corda.testing.node.internal.TestStartedNode
|
||||||
import net.corda.testing.node.internal.startFlow
|
import net.corda.testing.node.internal.startFlow
|
||||||
|
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
@ -78,7 +81,10 @@ class CertificateRotationTest {
|
|||||||
|
|
||||||
@Test(timeout = 300_000)
|
@Test(timeout = 300_000)
|
||||||
fun `restart with rotated key for one node`() {
|
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 alice = mockNet.createPartyNode(ALICE_NAME)
|
||||||
val bob = mockNet.createPartyNode(BOB_NAME)
|
val bob = mockNet.createPartyNode(BOB_NAME)
|
||||||
|
|
||||||
@ -111,9 +117,24 @@ class CertificateRotationTest {
|
|||||||
assertEquals(1300.POUNDS, bob2.services.getCashBalance(GBP))
|
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)
|
@Test(timeout = 300_000)
|
||||||
fun `backchain resolution with rotated issuer key`() {
|
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 alice = mockNet.createPartyNode(ALICE_NAME)
|
||||||
val bob = mockNet.createPartyNode(BOB_NAME)
|
val bob = mockNet.createPartyNode(BOB_NAME)
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ import net.corda.core.context.InvocationContext
|
|||||||
import net.corda.core.crypto.DigitalSignature
|
import net.corda.core.crypto.DigitalSignature
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.newSecureRandom
|
import net.corda.core.crypto.newSecureRandom
|
||||||
|
import net.corda.core.crypto.toStringShort
|
||||||
import net.corda.core.flows.ContractUpgradeFlow
|
import net.corda.core.flows.ContractUpgradeFlow
|
||||||
import net.corda.core.flows.FinalityFlow
|
import net.corda.core.flows.FinalityFlow
|
||||||
import net.corda.core.flows.FlowLogic
|
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.NODE_INFO_DIRECTORY
|
||||||
import net.corda.core.internal.NamedCacheFactory
|
import net.corda.core.internal.NamedCacheFactory
|
||||||
import net.corda.core.internal.NetworkParametersStorage
|
import net.corda.core.internal.NetworkParametersStorage
|
||||||
|
import net.corda.core.internal.PlatformVersionSwitches
|
||||||
import net.corda.core.internal.VisibleForTesting
|
import net.corda.core.internal.VisibleForTesting
|
||||||
import net.corda.core.internal.concurrent.flatMap
|
import net.corda.core.internal.concurrent.flatMap
|
||||||
import net.corda.core.internal.concurrent.map
|
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)
|
identityService.start(trustRoots, keyStoreHandler.nodeIdentity, netParams.notaries.map { it.identity }, pkToIdCache)
|
||||||
|
|
||||||
val nodeInfoAndSigned = database.transaction {
|
val nodeInfoAndSigned = database.transaction {
|
||||||
updateNodeInfo(publish = true)
|
updateNodeInfo(publish = true, minimumPlatformVersion = netParams.minimumPlatformVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
val (nodeInfo, signedNodeInfo) = nodeInfoAndSigned
|
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(
|
val potentialNodeInfo = NodeInfo(
|
||||||
myAddresses(),
|
myAddresses(),
|
||||||
setOf(keyStoreHandler.nodeIdentity, keyStoreHandler.notaryIdentity).filterNotNull(),
|
setOf(keyStoreHandler.nodeIdentity, keyStoreHandler.notaryIdentity).filterNotNull(),
|
||||||
@ -709,6 +711,9 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
|||||||
nodeInfoFromDb
|
nodeInfoFromDb
|
||||||
} else {
|
} else {
|
||||||
log.info("Node-info has changed so submitting update. Old node-info was $nodeInfoFromDb")
|
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())
|
val newNodeInfo = potentialNodeInfo.copy(serial = platformClock.millis())
|
||||||
networkMapCache.addOrUpdateNode(newNodeInfo)
|
networkMapCache.addOrUpdateNode(newNodeInfo)
|
||||||
log.info("New node-info: $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.
|
// Publish node info on startup and start task that sends every day a heartbeat - republishes node info.
|
||||||
private fun tryPublishNodeInfoAsync(signedNodeInfo: SignedNodeInfo, networkMapClient: NetworkMapClient) {
|
private fun tryPublishNodeInfoAsync(signedNodeInfo: SignedNodeInfo, networkMapClient: NetworkMapClient) {
|
||||||
// By default heartbeat interval should be set to 1 day, but for testing we may change it.
|
// By default heartbeat interval should be set to 1 day, but for testing we may change it.
|
||||||
|
@ -262,7 +262,7 @@ class FlowMetadataRecordingTest {
|
|||||||
it.initialParameters.deserialize(context = SerializationDefaults.STORAGE_CONTEXT)
|
it.initialParameters.deserialize(context = SerializationDefaults.STORAGE_CONTEXT)
|
||||||
)
|
)
|
||||||
assertThat(it.launchingCordapp).contains("custom-cordapp")
|
assertThat(it.launchingCordapp).contains("custom-cordapp")
|
||||||
assertEquals(8, it.platformVersion)
|
assertEquals(PLATFORM_VERSION, it.platformVersion)
|
||||||
assertEquals(nodeAHandle.nodeInfo.singleIdentity().name.toString(), it.startedBy)
|
assertEquals(nodeAHandle.nodeInfo.singleIdentity().name.toString(), it.startedBy)
|
||||||
assertEquals(context!!.trace.invocationId.timestamp.truncatedTo(ChronoUnit.MILLIS),
|
assertEquals(context!!.trace.invocationId.timestamp.truncatedTo(ChronoUnit.MILLIS),
|
||||||
it.invocationInstant.truncatedTo(ChronoUnit.MILLIS))
|
it.invocationInstant.truncatedTo(ChronoUnit.MILLIS))
|
||||||
|
Loading…
Reference in New Issue
Block a user