CORDA-3972: Support for node identity rotation in IdentityService (#6752)

This commit is contained in:
Denis Rekalov 2020-10-12 18:01:32 +03:00 committed by GitHub
parent e2efbaea35
commit cdd725e79c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 779 additions and 276 deletions

View File

@ -4,6 +4,7 @@ import net.corda.core.KeepForDJVM
import net.corda.core.contracts.PartyAndReference import net.corda.core.contracts.PartyAndReference
import net.corda.core.crypto.CompositeKey import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.toStringShort
import net.corda.core.flows.Destination import net.corda.core.flows.Destination
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
@ -43,4 +44,5 @@ class Party(val name: CordaX500Name, owningKey: PublicKey) : Destination, Abstra
fun anonymise(): AnonymousParty = AnonymousParty(owningKey) fun anonymise(): AnonymousParty = AnonymousParty(owningKey)
override fun ref(bytes: OpaqueBytes): PartyAndReference = PartyAndReference(this, bytes) override fun ref(bytes: OpaqueBytes): PartyAndReference = PartyAndReference(this, bytes)
override fun toString() = name.toString() override fun toString() = name.toString()
fun description() = "$name (owningKey = ${owningKey.toStringShort()})"
} }

View File

@ -0,0 +1,197 @@
package net.corda.node.services.identity
import net.corda.core.internal.div
import net.corda.core.utilities.OpaqueBytes
import net.corda.coretesting.internal.stubs.CertificateStoreStubs
import net.corda.finance.DOLLARS
import net.corda.finance.GBP
import net.corda.finance.POUNDS
import net.corda.finance.USD
import net.corda.finance.flows.CashIssueAndPaymentFlow
import net.corda.finance.flows.CashPaymentFlow
import net.corda.finance.workflows.getCashBalance
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.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.CHARLIE_NAME
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.junit.After
import org.junit.Test
import java.nio.file.Path
import java.security.PublicKey
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertNull
class CertificateRotationTest {
private val ref = OpaqueBytes.of(0x01)
private val TestStartedNode.party get() = info.legalIdentities.first()
private lateinit var mockNet: InternalMockNetwork
@After
fun tearDown() {
mockNet.stopNodes()
}
@Test(timeout = 300_000)
fun `restart with the same identities`() {
mockNet = InternalMockNetwork(cordappsForAllNodes = FINANCE_CORDAPPS)
val alice = mockNet.createPartyNode(ALICE_NAME)
val bob = mockNet.createPartyNode(BOB_NAME)
alice.services.startFlow(CashIssueAndPaymentFlow(300.DOLLARS, ref, alice.party, false, mockNet.defaultNotaryIdentity))
alice.services.startFlow(CashIssueAndPaymentFlow(1000.DOLLARS, ref, bob.party, false, mockNet.defaultNotaryIdentity))
bob.services.startFlow(CashIssueAndPaymentFlow(300.POUNDS, ref, bob.party, false, mockNet.defaultNotaryIdentity))
bob.services.startFlow(CashIssueAndPaymentFlow(1000.POUNDS, ref, alice.party, false, mockNet.defaultNotaryIdentity))
mockNet.runNetwork()
val alice2 = mockNet.restartNode(alice)
val bob2 = mockNet.restartNode(bob)
assertEquals(alice.party, alice2.party)
assertEquals(bob.party, bob2.party)
assertEquals(alice2.party, alice2.services.identityService.wellKnownPartyFromX500Name(ALICE_NAME))
assertEquals(bob2.party, alice2.services.identityService.wellKnownPartyFromX500Name(BOB_NAME))
assertEquals(alice2.party, bob2.services.identityService.wellKnownPartyFromX500Name(ALICE_NAME))
assertEquals(bob2.party, bob2.services.identityService.wellKnownPartyFromX500Name(BOB_NAME))
alice2.services.startFlow(CashPaymentFlow(300.DOLLARS, bob2.party, false))
bob2.services.startFlow(CashPaymentFlow(300.POUNDS, alice2.party, false))
mockNet.runNetwork()
bob2.services.startFlow(CashPaymentFlow(1300.DOLLARS, alice2.party, false))
alice2.services.startFlow(CashPaymentFlow(1300.POUNDS, bob2.party, false))
mockNet.runNetwork()
assertEquals(1300.DOLLARS, alice2.services.getCashBalance(USD))
assertEquals(0.POUNDS, alice2.services.getCashBalance(GBP))
assertEquals(0.DOLLARS, bob2.services.getCashBalance(USD))
assertEquals(1300.POUNDS, bob2.services.getCashBalance(GBP))
}
@Test(timeout = 300_000)
fun `restart with rotated key for one node`() {
mockNet = InternalMockNetwork(cordappsForAllNodes = FINANCE_CORDAPPS)
val alice = mockNet.createPartyNode(ALICE_NAME)
val bob = mockNet.createPartyNode(BOB_NAME)
alice.services.startFlow(CashIssueAndPaymentFlow(300.DOLLARS, ref, alice.party, false, mockNet.defaultNotaryIdentity))
alice.services.startFlow(CashIssueAndPaymentFlow(1000.DOLLARS, ref, bob.party, false, mockNet.defaultNotaryIdentity))
bob.services.startFlow(CashIssueAndPaymentFlow(300.POUNDS, ref, bob.party, false, mockNet.defaultNotaryIdentity))
bob.services.startFlow(CashIssueAndPaymentFlow(1000.POUNDS, ref, alice.party, false, mockNet.defaultNotaryIdentity))
mockNet.runNetwork()
val alice2 = mockNet.restartNodeWithRotateIdentityKey(alice)
val bob2 = mockNet.restartNode(bob)
assertNotEquals(alice.party, alice2.party)
assertEquals(bob.party, bob2.party)
assertEquals(alice2.party, alice2.services.identityService.wellKnownPartyFromX500Name(ALICE_NAME))
assertEquals(bob2.party, alice2.services.identityService.wellKnownPartyFromX500Name(BOB_NAME))
assertEquals(alice2.party, bob2.services.identityService.wellKnownPartyFromX500Name(ALICE_NAME))
assertEquals(bob2.party, bob2.services.identityService.wellKnownPartyFromX500Name(BOB_NAME))
alice2.services.startFlow(CashPaymentFlow(300.DOLLARS, bob2.party, false))
bob2.services.startFlow(CashPaymentFlow(300.POUNDS, alice2.party, false))
mockNet.runNetwork()
bob2.services.startFlow(CashPaymentFlow(1300.DOLLARS, alice2.party, false))
alice2.services.startFlow(CashPaymentFlow(1300.POUNDS, bob2.party, false))
mockNet.runNetwork()
assertEquals(1300.DOLLARS, alice2.services.getCashBalance(USD))
assertEquals(0.POUNDS, alice2.services.getCashBalance(GBP))
assertEquals(0.DOLLARS, bob2.services.getCashBalance(USD))
assertEquals(1300.POUNDS, bob2.services.getCashBalance(GBP))
}
@Test(timeout = 300_000)
fun `backchain resolution with rotated issuer key`() {
mockNet = InternalMockNetwork(cordappsForAllNodes = FINANCE_CORDAPPS)
val alice = mockNet.createPartyNode(ALICE_NAME)
val bob = mockNet.createPartyNode(BOB_NAME)
alice.services.startFlow(CashIssueAndPaymentFlow(1000.DOLLARS, ref, alice.party, false, mockNet.defaultNotaryIdentity))
mockNet.runNetwork()
alice.services.startFlow(CashPaymentFlow(1000.DOLLARS, bob.party, false))
mockNet.runNetwork()
val alice2 = mockNet.restartNodeWithRotateIdentityKey(alice)
val bob2 = mockNet.restartNode(bob)
val charlie = mockNet.createPartyNode(CHARLIE_NAME)
assertNotEquals(alice.party, alice2.party)
assertEquals(alice2.party, charlie.services.identityService.wellKnownPartyFromX500Name(ALICE_NAME))
assertEquals(bob2.party, charlie.services.identityService.wellKnownPartyFromX500Name(BOB_NAME))
assertEquals(charlie.party, charlie.services.identityService.wellKnownPartyFromX500Name(CHARLIE_NAME))
bob2.services.startFlow(CashPaymentFlow(1000.DOLLARS, charlie.party, false))
mockNet.runNetwork()
assertEquals(0.DOLLARS, alice2.services.getCashBalance(USD))
assertEquals(0.DOLLARS, bob2.services.getCashBalance(USD))
assertEquals(1000.DOLLARS, charlie.services.getCashBalance(USD))
}
@Test(timeout = 300_000)
fun `backchain resolution with issuer removed from network map`() {
mockNet = InternalMockNetwork(cordappsForAllNodes = FINANCE_CORDAPPS, autoVisibleNodes = false)
val alice = mockNet.createPartyNode(ALICE_NAME)
val bob = mockNet.createPartyNode(BOB_NAME)
advertiseNodesToNetwork(mockNet.defaultNotaryNode, alice, bob)
alice.services.startFlow(CashIssueAndPaymentFlow(1000.DOLLARS, ref, alice.party, false, mockNet.defaultNotaryIdentity))
mockNet.runNetwork()
alice.services.startFlow(CashPaymentFlow(1000.DOLLARS, bob.party, false))
mockNet.runNetwork()
bob.services.networkMapCache.clearNetworkMapCache()
val bob2 = mockNet.restartNode(bob)
val charlie = mockNet.createPartyNode(CHARLIE_NAME)
advertiseNodesToNetwork(mockNet.defaultNotaryNode, bob2, charlie)
assertNull(bob2.services.identityService.wellKnownPartyFromX500Name(ALICE_NAME))
assertNull(charlie.services.identityService.wellKnownPartyFromX500Name(ALICE_NAME))
bob2.services.startFlow(CashPaymentFlow(1000.DOLLARS, charlie.party, false))
mockNet.runNetwork()
charlie.services.startFlow(CashPaymentFlow(300.DOLLARS, bob2.party, false))
mockNet.runNetwork()
assertEquals(300.DOLLARS, bob2.services.getCashBalance(USD))
assertEquals(700.DOLLARS, charlie.services.getCashBalance(USD))
}
private fun InternalMockNetwork.restartNodeWithRotateIdentityKey(node: TestStartedNode): TestStartedNode {
val oldIdentity = rotateIdentityKey(baseDirectory(node) / "certificates")
val restartedNode = restartNode(node)
(restartedNode.services.keyManagementService as KeyManagementServiceInternal).start(listOf(oldIdentity))
return restartedNode
}
private fun rotateIdentityKey(certificatesDirectory: Path): Pair<PublicKey, String> {
val oldIdentityAlias = "old-identity"
val certStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory).get()
certStore.update {
val oldKey = getPrivateKey(NODE_IDENTITY_KEY_ALIAS, DEV_CA_KEY_STORE_PASS)
setPrivateKey(oldIdentityAlias, oldKey, getCertificateChain(NODE_IDENTITY_KEY_ALIAS), DEV_CA_KEY_STORE_PASS)
}
certStore.storeLegalIdentity(NODE_IDENTITY_KEY_ALIAS)
return certStore[oldIdentityAlias].publicKey to oldIdentityAlias
}
private fun advertiseNodesToNetwork(vararg nodes: TestStartedNode) {
nodes.forEach { node ->
nodes.forEach { node.services.networkMapCache.addOrUpdateNode(it.info) }
}
}
}

View File

@ -572,7 +572,6 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
} }
val (nodeInfo, signedNodeInfo) = nodeInfoAndSigned val (nodeInfo, signedNodeInfo) = nodeInfoAndSigned
identityService.ourNames = nodeInfo.legalIdentities.map { it.name }.toSet()
services.start(nodeInfo, netParams) services.start(nodeInfo, netParams)
val networkParametersHotloader = if (networkMapClient == null) { val networkParametersHotloader = if (networkMapClient == null) {

View File

@ -6,7 +6,6 @@ import liquibase.database.Database
import liquibase.database.jvm.JdbcConnection import liquibase.database.jvm.JdbcConnection
import liquibase.exception.ValidationErrors import liquibase.exception.ValidationErrors
import liquibase.resource.ResourceAccessor import liquibase.resource.ResourceAccessor
import net.corda.core.identity.CordaX500Name
import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.MappedSchema
import net.corda.node.SimpleClock import net.corda.node.SimpleClock
import net.corda.node.services.identity.PersistentIdentityService import net.corda.node.services.identity.PersistentIdentityService
@ -15,7 +14,6 @@ import net.corda.node.services.persistence.DBTransactionStorage
import net.corda.node.services.persistence.NodeAttachmentService import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.node.services.persistence.PublicKeyToTextConverter import net.corda.node.services.persistence.PublicKeyToTextConverter
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.SchemaMigration.Companion.NODE_X500_NAME
import java.io.PrintWriter import java.io.PrintWriter
import java.sql.Connection import java.sql.Connection
import java.sql.SQLFeatureNotSupportedException import java.sql.SQLFeatureNotSupportedException
@ -62,10 +60,8 @@ abstract class CordaMigration : CustomTaskChange {
_cordaDB = createDatabase(url, cacheFactory, identityService, schema) _cordaDB = createDatabase(url, cacheFactory, identityService, schema)
cordaDB.start(dataSource) cordaDB.start(dataSource)
identityService.database = cordaDB identityService.database = cordaDB
val ourName = CordaX500Name.parse(System.getProperty(NODE_X500_NAME))
cordaDB.transaction { cordaDB.transaction {
identityService.ourNames = setOf(ourName)
val dbTransactions = DBTransactionStorage(cordaDB, cacheFactory, SimpleClock(Clock.systemUTC())) val dbTransactions = DBTransactionStorage(cordaDB, cacheFactory, SimpleClock(Clock.systemUTC()))
val attachmentsService = NodeAttachmentService(metricRegistry, cacheFactory, cordaDB) val attachmentsService = NodeAttachmentService(metricRegistry, cacheFactory, cordaDB)
_servicesForResolution = MigrationServicesForResolution(identityService, attachmentsService, dbTransactions, cordaDB, cacheFactory) _servicesForResolution = MigrationServicesForResolution(identityService, attachmentsService, dbTransactions, cordaDB, cacheFactory)

View File

@ -28,9 +28,10 @@ class MigrationNamedCacheFactory(private val metricRegistry: MetricRegistry?,
nodeConfiguration?.transactionCacheSizeBytes ?: NodeConfiguration.defaultTransactionCacheSize nodeConfiguration?.transactionCacheSizeBytes ?: NodeConfiguration.defaultTransactionCacheSize
) )
"PersistentIdentityService_keyToPartyAndCert" -> caffeine.maximumSize(defaultCacheSize) "PersistentIdentityService_keyToPartyAndCert" -> caffeine.maximumSize(defaultCacheSize)
"PersistentIdentityService_nameToKey" -> caffeine.maximumSize(defaultCacheSize) "PersistentIdentityService_nameToParty" -> caffeine.maximumSize(defaultCacheSize)
"PersistentIdentityService_keyToName" -> caffeine.maximumSize(defaultCacheSize) "PersistentIdentityService_keyToParty" -> caffeine.maximumSize(defaultCacheSize)
"PersistentIdentityService_hashToKey" -> caffeine.maximumSize(defaultCacheSize) "PersistentNetworkMap_nodesByKey" -> caffeine.maximumSize(defaultCacheSize)
"PersistentNetworkMap_idByLegalName" -> caffeine.maximumSize(defaultCacheSize)
"BasicHSMKeyManagementService_keys" -> caffeine.maximumSize(defaultCacheSize) "BasicHSMKeyManagementService_keys" -> caffeine.maximumSize(defaultCacheSize)
"NodeAttachmentService_attachmentContent" -> caffeine.maximumWeight(defaultCacheSize) "NodeAttachmentService_attachmentContent" -> caffeine.maximumWeight(defaultCacheSize)
"NodeAttachmentService_attachmentPresence" -> caffeine.maximumSize(defaultCacheSize) "NodeAttachmentService_attachmentPresence" -> caffeine.maximumSize(defaultCacheSize)

View File

@ -0,0 +1,102 @@
package net.corda.node.migration
import liquibase.change.custom.CustomTaskChange
import liquibase.database.Database
import liquibase.database.jvm.JdbcConnection
import liquibase.exception.ValidationErrors
import liquibase.resource.ResourceAccessor
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import java.sql.ResultSet
class NodeIdentitiesNoCertMigration : CustomTaskChange {
companion object {
private val logger = contextLogger()
const val UNRESOLVED = "Unresolved"
}
@Suppress("MagicNumber")
override fun execute(database: Database) {
val connection = database.connection as JdbcConnection
logger.info("Preparing to migrate node_identities_no_cert.")
val nodeKeysByHash = mutableMapOf<String, ByteArray>()
connection.queryAll("SELECT pk_hash, identity_value FROM node_identities") { resultSet ->
val hash = resultSet.getString(1)
val certificateBytes = resultSet.getBytes(2)
nodeKeysByHash[hash] = certificateBytes.toKeyBytes()
}
val nodeKeyHashesByName = mutableMapOf<String, String>()
connection.queryAll("SELECT name, pk_hash FROM node_named_identities") { resultSet ->
val name = resultSet.getString(1)
val hash = resultSet.getString(2)
nodeKeyHashesByName[name] = hash
}
logger.info("Starting to migrate node_identities_no_cert.")
var count = 0
connection.queryAll("SELECT pk_hash, name FROM node_identities_no_cert") { resultSet ->
val hash = resultSet.getString(1)
val name = resultSet.getString(2)
val partyKeyHash = nodeKeysByHash[hash]?.let { hash }
?: nodeKeyHashesByName[name]
?: UNRESOLVED.also { logger.warn("Unable to find party key hash for [$name] [$hash]") }
val key = nodeKeysByHash[hash]
?: connection.query("SELECT public_key FROM v_our_key_pairs WHERE public_key_hash = ?", hash)
?: connection.query("SELECT public_key FROM node_hash_to_key WHERE pk_hash = ?", hash)
?: UNRESOLVED.toByteArray().also { logger.warn("Unable to find key for [$name] [$hash]") }
connection.prepareStatement("UPDATE node_identities_no_cert SET party_pk_hash = ?, public_key = ? WHERE pk_hash = ?").use {
it.setString(1, partyKeyHash)
it.setBytes(2, key)
it.setString(3, hash)
it.executeUpdate()
}
count++
}
logger.info("Migrated $count node_identities_no_cert entries.")
}
private fun JdbcConnection.queryAll(statement: String, action: (ResultSet) -> Unit) = createStatement().use {
it.executeQuery(statement).use { resultSet ->
while (resultSet.next()) {
action.invoke(resultSet)
}
}
}
private fun JdbcConnection.query(statement: String, key: String): ByteArray? = prepareStatement(statement).use {
it.setString(1, key)
it.executeQuery().use { resultSet ->
if (resultSet.next()) resultSet.getBytes(1) else null
}
}
private fun ByteArray.toKeyBytes(): ByteArray {
val certPath = X509CertificateFactory().delegate.generateCertPath(inputStream())
val partyAndCertificate = PartyAndCertificate(certPath)
return partyAndCertificate.party.owningKey.encoded
}
override fun setUp() {
}
override fun setFileOpener(resourceAccessor: ResourceAccessor?) {
}
override fun getConfirmationMessage(): String? {
return null
}
override fun validate(database: Database?): ValidationErrors? {
return null
}
}

View File

@ -3,6 +3,7 @@ package net.corda.node.migration
import liquibase.database.Database import liquibase.database.Database
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
import net.corda.core.node.services.Vault import net.corda.core.node.services.Vault
import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentStateRef import net.corda.core.schemas.PersistentStateRef
@ -10,6 +11,7 @@ import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.internal.* import net.corda.core.serialization.internal.*
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.node.internal.DBNetworkParametersStorage import net.corda.node.internal.DBNetworkParametersStorage
import net.corda.node.internal.schemas.NodeInfoSchemaV1
import net.corda.node.services.identity.PersistentIdentityService import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.keys.BasicHSMKeyManagementService import net.corda.node.services.keys.BasicHSMKeyManagementService
import net.corda.node.services.persistence.DBTransactionStorage import net.corda.node.services.persistence.DBTransactionStorage
@ -18,6 +20,7 @@ import net.corda.node.services.vault.NodeVaultService
import net.corda.node.services.vault.VaultSchemaV1 import net.corda.node.services.vault.VaultSchemaV1
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseTransaction import net.corda.nodeapi.internal.persistence.DatabaseTransaction
import net.corda.nodeapi.internal.persistence.SchemaMigration
import net.corda.nodeapi.internal.persistence.currentDBSession import net.corda.nodeapi.internal.persistence.currentDBSession
import net.corda.serialization.internal.AMQP_P2P_CONTEXT import net.corda.serialization.internal.AMQP_P2P_CONTEXT
import net.corda.serialization.internal.AMQP_STORAGE_CONTEXT import net.corda.serialization.internal.AMQP_STORAGE_CONTEXT
@ -74,13 +77,14 @@ class VaultStateMigration : CordaMigration() {
logger.error("Cannot migrate vault states: Liquibase failed to provide a suitable database connection") logger.error("Cannot migrate vault states: Liquibase failed to provide a suitable database connection")
throw VaultStateMigrationException("Cannot migrate vault states as liquibase failed to provide a suitable database connection") throw VaultStateMigrationException("Cannot migrate vault states as liquibase failed to provide a suitable database connection")
} }
initialiseNodeServices(database, setOf(VaultMigrationSchemaV1, VaultSchemaV1)) initialiseNodeServices(database, setOf(VaultMigrationSchemaV1, VaultSchemaV1, NodeInfoSchemaV1))
var statesSkipped = 0 var statesSkipped = 0
val persistentStates = VaultStateIterator(cordaDB) val persistentStates = VaultStateIterator(cordaDB)
if (persistentStates.numStates > 0) { if (persistentStates.numStates > 0) {
logger.warn("Found ${persistentStates.numStates} states to update from a previous version. This may take a while for large " logger.warn("Found ${persistentStates.numStates} states to update from a previous version. This may take a while for large "
+ "volumes of data.") + "volumes of data.")
} }
val ourName = CordaX500Name.parse(System.getProperty(SchemaMigration.NODE_X500_NAME))
VaultStateIterator.withSerializationEnv { VaultStateIterator.withSerializationEnv {
persistentStates.forEach { persistentStates.forEach {
val session = currentDBSession() val session = currentDBSession()
@ -91,9 +95,8 @@ class VaultStateMigration : CordaMigration() {
// Can get away without checking for AbstractMethodErrors here as these will have already occurred when trying to add // Can get away without checking for AbstractMethodErrors here as these will have already occurred when trying to add
// state parties. // state parties.
val myKeys = identityService.stripNotOurKeys(stateAndRef.state.data.participants.map { participant -> val myKeys = stateAndRef.state.data.participants.map { participant -> participant.owningKey}
participant.owningKey .filter { key -> identityService.certificateFromKey(key)?.name == ourName }.toSet()
}).toSet()
if (!NodeVaultService.isRelevant(stateAndRef.state.data, myKeys)) { if (!NodeVaultService.isRelevant(stateAndRef.state.data, myKeys)) {
it.relevancyStatus = Vault.RelevancyStatus.NOT_RELEVANT it.relevancyStatus = Vault.RelevancyStatus.NOT_RELEVANT
} }
@ -127,7 +130,6 @@ object VaultMigrationSchemaV1 : MappedSchema(schemaFamily = VaultMigrationSchema
mappedTypes = listOf( mappedTypes = listOf(
DBTransactionStorage.DBTransaction::class.java, DBTransactionStorage.DBTransaction::class.java,
PersistentIdentityService.PersistentPublicKeyHashToCertificate::class.java, PersistentIdentityService.PersistentPublicKeyHashToCertificate::class.java,
PersistentIdentityService.PersistentPartyToPublicKeyHash::class.java,
PersistentIdentityService.PersistentPublicKeyHashToParty::class.java, PersistentIdentityService.PersistentPublicKeyHashToParty::class.java,
BasicHSMKeyManagementService.PersistentKey::class.java, BasicHSMKeyManagementService.PersistentKey::class.java,
NodeAttachmentService.DBAttachment::class.java, NodeAttachmentService.DBAttachment::class.java,

View File

@ -1,22 +1,15 @@
package net.corda.node.services.api package net.corda.node.services.api
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.services.IdentityService import net.corda.core.node.services.IdentityService
import net.corda.core.utilities.contextLogger
import java.security.InvalidAlgorithmParameterException import java.security.InvalidAlgorithmParameterException
import java.security.cert.CertificateExpiredException import java.security.cert.CertificateExpiredException
import java.security.cert.CertificateNotYetValidException import java.security.cert.CertificateNotYetValidException
interface IdentityServiceInternal : IdentityService { interface IdentityServiceInternal : IdentityService {
private companion object {
val log = contextLogger()
}
/** This method exists so it can be mocked with doNothing, rather than having to make up a possibly invalid return value. */
fun justVerifyAndRegisterIdentity(identity: PartyAndCertificate, isNewRandomIdentity: Boolean = false) {
verifyAndRegisterIdentity(identity, isNewRandomIdentity)
}
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class) @Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
fun verifyAndRegisterIdentity(identity: PartyAndCertificate, isNewRandomIdentity: Boolean): PartyAndCertificate? fun verifyAndRegisterNewRandomIdentity(identity: PartyAndCertificate)
fun invalidateCaches(name: CordaX500Name) {}
} }

View File

@ -59,8 +59,8 @@ class InMemoryIdentityService(
} }
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class) @Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
override fun verifyAndRegisterIdentity(identity: PartyAndCertificate, isNewRandomIdentity: Boolean): PartyAndCertificate? { override fun verifyAndRegisterNewRandomIdentity(identity: PartyAndCertificate) {
return verifyAndRegisterIdentity(trustAnchor, identity) verifyAndRegisterIdentity(trustAnchor, identity)
} }
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class) @Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
@ -134,9 +134,6 @@ class InMemoryIdentityService(
if (externalId != null) { if (externalId != null) {
registerKeyToExternalId(publicKey, externalId) registerKeyToExternalId(publicKey, externalId)
} }
} else {
if (party.name != existingEntry) {
}
} }
} }

View File

@ -11,26 +11,27 @@ import net.corda.core.identity.x500Matches
import net.corda.core.internal.CertRole import net.corda.core.internal.CertRole
import net.corda.core.internal.NamedCacheFactory import net.corda.core.internal.NamedCacheFactory
import net.corda.core.internal.hash import net.corda.core.internal.hash
import net.corda.core.internal.toSet
import net.corda.core.node.NotaryInfo import net.corda.core.node.NotaryInfo
import net.corda.core.node.services.UnknownAnonymousPartyException import net.corda.core.node.services.UnknownAnonymousPartyException
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.utilities.MAX_HASH_HEX_SIZE import net.corda.core.utilities.MAX_HASH_HEX_SIZE
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug import net.corda.core.utilities.debug
import net.corda.node.internal.schemas.NodeInfoSchemaV1
import net.corda.node.services.api.IdentityServiceInternal import net.corda.node.services.api.IdentityServiceInternal
import net.corda.node.services.keys.BasicHSMKeyManagementService
import net.corda.node.services.network.NotaryUpdateListener import net.corda.node.services.network.NotaryUpdateListener
import net.corda.node.services.persistence.PublicKeyHashToExternalId import net.corda.node.services.persistence.PublicKeyHashToExternalId
import net.corda.node.services.persistence.WritablePublicKeyToOwningIdentityCache import net.corda.node.services.persistence.WritablePublicKeyToOwningIdentityCache
import net.corda.node.utilities.AppendOnlyPersistentMap import net.corda.node.utilities.AppendOnlyPersistentMap
import net.corda.node.utilities.NonInvalidatingCache
import net.corda.nodeapi.internal.KeyOwningIdentity import net.corda.nodeapi.internal.KeyOwningIdentity
import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.x509Certificates import net.corda.nodeapi.internal.crypto.x509Certificates
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
import org.apache.commons.lang3.ArrayUtils import net.corda.nodeapi.internal.persistence.currentDBSession
import org.hibernate.Session
import org.hibernate.annotations.Type import org.hibernate.annotations.Type
import org.hibernate.internal.util.collections.ArrayHelper.EMPTY_BYTE_ARRAY import org.hibernate.internal.util.collections.ArrayHelper.EMPTY_BYTE_ARRAY
import java.security.InvalidAlgorithmParameterException import java.security.InvalidAlgorithmParameterException
@ -47,7 +48,6 @@ import javax.annotation.concurrent.ThreadSafe
import javax.persistence.Column import javax.persistence.Column
import javax.persistence.Entity import javax.persistence.Entity
import javax.persistence.Id import javax.persistence.Id
import kotlin.collections.HashSet
import kotlin.streams.toList import kotlin.streams.toList
/** /**
@ -61,15 +61,7 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
companion object { companion object {
private val log = contextLogger() private val log = contextLogger()
const val HASH_TO_IDENTITY_TABLE_NAME = "${NODE_DATABASE_PREFIX}identities" private fun createKeyToPartyAndCertMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap<String, PartyAndCertificate,
const val NAME_TO_HASH_TABLE_NAME = "${NODE_DATABASE_PREFIX}named_identities"
const val KEY_TO_NAME_TABLE_NAME = "${NODE_DATABASE_PREFIX}identities_no_cert"
const val HASH_TO_KEY_TABLE_NAME = "${NODE_DATABASE_PREFIX}hash_to_key"
const val PK_HASH_COLUMN_NAME = "pk_hash"
const val IDENTITY_COLUMN_NAME = "identity_value"
const val NAME_COLUMN_NAME = "name"
fun createKeyToPartyAndCertMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap<String, PartyAndCertificate,
PersistentPublicKeyHashToCertificate, String> { PersistentPublicKeyHashToCertificate, String> {
return AppendOnlyPersistentMap( return AppendOnlyPersistentMap(
cacheFactory = cacheFactory, cacheFactory = cacheFactory,
@ -88,106 +80,76 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
) )
} }
fun createX500ToKeyMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap<CordaX500Name, String, /**
PersistentPartyToPublicKeyHash, String> { * Link anonymous public key to well known party (substituting well-known party public key with its hash).
return AppendOnlyPersistentMap( * Public key for well-known party is linked to itself.
cacheFactory = cacheFactory, */
name = "PersistentIdentityService_nameToKey", private data class KeyToParty(val publicKey: PublicKey, val name: CordaX500Name, val partyPublicKeyHash: String) {
toPersistentEntityKey = { it.toString() }, constructor(party: Party, publicKey: PublicKey = party.owningKey) : this(publicKey, party.name, party.owningKey.toStringShort())
fromPersistentEntity = { val party get() = Party(name, publicKey)
Pair(CordaX500Name.parse(it.name), it.publicKeyHash)
},
toPersistentEntity = { key: CordaX500Name, value: String ->
PersistentPartyToPublicKeyHash(key.toString(), value)
},
persistentEntityClass = PersistentPartyToPublicKeyHash::class.java
)
} }
fun createKeyToX500Map(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap<String, CordaX500Name, private fun createKeyToPartyMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap<String, KeyToParty,
PersistentPublicKeyHashToParty, String> { PersistentPublicKeyHashToParty, String> {
return AppendOnlyPersistentMap( return AppendOnlyPersistentMap(
cacheFactory = cacheFactory, cacheFactory = cacheFactory,
name = "PersistentIdentityService_keyToName", name = "PersistentIdentityService_keyToParty",
toPersistentEntityKey = { it }, toPersistentEntityKey = { it },
fromPersistentEntity = { fromPersistentEntity = {
Pair( Pair(
it.publicKeyHash, it.publicKeyHash,
CordaX500Name.parse(it.name) KeyToParty(Crypto.decodePublicKey(it.publicKey), CordaX500Name.parse(it.name), it.partyPublicKeyHash)
) )
}, },
toPersistentEntity = { key: String, value: CordaX500Name -> toPersistentEntity = { key: String, value: KeyToParty ->
PersistentPublicKeyHashToParty(key, value.toString()) PersistentPublicKeyHashToParty(key, value.name.toString(), value.partyPublicKeyHash, value.publicKey.encoded)
}, },
persistentEntityClass = PersistentPublicKeyHashToParty::class.java) persistentEntityClass = PersistentPublicKeyHashToParty::class.java)
} }
fun createHashToKeyMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap<String, PublicKey, PersistentHashToPublicKey, private fun createNameToPartyMap(cacheFactory: NamedCacheFactory): NonInvalidatingCache<CordaX500Name, Optional<Party>> {
String> { return NonInvalidatingCache(
return AppendOnlyPersistentMap(
cacheFactory = cacheFactory, cacheFactory = cacheFactory,
name = "PersistentIdentityService_hashToKey", name = "PersistentIdentityService_nameToParty",
toPersistentEntityKey = { it }, loadFunction = {
fromPersistentEntity = { val result = currentDBSession().find(NodeInfoSchemaV1.DBPartyAndCertificate::class.java, it.toString())
Pair( Optional.ofNullable(result?.toLegalIdentityAndCert()?.party)
it.publicKeyHash, }
Crypto.decodePublicKey(it.publicKey) )
)
},
toPersistentEntity = { key: String, value: PublicKey ->
PersistentHashToPublicKey(key, value.encoded)
},
persistentEntityClass = PersistentHashToPublicKey::class.java)
} }
private fun mapToKey(party: PartyAndCertificate) = party.owningKey.toStringShort() private fun mapToKey(party: PartyAndCertificate) = party.owningKey.toStringShort()
} }
@Entity @Entity
@javax.persistence.Table(name = HASH_TO_IDENTITY_TABLE_NAME) @javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}identities")
class PersistentPublicKeyHashToCertificate( class PersistentPublicKeyHashToCertificate(
@Id @Id
@Column(name = PK_HASH_COLUMN_NAME, length = MAX_HASH_HEX_SIZE, nullable = false) @Column(name = "pk_hash", length = MAX_HASH_HEX_SIZE, nullable = false)
var publicKeyHash: String = "", var publicKeyHash: String = "",
@Type(type = "corda-blob") @Type(type = "corda-blob")
@Column(name = IDENTITY_COLUMN_NAME, nullable = false) @Column(name = "identity_value", nullable = false)
var identity: ByteArray = EMPTY_BYTE_ARRAY var identity: ByteArray = EMPTY_BYTE_ARRAY
) )
@Entity @Entity
@javax.persistence.Table(name = NAME_TO_HASH_TABLE_NAME) @javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}identities_no_cert")
class PersistentPartyToPublicKeyHash( class PersistentPublicKeyHashToParty(
@Suppress("Unused")
@Id @Id
@Suppress("MagicNumber") // database column width @Column(name = "pk_hash", length = MAX_HASH_HEX_SIZE, nullable = false)
@Column(name = NAME_COLUMN_NAME, length = 128, nullable = false) var publicKeyHash: String = "",
@Column(name = "name", length = 128, nullable = false)
var name: String = "", var name: String = "",
@Column(name = PK_HASH_COLUMN_NAME, length = MAX_HASH_HEX_SIZE, nullable = false) @Column(name = "party_pk_hash", length = MAX_HASH_HEX_SIZE, nullable = false)
var publicKeyHash: String = "" var partyPublicKeyHash: String = "",
)
@Entity
@javax.persistence.Table(name = KEY_TO_NAME_TABLE_NAME)
class PersistentPublicKeyHashToParty(
@Id
@Column(name = PK_HASH_COLUMN_NAME, length = MAX_HASH_HEX_SIZE, nullable = false)
var publicKeyHash: String = "",
@Column(name = NAME_COLUMN_NAME, length = 128, nullable = false)
var name: String = ""
)
@Entity
@javax.persistence.Table(name = HASH_TO_KEY_TABLE_NAME)
class PersistentHashToPublicKey(
@Id
@Column(name = PK_HASH_COLUMN_NAME, length = MAX_HASH_HEX_SIZE, nullable = false)
var publicKeyHash: String = "",
@Type(type = "corda-blob") @Type(type = "corda-blob")
@Column(name = "public_key", nullable = false) @Column(name = "public_key", nullable = false)
var publicKey: ByteArray = ArrayUtils.EMPTY_BYTE_ARRAY var publicKey: ByteArray = EMPTY_BYTE_ARRAY
) )
private lateinit var _caCertStore: CertStore private lateinit var _caCertStore: CertStore
@ -199,6 +161,8 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
private lateinit var _trustAnchor: TrustAnchor private lateinit var _trustAnchor: TrustAnchor
override val trustAnchor: TrustAnchor get() = _trustAnchor override val trustAnchor: TrustAnchor get() = _trustAnchor
private lateinit var ourParty: Party
/** Stores notary identities obtained from the network parameters, for which we don't need to perform a database lookup. */ /** Stores notary identities obtained from the network parameters, for which we don't need to perform a database lookup. */
@Volatile @Volatile
private var notaryIdentityCache = HashSet<Party>() private var notaryIdentityCache = HashSet<Party>()
@ -209,9 +173,8 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
private lateinit var _pkToIdCache: WritablePublicKeyToOwningIdentityCache private lateinit var _pkToIdCache: WritablePublicKeyToOwningIdentityCache
private val keyToPartyAndCert = createKeyToPartyAndCertMap(cacheFactory) private val keyToPartyAndCert = createKeyToPartyAndCertMap(cacheFactory)
private val nameToKey = createX500ToKeyMap(cacheFactory) private val keyToParty = createKeyToPartyMap(cacheFactory)
private val keyToName = createKeyToX500Map(cacheFactory) private val nameToParty = createNameToPartyMap(cacheFactory)
private val hashToKey = createHashToKeyMap(cacheFactory)
fun start( fun start(
trustRoot: X509Certificate, trustRoot: X509Certificate,
@ -226,50 +189,38 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
_caCertStore = CertStore.getInstance("Collection", CollectionCertStoreParameters(certificates)) _caCertStore = CertStore.getInstance("Collection", CollectionCertStoreParameters(certificates))
_pkToIdCache = pkToIdCache _pkToIdCache = pkToIdCache
notaryIdentityCache.addAll(notaryIdentities) notaryIdentityCache.addAll(notaryIdentities)
ourParty = ourIdentity.party
} }
fun loadIdentities(identities: Collection<PartyAndCertificate> = emptySet(), confidentialIdentities: Collection<PartyAndCertificate> = fun loadIdentities(identities: Collection<PartyAndCertificate>) {
emptySet()) {
identities.forEach { identities.forEach {
val key = mapToKey(it) val key = mapToKey(it)
keyToPartyAndCert.addWithDuplicatesAllowed(key, it, false) keyToPartyAndCert.addWithDuplicatesAllowed(key, it, false)
nameToKey.addWithDuplicatesAllowed(it.name, key, false) keyToParty.addWithDuplicatesAllowed(it.owningKey.toStringShort(), KeyToParty(it.party), false)
keyToName.addWithDuplicatesAllowed(mapToKey(it), it.name, false) nameToParty.asMap()[it.name] = Optional.of(it.party)
}
confidentialIdentities.forEach {
keyToName.addWithDuplicatesAllowed(mapToKey(it), it.name, false)
} }
log.debug("Identities loaded") log.debug("Identities loaded")
} }
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class) @Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
override fun verifyAndRegisterIdentity(identity: PartyAndCertificate): PartyAndCertificate? { override fun verifyAndRegisterIdentity(identity: PartyAndCertificate): PartyAndCertificate? {
return verifyAndRegisterIdentity(identity, false) return verifyAndRegisterIdentity(trustAnchor, identity)
} }
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class) @Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
override fun verifyAndRegisterIdentity(identity: PartyAndCertificate, isNewRandomIdentity: Boolean): PartyAndCertificate? { override fun verifyAndRegisterNewRandomIdentity(identity: PartyAndCertificate) {
return database.transaction { verifyAndRegisterIdentity(trustAnchor, identity, true)
verifyAndRegisterIdentity(trustAnchor, identity, isNewRandomIdentity)
}
} }
/**
* Verifies that an identity is valid. If it is valid, it gets registered in the database and the [PartyAndCertificate] is returned.
*
* @param trustAnchor The trust anchor that will verify the identity's validity
* @param identity The identity to verify
* @param isNewRandomIdentity true if identity will not have been registered before (e.g. because it is randomly generated by us)
*/
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class) @Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
private fun verifyAndRegisterIdentity(trustAnchor: TrustAnchor, identity: PartyAndCertificate, isNewRandomIdentity: Boolean = false): private fun verifyAndRegisterIdentity(trustAnchor: TrustAnchor, identity: PartyAndCertificate, isNewRandomIdentity: Boolean = false):
PartyAndCertificate? { PartyAndCertificate? {
// Validate the chain first, before we do anything clever with it // Validate the chain first, before we do anything clever with it
val identityCertChain = identity.certPath.x509Certificates val identityCertChain = identity.certPath.x509Certificates
try { try {
identity.verify(trustAnchor) identity.verify(trustAnchor)
} catch (e: CertPathValidatorException) { } catch (e: CertPathValidatorException) {
log.warn("Certificate validation failed for ${identity.name} against trusted root ${trustAnchor.trustedCert.subjectX500Principal}.") log.warn("Certificate validation failed for ${identity.name} against trusted root ${trustAnchor.trustedCert.subjectX500Principal}.")
log.warn("Certificate path :") log.warn("Certificate path :")
identityCertChain.reversed().forEachIndexed { index, certificate -> identityCertChain.reversed().forEachIndexed { index, certificate ->
val space = (0 until index).joinToString("") { " " } val space = (0 until index).joinToString("") { " " }
@ -292,38 +243,37 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
val identityCertChain = identity.certPath.x509Certificates val identityCertChain = identity.certPath.x509Certificates
val key = mapToKey(identity) val key = mapToKey(identity)
if (isNewRandomIdentity) { return database.transaction {
// Because this is supposed to be new and random, there's no way we have it in the database already, so skip the this check if (isNewRandomIdentity) {
keyToPartyAndCert[key] = identity // Because this is supposed to be new and random, there's no way we have it in the database already, so skip the this check
val parentId = identityCertChain[1].publicKey.toStringShort() keyToPartyAndCert[key] = identity
return keyToPartyAndCert[parentId] // keyToParty is already registered via KMS freshKeyInternal()
} else { } else {
return database.transaction {
keyToPartyAndCert.addWithDuplicatesAllowed(key, identity, false) keyToPartyAndCert.addWithDuplicatesAllowed(key, identity, false)
nameToKey.addWithDuplicatesAllowed(identity.name, key, false) keyToParty.addWithDuplicatesAllowed(identity.owningKey.toStringShort(), KeyToParty(identity.party), false)
keyToName.addWithDuplicatesAllowed(key, identity.name, false)
val parentId = identityCertChain[1].publicKey.toStringShort()
keyToPartyAndCert[parentId]
} }
val parentId = identityCertChain[1].publicKey.toStringShort()
keyToPartyAndCert[parentId]
} }
} }
override fun invalidateCaches(name: CordaX500Name) {
nameToParty.invalidate(name)
}
override fun certificateFromKey(owningKey: PublicKey): PartyAndCertificate? = database.transaction { override fun certificateFromKey(owningKey: PublicKey): PartyAndCertificate? = database.transaction {
keyToPartyAndCert[owningKey.toStringShort()] keyToPartyAndCert[owningKey.toStringShort()]
} }
override fun partyFromKey(key: PublicKey): Party? { override fun partyFromKey(key: PublicKey): Party? = database.transaction {
return certificateFromKey(key)?.party ?: database.transaction { keyToParty[key.toStringShort()]?.let {
keyToName[key.toStringShort()] if (it.partyPublicKeyHash == key.toStringShort()) {
}?.let { wellKnownPartyFromX500Name(it) } // Well-known party is linked to itself.
} it.party
} else {
private fun certificateFromCordaX500Name(name: CordaX500Name): PartyAndCertificate? { // Anonymous party is linked to well-known party.
return database.transaction { keyToParty[it.partyPublicKeyHash]?.party
val partyId = nameToKey[name] }
if (partyId != null) {
keyToPartyAndCert[partyId]
} else null
} }
} }
@ -333,45 +283,47 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
keyToPartyAndCert.allPersisted.use { it.map { it.second }.toList() } keyToPartyAndCert.allPersisted.use { it.map { it.second }.toList() }
} }
} }
override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = database.transaction { override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = database.transaction {
certificateFromCordaX500Name(name)?.party nameToParty[name]?.orElse(null)
} }
override fun wellKnownPartyFromAnonymous(party: AbstractParty): Party? { override fun wellKnownPartyFromAnonymous(party: AbstractParty): Party? {
// Skip database lookup if the party is a notary identity. return database.transaction {
// This also prevents an issue where the notary identity can't be resolved if it's not in the network map cache. The node obtains log.debug("Attempting to find wellKnownParty for: ${party.owningKey.toStringShort()}")
// a trusted list of notary identities from the network parameters automatically. if (party is Party) {
return if (party is Party && party in notaryIdentityCache) { val candidate = wellKnownPartyFromX500Name(party.name)
party if (candidate != null && candidate != party) {
} else { // Party doesn't match existing well-known party: check that the key is registered, otherwise return null.
database.transaction { require(party.name == candidate.name) { "Candidate party $candidate does not match expected $party" }
// Try and resolve the party from the table to public keys to party and certificates keyToParty[party.owningKey.toStringShort()]?.let { candidate }
// If we cannot find it then we perform a lookup on the public key to X500 name table
val legalIdentity = super.wellKnownPartyFromAnonymous(party)
if (legalIdentity == null) {
// If there is no entry in the legal keyToPartyAndCert table then the party must be a confidential identity so we
// perform a lookup in the keyToName table. If an entry for that public key exists, then we attempt look up the
// associated node's PartyAndCertificate.
val name = keyToName[party.owningKey.toStringShort()]
if (name != null) {
// This should never return null as this node would not be able to communicate with the node providing a
// confidential identity unless its NodeInfo/PartyAndCertificate were available.
wellKnownPartyFromX500Name(name)
} else {
null
}
} else { } else {
legalIdentity // Party is a well-known party or well-known party doesn't exist: skip checks.
// If the notary is not in the network map cache, try getting it from the network parameters
// to prevent database conversion issues with vault updates (CORDA-2745).
candidate ?: party.takeIf { it in notaryIdentityCache }
}
} else {
keyToParty[party.owningKey.toStringShort()]?.let {
// Resolved party can be stale due to key rotation: always convert it to the actual well-known party.
wellKnownPartyFromX500Name(it.name)
} }
} }
} }
} }
private fun getAllCertificates(session: Session): List<NodeInfoSchemaV1.DBPartyAndCertificate> {
val criteria = session.criteriaBuilder.createQuery(NodeInfoSchemaV1.DBPartyAndCertificate::class.java)
criteria.select(criteria.from(NodeInfoSchemaV1.DBPartyAndCertificate::class.java))
return session.createQuery(criteria).resultList
}
override fun partiesFromName(query: String, exactMatch: Boolean): Set<Party> { override fun partiesFromName(query: String, exactMatch: Boolean): Set<Party> {
return database.transaction { return database.transaction {
nameToKey.allPersisted.use { getAllCertificates(session)
it.filter { x500Matches(query, exactMatch, it.first) }.map { keyToPartyAndCert[it.second]!!.party }.toSet() .map { it.toLegalIdentityAndCert() }
} .filter { x500Matches(query, exactMatch, it.name) }
.map { it.party }.toSet()
} }
} }
@ -379,34 +331,24 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
override fun assertOwnership(party: Party, anonymousParty: AnonymousParty) = database.transaction { super.assertOwnership(party, override fun assertOwnership(party: Party, anonymousParty: AnonymousParty) = database.transaction { super.assertOwnership(party,
anonymousParty) } anonymousParty) }
lateinit var ourNames: Set<CordaX500Name>
// Allows us to eliminate keys we know belong to others by using the cache contents that might have been seen during other identity
// activity. Concentrating activity on the identity cache works better than spreading checking across identity and key management,
// because we cache misses too.
fun stripNotOurKeys(keys: Iterable<PublicKey>): Iterable<PublicKey> {
return keys.filter { (@Suppress("DEPRECATION") certificateFromKey(it))?.name in ourNames }
}
override fun registerKey(publicKey: PublicKey, party: Party, externalId: UUID?) { override fun registerKey(publicKey: PublicKey, party: Party, externalId: UUID?) {
return database.transaction { return database.transaction {
val publicKeyHash = publicKey.toStringShort()
// EVERY key should be mapped to a Party in the "keyToName" table. Therefore if there is already a record in that table for the // EVERY key should be mapped to a Party in the "keyToName" table. Therefore if there is already a record in that table for the
// specified key then it's either our key which has been stored prior or another node's key which we have previously mapped. // specified key then it's either our key which has been stored prior or another node's key which we have previously mapped.
val existingEntryForKey = keyToName[publicKeyHash] val existingEntryForKey = keyToParty[publicKey.toStringShort()]
if (existingEntryForKey == null) { if (existingEntryForKey == null) {
// Update the three tables as necessary. We definitely store the public key and map it to a party and we optionally update // Update the three tables as necessary. We definitely store the public key and map it to a party and we optionally update
// the public key to external ID mapping table. This block will only ever be reached when registering keys generated on // the public key to external ID mapping table. This block will only ever be reached when registering keys generated on
// other because when a node generates its own keys "registerKeyToParty" is automatically called by // other because when a node generates its own keys "registerKeyToParty" is automatically called by
// KeyManagementService.freshKey. // KeyManagementService.freshKey.
registerKeyToParty(publicKey, party) registerKeyToParty(publicKey, party)
hashToKey[publicKeyHash] = publicKey
if (externalId != null) { if (externalId != null) {
registerKeyToExternalId(publicKey, externalId) registerKeyToExternalId(publicKey, externalId)
} }
} else { } else {
val publicKeyHash = publicKey.toStringShort()
log.info("An existing entry for $publicKeyHash already exists.") log.info("An existing entry for $publicKeyHash already exists.")
if (party.name != existingEntryForKey) { if (party.name != existingEntryForKey.name) {
throw IllegalStateException("The public publicKey $publicKeyHash is already assigned to a different party than the " + throw IllegalStateException("The public publicKey $publicKeyHash is already assigned to a different party than the " +
"supplied party.") "supplied party.")
} }
@ -415,11 +357,11 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
} }
// Internal function used by the KMS to register a public key to a Corda Party. // Internal function used by the KMS to register a public key to a Corda Party.
fun registerKeyToParty(publicKey: PublicKey, party: Party) { fun registerKeyToParty(publicKey: PublicKey, party: Party = ourParty) {
return database.transaction { return database.transaction {
log.info("Linking: ${publicKey.hash} to ${party.name}") log.info("Linking: ${publicKey.hash} to ${party.name}")
keyToName[publicKey.toStringShort()] = party.name keyToParty[publicKey.toStringShort()] = KeyToParty(party, publicKey)
if (party == wellKnownPartyFromX500Name(ourNames.first())) { if (party == ourParty) {
_pkToIdCache[publicKey] = KeyOwningIdentity.UnmappedIdentity _pkToIdCache[publicKey] = KeyOwningIdentity.UnmappedIdentity
} }
} }
@ -434,12 +376,12 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
return _pkToIdCache[publicKey].uuid return _pkToIdCache[publicKey].uuid
} }
private fun publicKeysForExternalId(externalId: UUID, table: Class<*>): List<PublicKey> { override fun publicKeysForExternalId(externalId: UUID): Iterable<PublicKey> {
return database.transaction { return database.transaction {
val query = session.createQuery( val query = session.createQuery(
""" """
select a.publicKey select a.publicKey
from ${table.name} a, ${PublicKeyHashToExternalId::class.java.name} b from ${PersistentPublicKeyHashToParty::class.java.name} a, ${PublicKeyHashToExternalId::class.java.name} b
where b.externalId = :uuid where b.externalId = :uuid
and b.publicKeyHash = a.publicKeyHash and b.publicKeyHash = a.publicKeyHash
""", """,
@ -450,16 +392,6 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
} }
} }
override fun publicKeysForExternalId(externalId: UUID): Iterable<PublicKey> {
// If the externalId was created by this node then we'll find the keys in the KMS, otherwise they'll be in the IdentityService.
val keys = publicKeysForExternalId(externalId, BasicHSMKeyManagementService.PersistentKey::class.java)
return if (keys.isEmpty()) {
publicKeysForExternalId(externalId, PersistentHashToPublicKey::class.java)
} else {
keys
}
}
override fun onNewNotaryList(notaries: List<NotaryInfo>) { override fun onNewNotaryList(notaries: List<NotaryInfo>) {
notaryIdentityCache = HashSet(notaries.map { it.identity }) notaryIdentityCache = HashSet(notaries.map { it.identity })
} }

View File

@ -37,6 +37,7 @@ class BasicHSMKeyManagementService(
@Entity @Entity
@Table(name = "${NODE_DATABASE_PREFIX}our_key_pairs") @Table(name = "${NODE_DATABASE_PREFIX}our_key_pairs")
class PersistentKey( class PersistentKey(
@Suppress("Unused")
@Id @Id
@Column(name = "public_key_hash", length = MAX_HASH_HEX_SIZE, nullable = false) @Column(name = "public_key_hash", length = MAX_HASH_HEX_SIZE, nullable = false)
var publicKeyHash: String, var publicKeyHash: String,
@ -101,10 +102,8 @@ class BasicHSMKeyManagementService(
database.transaction { database.transaction {
keysMap[keyPair.public] = keyPair.private keysMap[keyPair.public] = keyPair.private
// Register the key to our identity. // Register the key to our identity.
val ourIdentity = identityService.wellKnownPartyFromX500Name(identityService.ourNames.first())
?: throw IllegalStateException("Could not lookup node Identity.")
// No checks performed here as entries for the new key couldn't have existed before in the maps. // No checks performed here as entries for the new key couldn't have existed before in the maps.
identityService.registerKeyToParty(keyPair.public, ourIdentity) identityService.registerKeyToParty(keyPair.public)
if (externalId != null) { if (externalId != null) {
identityService.registerKeyToExternalId(keyPair.public, externalId) identityService.registerKeyToExternalId(keyPair.public, externalId)
} }

View File

@ -46,7 +46,7 @@ fun freshCertificate(identityService: IdentityService,
val ourCertPath = X509Utilities.buildCertPath(ourCertificate, issuer.certPath.x509Certificates) val ourCertPath = X509Utilities.buildCertPath(ourCertificate, issuer.certPath.x509Certificates)
val anonymisedIdentity = PartyAndCertificate(ourCertPath) val anonymisedIdentity = PartyAndCertificate(ourCertPath)
if (identityService is IdentityServiceInternal) { if (identityService is IdentityServiceInternal) {
identityService.justVerifyAndRegisterIdentity(anonymisedIdentity, true) identityService.verifyAndRegisterNewRandomIdentity(anonymisedIdentity)
} else { } else {
identityService.verifyAndRegisterIdentity(anonymisedIdentity) identityService.verifyAndRegisterIdentity(anonymisedIdentity)
} }

View File

@ -13,7 +13,6 @@ import net.corda.core.internal.concurrent.openFuture
import net.corda.core.messaging.DataFeed import net.corda.core.messaging.DataFeed
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.node.NotaryInfo import net.corda.core.node.NotaryInfo
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.NetworkMapCache.MapChange import net.corda.core.node.services.NetworkMapCache.MapChange
import net.corda.core.node.services.PartyInfo import net.corda.core.node.services.PartyInfo
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
@ -22,6 +21,7 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug import net.corda.core.utilities.debug
import net.corda.node.internal.schemas.NodeInfoSchemaV1 import net.corda.node.internal.schemas.NodeInfoSchemaV1
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.node.services.api.NetworkMapCacheInternal
import net.corda.node.utilities.NonInvalidatingCache import net.corda.node.utilities.NonInvalidatingCache
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
@ -41,7 +41,8 @@ import javax.persistence.PersistenceException
@Suppress("TooManyFunctions") @Suppress("TooManyFunctions")
open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory, open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory,
private val database: CordaPersistence, private val database: CordaPersistence,
private val identityService: IdentityService) : NetworkMapCacheInternal, SingletonSerializeAsToken(), NotaryUpdateListener { private val identityService: IdentityServiceInternal
) : NetworkMapCacheInternal, SingletonSerializeAsToken(), NotaryUpdateListener {
companion object { companion object {
private val logger = contextLogger() private val logger = contextLogger()
@ -182,8 +183,8 @@ open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory,
} }
previousNode != node -> { previousNode != node -> {
logger.info("Previous node was found for ${node.legalIdentities.first().name} as: ${previousNode.printWithKey()}") logger.info("Previous node was found for ${node.legalIdentities.first().name} as: ${previousNode.printWithKey()}")
// TODO We should be adding any new identities as well // Register new identities for rotated certificates
if (verifyIdentities(node)) { if (verifyAndRegisterIdentities(node)) {
updatedNodes.add(node to previousNode) updatedNodes.add(node to previousNode)
} }
} }
@ -246,6 +247,10 @@ open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory,
changePublisher.onNext(change) changePublisher.onNext(change)
} }
} }
// Invalidate caches outside database transaction to prevent reloading of uncommitted values.
nodeUpdates.forEach { (nodeInfo, _) ->
invalidateIdentityServiceCaches(nodeInfo)
}
} }
override fun addOrUpdateNode(node: NodeInfo) { override fun addOrUpdateNode(node: NodeInfo) {
@ -277,13 +282,15 @@ open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory,
} }
override fun removeNode(node: NodeInfo) { override fun removeNode(node: NodeInfo) {
logger.info("Removing node with info: $node") logger.info("Removing node with info: ${node.printWithKey()}")
synchronized(_changed) { synchronized(_changed) {
database.transaction { database.transaction {
removeInfoDB(session, node) removeInfoDB(session, node)
changePublisher.onNext(MapChange.Removed(node)) changePublisher.onNext(MapChange.Removed(node))
} }
} }
// Invalidate caches outside database transaction to prevent reloading of uncommitted values.
invalidateIdentityServiceCaches(node)
logger.debug { "Done removing node with info: $node" } logger.debug { "Done removing node with info: $node" }
} }
@ -398,6 +405,10 @@ open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory,
identityByLegalNameCache.invalidateAll(nodeInfo.legalIdentities.map { it.name }) identityByLegalNameCache.invalidateAll(nodeInfo.legalIdentities.map { it.name })
} }
private fun invalidateIdentityServiceCaches(nodeInfo: NodeInfo) {
nodeInfo.legalIdentities.forEach { identityService.invalidateCaches(it.name) }
}
private fun invalidateCaches() { private fun invalidateCaches() {
nodesByKeyCache.invalidateAll() nodesByKeyCache.invalidateAll()
identityByLegalNameCache.invalidateAll() identityByLegalNameCache.invalidateAll()

View File

@ -45,9 +45,7 @@ class NodeSchemaService(private val extraSchemas: Set<MappedSchema> = emptySet()
NodeAttachmentService.DBAttachment::class.java, NodeAttachmentService.DBAttachment::class.java,
P2PMessageDeduplicator.ProcessedMessage::class.java, P2PMessageDeduplicator.ProcessedMessage::class.java,
PersistentIdentityService.PersistentPublicKeyHashToCertificate::class.java, PersistentIdentityService.PersistentPublicKeyHashToCertificate::class.java,
PersistentIdentityService.PersistentPartyToPublicKeyHash::class.java,
PersistentIdentityService.PersistentPublicKeyHashToParty::class.java, PersistentIdentityService.PersistentPublicKeyHashToParty::class.java,
PersistentIdentityService.PersistentHashToPublicKey::class.java,
ContractUpgradeServiceImpl.DBContractUpgrade::class.java, ContractUpgradeServiceImpl.DBContractUpgrade::class.java,
DBNetworkParametersStorage.PersistentNetworkParameters::class.java, DBNetworkParametersStorage.PersistentNetworkParameters::class.java,
PublicKeyHashToExternalId::class.java PublicKeyHashToExternalId::class.java

View File

@ -5,7 +5,7 @@ import com.esotericsoftware.kryo.KryoException
import net.corda.core.context.InvocationOrigin import net.corda.core.context.InvocationOrigin
import net.corda.core.flows.Destination import net.corda.core.flows.Destination
import net.corda.core.flows.FlowException import net.corda.core.flows.FlowException
import net.corda.core.identity.AnonymousParty import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
@ -68,19 +68,18 @@ class FlowMessagingImpl(val serviceHub: ServiceHubInternal): FlowMessaging {
} }
private fun createMessage(destination: Destination, message: SessionMessage, deduplicationId: SenderDeduplicationId): MessagingService.AddressedMessage { private fun createMessage(destination: Destination, message: SessionMessage, deduplicationId: SenderDeduplicationId): MessagingService.AddressedMessage {
val party = if (destination is Party) { // We assume that the destination type has already been checked by initiateFlow.
log.trace { "Sending message $deduplicationId $message to $destination" } // Destination may point to a stale well-known identity due to key rotation, so always resolve actual identity via IdentityService.
destination val party = requireNotNull(serviceHub.identityService.wellKnownPartyFromAnonymous(destination as AbstractParty)) {
"We do not know who $destination belongs to"
}
if (destination == party) {
log.trace { "Sending message $deduplicationId $message to $party" }
} else { } else {
// We assume that the destination type has already been checked by initiateFlow log.trace { "Sending message $deduplicationId $message to $party on behalf of $destination" }
val wellKnown = requireNotNull(serviceHub.identityService.wellKnownPartyFromAnonymous(destination as AnonymousParty)) {
"We do not know who $destination belongs to"
}
log.trace { "Sending message $deduplicationId $message to $wellKnown on behalf of $destination" }
wellKnown
} }
val networkMessage = serviceHub.networkService.createMessage(sessionTopic, serializeSessionMessage(message).bytes, deduplicationId, message.additionalHeaders(party)) val networkMessage = serviceHub.networkService.createMessage(sessionTopic, serializeSessionMessage(message).bytes, deduplicationId, message.additionalHeaders(party))
val partyInfo = requireNotNull(serviceHub.networkMapCache.getPartyInfo(party)) { "Don't know about $party" } val partyInfo = requireNotNull(serviceHub.networkMapCache.getPartyInfo(party)) { "Don't know about ${party.description()}" }
val address = serviceHub.networkService.getAddressOfParty(partyInfo) val address = serviceHub.networkService.getAddressOfParty(partyInfo)
val sequenceKey = when (message) { val sequenceKey = when (message) {
is InitialSessionMessage -> message.initiatorSessionId is InitialSessionMessage -> message.initiatorSessionId

View File

@ -46,9 +46,8 @@ open class DefaultNamedCacheFactory protected constructor(private val metricRegi
name == "NodeAttachmentService_attachmentPresence" -> caffeine.maximumSize(attachmentCacheBound) name == "NodeAttachmentService_attachmentPresence" -> caffeine.maximumSize(attachmentCacheBound)
name == "NodeAttachmentService_contractAttachmentVersions" -> caffeine.maximumSize(defaultCacheSize) name == "NodeAttachmentService_contractAttachmentVersions" -> caffeine.maximumSize(defaultCacheSize)
name == "PersistentIdentityService_keyToPartyAndCert" -> caffeine.maximumSize(defaultCacheSize) name == "PersistentIdentityService_keyToPartyAndCert" -> caffeine.maximumSize(defaultCacheSize)
name == "PersistentIdentityService_nameToKey" -> caffeine.maximumSize(defaultCacheSize) name == "PersistentIdentityService_nameToParty" -> caffeine.maximumSize(defaultCacheSize)
name == "PersistentIdentityService_keyToName" -> caffeine.maximumSize(defaultCacheSize) name == "PersistentIdentityService_keyToParty" -> caffeine.maximumSize(defaultCacheSize)
name == "PersistentIdentityService_hashToKey" -> caffeine.maximumSize(defaultCacheSize)
name == "PersistentNetworkMap_nodesByKey" -> caffeine.maximumSize(defaultCacheSize) name == "PersistentNetworkMap_nodesByKey" -> caffeine.maximumSize(defaultCacheSize)
name == "PersistentNetworkMap_idByLegalName" -> caffeine.maximumSize(defaultCacheSize) name == "PersistentNetworkMap_idByLegalName" -> caffeine.maximumSize(defaultCacheSize)
name == "PersistentKeyManagementService_keys" -> caffeine.maximumSize(defaultCacheSize) name == "PersistentKeyManagementService_keys" -> caffeine.maximumSize(defaultCacheSize)

View File

@ -26,6 +26,7 @@
<include file="migration/node-core.changelog-v15.xml"/> <include file="migration/node-core.changelog-v15.xml"/>
<include file="migration/node-core.changelog-v14-data.xml"/> <include file="migration/node-core.changelog-v14-data.xml"/>
<include file="migration/node-core.changelog-v16.xml"/> <include file="migration/node-core.changelog-v16.xml"/>
<include file="migration/node-core.changelog-v20.xml"/>
<!-- This must run after node-core.changelog-init.xml, to prevent database columns being created twice. --> <!-- This must run after node-core.changelog-init.xml, to prevent database columns being created twice. -->
<include file="migration/vault-schema.changelog-v9.xml"/> <include file="migration/vault-schema.changelog-v9.xml"/>

View File

@ -0,0 +1,54 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"
logicalFilePath="migration/node-services.changelog-init.xml">
<changeSet author="R3.Corda" id="identity_service_key_rotation-start" dbms="!postgresql">
<addColumn tableName="node_identities_no_cert">
<column name="party_pk_hash" type="NVARCHAR(130)">
<constraints nullable="true"/>
</column>
<column name="public_key" type="blob">
<constraints nullable="true"/>
</column>
</addColumn>
<createView viewName="v_our_key_pairs">
select public_key_hash, public_key from node_our_key_pairs
</createView>
</changeSet>
<changeSet author="R3.Corda" id="identity_service_key_rotation-start-postgres" dbms="postgresql">
<addColumn tableName="node_identities_no_cert">
<column name="party_pk_hash" type="NVARCHAR(130)">
<constraints nullable="true"/>
</column>
<column name="public_key" type="varbinary(64000)">
<constraints nullable="true"/>
</column>
</addColumn>
<createView viewName="v_our_key_pairs">
select public_key_hash, lo_get(public_key) public_key from node_our_key_pairs
</createView>
</changeSet>
<changeSet author="R3.Corda" id="identity_service_key_rotation-custom">
<customChange class="net.corda.node.migration.NodeIdentitiesNoCertMigration"/>
</changeSet>
<changeSet author="R3.Corda" id="identity_service_key_rotation-end" dbms="!postgresql">
<addNotNullConstraint tableName="node_identities_no_cert" columnName="party_pk_hash" columnDataType="NVARCHAR(130)"/>
<addNotNullConstraint tableName="node_identities_no_cert" columnName="public_key" columnDataType="blob"/>
<dropTable tableName="node_hash_to_key"/>
<dropTable tableName="node_named_identities"/>
<dropView viewName="v_our_key_pairs"/>
</changeSet>
<changeSet author="R3.Corda" id="identity_service_key_rotation-end-postgres" dbms="postgresql">
<addNotNullConstraint tableName="node_identities_no_cert" columnName="party_pk_hash" columnDataType="NVARCHAR(130)"/>
<addNotNullConstraint tableName="node_identities_no_cert" columnName="public_key" columnDataType="varbinary(64000)"/>
<dropTable tableName="node_hash_to_key"/>
<dropTable tableName="node_named_identities"/>
<dropView viewName="v_our_key_pairs"/>
</changeSet>
</databaseChangeLog>

View File

@ -0,0 +1,160 @@
package net.corda.node.migration
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import liquibase.Contexts
import liquibase.Liquibase
import liquibase.database.Database
import liquibase.database.core.H2Database
import liquibase.database.jvm.JdbcConnection
import liquibase.resource.ClassLoaderResourceAccessor
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.toStringShort
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.coretesting.internal.rigorousMock
import net.corda.node.migration.NodeIdentitiesNoCertMigration.Companion.UNRESOLVED
import net.corda.node.services.api.SchemaService
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.x509Certificates
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.contextTransactionOrNull
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.CHARLIE_NAME
import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.configureDatabase
import net.corda.testing.node.MockServices
import org.junit.After
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
class IdenityServiceKeyRotationMigrationTest {
private lateinit var liquibaseDB: Database
private lateinit var cordaDB: CordaPersistence
@Before
fun setUp() {
val schemaService = rigorousMock<SchemaService>()
doReturn(setOf(IdentityTestSchemaV1, KMSTestSchemaV1)).whenever(schemaService).schemas
cordaDB = configureDatabase(
MockServices.makeTestDataSourceProperties(),
DatabaseConfig(),
{ null },
{ null },
schemaService = schemaService,
internalSchemas = setOf(),
ourName = ALICE_NAME)
liquibaseDB = H2Database()
liquibaseDB.connection = JdbcConnection(cordaDB.dataSource.connection)
liquibaseDB.isAutoCommit = true
}
@After
fun close() {
contextTransactionOrNull?.close()
cordaDB.close()
liquibaseDB.close()
}
private fun persist(vararg entries: Any) = cordaDB.transaction {
entries.forEach { session.persist(it) }
}
private fun PartyAndCertificate.dbCertificate() = IdentityTestSchemaV1.NodeIdentities(owningKey.toStringShort(), certPath.encoded)
private fun Party.dbParty() = IdentityTestSchemaV1.NodeIdentitiesNoCert(owningKey.toStringShort(), name.toString())
private fun TestIdentity.dbName() = IdentityTestSchemaV1.NodeNamedIdentities(name.toString(), publicKey.toStringShort())
private fun TestIdentity.dbKey() = IdentityTestSchemaV1.NodeHashToKey(publicKey.toStringShort(), publicKey.encoded)
private fun TestIdentity.dbKeyPair() =
KMSTestSchemaV1.PersistentKey(publicKey.toStringShort(), publicKey.encoded, keyPair.private.encoded)
private fun TestIdentity.generateConfidentialIdentityWithCert(): PartyAndCertificate {
val certificate = X509Utilities.createCertificate(
CertificateType.CONFIDENTIAL_LEGAL_IDENTITY,
identity.certificate,
keyPair,
name.x500Principal,
Crypto.generateKeyPair().public)
return PartyAndCertificate(X509Utilities.buildCertPath(certificate, identity.certPath.x509Certificates))
}
@Test(timeout = 300_000)
fun `test migration`() {
val alice = TestIdentity(ALICE_NAME, 70)
val bob = TestIdentity(BOB_NAME, 80)
val charlie = TestIdentity(CHARLIE_NAME, 90)
val alice2 = TestIdentity(ALICE_NAME, 71)
val alice3 = TestIdentity(ALICE_NAME, 72)
val aliceCiWithCert = alice.generateConfidentialIdentityWithCert()
val bob2 = TestIdentity(BOB_NAME, 81)
val bob3 = TestIdentity(BOB_NAME, 82)
val charlie2 = TestIdentity(CHARLIE_NAME, 91)
val charlie3 = TestIdentity(CHARLIE_NAME, 92)
val charlieCiWithCert = charlie.generateConfidentialIdentityWithCert()
persist(alice.identity.dbCertificate(), alice.party.dbParty(), alice.dbName())
persist(charlie.identity.dbCertificate(), charlie.party.dbParty())
persist(bob.identity.dbCertificate(), bob.party.dbParty(), bob.dbName())
persist(alice2.party.dbParty(), alice2.dbKeyPair())
persist(alice3.party.dbParty())
persist(aliceCiWithCert.dbCertificate(), aliceCiWithCert.party.dbParty())
persist(bob2.party.dbParty(), bob2.dbKey())
persist(bob3.party.dbParty())
persist(charlie2.party.dbParty(), charlie2.dbKey())
persist(charlie3.party.dbParty())
persist(charlieCiWithCert.dbCertificate(), charlieCiWithCert.party.dbParty())
Liquibase("migration/node-core.changelog-v20.xml", object : ClassLoaderResourceAccessor() {
override fun getResourcesAsStream(path: String) = super.getResourcesAsStream(path)?.firstOrNull()?.let { setOf(it) }
}, liquibaseDB).update(Contexts().toString())
val dummyKey = Crypto.generateKeyPair().public
val results = mutableMapOf<String, List<*>>()
cordaDB.transaction {
connection.prepareStatement("SELECT pk_hash, name, party_pk_hash, public_key FROM node_identities_no_cert").use { ps ->
ps.executeQuery().use { rs ->
while (rs.next()) {
val partyKeyHash = rs.getString(3).takeUnless { it == UNRESOLVED } ?: dummyKey.toStringShort()
val key = if (UNRESOLVED.toByteArray().contentEquals(rs.getBytes(4))) {
dummyKey
} else {
Crypto.decodePublicKey(rs.getBytes(4))
}
results[rs.getString(1)] = listOf(key, CordaX500Name.parse(rs.getString(2)), partyKeyHash)
}
}
}
}
assertEquals(11, results.size)
listOf(alice.identity, bob.identity, charlie.identity, aliceCiWithCert, charlieCiWithCert).forEach {
assertEquals(results[it.owningKey.toStringShort()], listOf(it.owningKey, it.party.name, it.owningKey.toStringShort()))
}
assertEquals(results[alice2.publicKey.toStringShort()], listOf(alice2.publicKey, ALICE_NAME, alice.publicKey.toStringShort()))
assertEquals(results[alice3.publicKey.toStringShort()], listOf(dummyKey, ALICE_NAME, alice.publicKey.toStringShort()))
assertEquals(results[bob2.publicKey.toStringShort()], listOf(bob2.publicKey, BOB_NAME, bob.publicKey.toStringShort()))
assertEquals(results[bob3.publicKey.toStringShort()], listOf(dummyKey, BOB_NAME, bob.publicKey.toStringShort()))
assertEquals(results[charlie2.publicKey.toStringShort()], listOf(charlie2.publicKey, CHARLIE_NAME, dummyKey.toStringShort()))
assertEquals(results[charlie3.publicKey.toStringShort()], listOf(dummyKey, CHARLIE_NAME, dummyKey.toStringShort()))
}
}

View File

@ -7,6 +7,7 @@ import org.hibernate.annotations.Type
import javax.persistence.Column import javax.persistence.Column
import javax.persistence.Entity import javax.persistence.Entity
import javax.persistence.Id import javax.persistence.Id
import javax.persistence.Lob
import javax.persistence.Table import javax.persistence.Table
object MigrationTestSchema object MigrationTestSchema
@ -75,4 +76,28 @@ object IdentityTestSchemaV1 : MappedSchema(
@Column(name = "public_key", nullable = false) @Column(name = "public_key", nullable = false)
var publicKey: ByteArray = ArrayUtils.EMPTY_BYTE_ARRAY var publicKey: ByteArray = ArrayUtils.EMPTY_BYTE_ARRAY
) )
}
object KMSTestSchemaV1 : MappedSchema(
schemaFamily = MigrationTestSchema::class.java,
version = 1,
mappedTypes = listOf(
PersistentKey::class.java
)
) {
@Entity
@Table(name = "node_our_key_pairs")
class PersistentKey(
@Id
@Column(name = "public_key_hash", length = MAX_HASH_HEX_SIZE, nullable = false)
var publicKeyHash: String,
@Lob
@Column(name = "public_key", nullable = false)
var publicKey: ByteArray = ArrayUtils.EMPTY_BYTE_ARRAY,
@Lob
@Column(name = "private_key", nullable = false)
var privateKey: ByteArray = ArrayUtils.EMPTY_BYTE_ARRAY
)
} }

View File

@ -26,6 +26,7 @@ import net.corda.finance.contracts.asset.Obligation
import net.corda.finance.contracts.asset.OnLedgerAsset import net.corda.finance.contracts.asset.OnLedgerAsset
import net.corda.finance.schemas.CashSchemaV1 import net.corda.finance.schemas.CashSchemaV1
import net.corda.node.internal.DBNetworkParametersStorage import net.corda.node.internal.DBNetworkParametersStorage
import net.corda.node.internal.schemas.NodeInfoSchemaV1
import net.corda.node.services.identity.PersistentIdentityService import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.keys.BasicHSMKeyManagementService import net.corda.node.services.keys.BasicHSMKeyManagementService
import net.corda.node.services.persistence.DBTransactionStorage import net.corda.node.services.persistence.DBTransactionStorage
@ -194,11 +195,12 @@ class VaultStateMigrationTest {
private fun saveAllIdentities(identities: List<PartyAndCertificate>) { private fun saveAllIdentities(identities: List<PartyAndCertificate>) {
cordaDB.transaction { cordaDB.transaction {
identities.groupBy { it.name }.forEach { name, certs -> identities.groupBy { it.name }.forEach { (_, certs) ->
val persistentIDs = certs.map { PersistentIdentityService.PersistentPublicKeyHashToCertificate(it.owningKey.toStringShort(), it.certPath.encoded) } val persistentIDs = certs.map { PersistentIdentityService.PersistentPublicKeyHashToCertificate(it.owningKey.toStringShort(), it.certPath.encoded) }
val persistentName = PersistentIdentityService.PersistentPartyToPublicKeyHash(name.toString(), certs.first().owningKey.toStringShort())
persistentIDs.forEach { session.save(it) } persistentIDs.forEach { session.save(it) }
session.save(persistentName) val networkIdentity = NodeInfoSchemaV1.DBPartyAndCertificate(certs.first(), true)
val persistentNodeInfo = NodeInfoSchemaV1.PersistentNodeInfo(0, "", listOf(), listOf(networkIdentity), 0, 0)
session.save(persistentNodeInfo)
} }
} }
} }

View File

@ -6,10 +6,13 @@ import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.UnknownAnonymousPartyException import net.corda.core.node.services.UnknownAnonymousPartyException
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.coretesting.internal.DEV_INTERMEDIATE_CA import net.corda.coretesting.internal.DEV_INTERMEDIATE_CA
import net.corda.coretesting.internal.DEV_ROOT_CA import net.corda.coretesting.internal.DEV_ROOT_CA
import net.corda.node.services.network.PersistentNetworkMapCache
import net.corda.node.services.persistence.PublicKeyToOwningIdentityCacheImpl import net.corda.node.services.persistence.PublicKeyToOwningIdentityCacheImpl
import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
@ -18,13 +21,13 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
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.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity import net.corda.testing.core.TestIdentity
import net.corda.testing.core.getTestPartyAndCertificate import net.corda.testing.core.getTestPartyAndCertificate
import net.corda.testing.internal.TestingNamedCacheFactory import net.corda.testing.internal.TestingNamedCacheFactory
import net.corda.testing.internal.configureDatabase import net.corda.testing.internal.configureDatabase
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import net.corda.testing.node.makeTestIdentityService
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
@ -38,6 +41,7 @@ class PersistentIdentityServiceTests {
private companion object { private companion object {
val alice = TestIdentity(ALICE_NAME, 70) val alice = TestIdentity(ALICE_NAME, 70)
val bob = TestIdentity(BOB_NAME, 80) val bob = TestIdentity(BOB_NAME, 80)
val notary = TestIdentity(DUMMY_NOTARY_NAME, 90)
val ALICE get() = alice.party val ALICE get() = alice.party
val ALICE_IDENTITY get() = alice.identity val ALICE_IDENTITY get() = alice.identity
val ALICE_PUBKEY get() = alice.publicKey val ALICE_PUBKEY get() = alice.publicKey
@ -52,6 +56,7 @@ class PersistentIdentityServiceTests {
private val cacheFactory = TestingNamedCacheFactory() private val cacheFactory = TestingNamedCacheFactory()
private lateinit var database: CordaPersistence private lateinit var database: CordaPersistence
private lateinit var identityService: PersistentIdentityService private lateinit var identityService: PersistentIdentityService
private lateinit var networkMapCache: PersistentNetworkMapCache
@Before @Before
fun setup() { fun setup() {
@ -64,11 +69,13 @@ class PersistentIdentityServiceTests {
identityService::wellKnownPartyFromAnonymous identityService::wellKnownPartyFromAnonymous
) )
identityService.database = database identityService.database = database
identityService.ourNames = setOf(ALICE_NAME) identityService.start(
identityService.start(DEV_ROOT_CA.certificate, alice.identity, pkToIdCache = PublicKeyToOwningIdentityCacheImpl( DEV_ROOT_CA.certificate,
database, alice.identity,
cacheFactory listOf(notary.party),
)) PublicKeyToOwningIdentityCacheImpl(database, cacheFactory)
)
networkMapCache = PersistentNetworkMapCache(cacheFactory, database, identityService)
} }
@After @After
@ -97,7 +104,7 @@ class PersistentIdentityServiceTests {
@Test(timeout=300_000) @Test(timeout=300_000)
fun `get identity by key`() { fun `get identity by key`() {
assertNull(identityService.partyFromKey(ALICE_PUBKEY)) assertNull(identityService.partyFromKey(ALICE_PUBKEY))
identityService.verifyAndRegisterIdentity(ALICE_IDENTITY) networkMapCache.verifyAndRegisterIdentity(ALICE_IDENTITY)
assertEquals(ALICE, identityService.partyFromKey(ALICE_PUBKEY)) assertEquals(ALICE, identityService.partyFromKey(ALICE_PUBKEY))
assertNull(identityService.partyFromKey(BOB_PUBKEY)) assertNull(identityService.partyFromKey(BOB_PUBKEY))
} }
@ -107,31 +114,12 @@ class PersistentIdentityServiceTests {
assertNull(identityService.wellKnownPartyFromX500Name(ALICE.name)) assertNull(identityService.wellKnownPartyFromX500Name(ALICE.name))
} }
@Test(timeout=300_000)
fun `stripping others when none registered strips`() {
assertEquals(identityService.stripNotOurKeys(listOf(BOB_PUBKEY)).firstOrNull(), null)
}
@Test(timeout=300_000)
fun `stripping others when only us registered strips`() {
identityService.verifyAndRegisterIdentity(ALICE_IDENTITY)
assertEquals(identityService.stripNotOurKeys(listOf(BOB_PUBKEY)).firstOrNull(), null)
}
@Test(timeout=300_000)
fun `stripping others when us and others registered does not strip us`() {
identityService.verifyAndRegisterIdentity(ALICE_IDENTITY)
identityService.verifyAndRegisterIdentity(BOB_IDENTITY)
val stripped = identityService.stripNotOurKeys(listOf(ALICE_PUBKEY, BOB_PUBKEY))
assertEquals(stripped.single(), ALICE_PUBKEY)
}
@Test(timeout=300_000) @Test(timeout=300_000)
fun `get identity by substring match`() { fun `get identity by substring match`() {
identityService.verifyAndRegisterIdentity(ALICE_IDENTITY) networkMapCache.verifyAndRegisterIdentity(ALICE_IDENTITY)
identityService.verifyAndRegisterIdentity(BOB_IDENTITY) networkMapCache.verifyAndRegisterIdentity(BOB_IDENTITY)
val alicente = getTestPartyAndCertificate(CordaX500Name(organisation = "Alicente Worldwide", locality = "London", country = "GB"), generateKeyPair().public) val alicente = getTestPartyAndCertificate(CordaX500Name(organisation = "Alicente Worldwide", locality = "London", country = "GB"), generateKeyPair().public)
identityService.verifyAndRegisterIdentity(alicente) networkMapCache.verifyAndRegisterIdentity(alicente)
assertEquals(setOf(ALICE, alicente.party), identityService.partiesFromName("Alice", false)) assertEquals(setOf(ALICE, alicente.party), identityService.partiesFromName("Alice", false))
assertEquals(setOf(ALICE), identityService.partiesFromName("Alice Corp", true)) assertEquals(setOf(ALICE), identityService.partiesFromName("Alice Corp", true))
assertEquals(setOf(BOB), identityService.partiesFromName("Bob Plc", true)) assertEquals(setOf(BOB), identityService.partiesFromName("Bob Plc", true))
@ -143,7 +131,7 @@ class PersistentIdentityServiceTests {
.map { getTestPartyAndCertificate(CordaX500Name(organisation = it, locality = "London", country = "GB"), generateKeyPair().public) } .map { getTestPartyAndCertificate(CordaX500Name(organisation = it, locality = "London", country = "GB"), generateKeyPair().public) }
assertNull(identityService.wellKnownPartyFromX500Name(identities.first().name)) assertNull(identityService.wellKnownPartyFromX500Name(identities.first().name))
identities.forEach { identities.forEach {
identityService.verifyAndRegisterIdentity(it) networkMapCache.verifyAndRegisterIdentity(it)
} }
identities.forEach { identities.forEach {
assertEquals(it.party, identityService.wellKnownPartyFromX500Name(it.name)) assertEquals(it.party, identityService.wellKnownPartyFromX500Name(it.name))
@ -224,8 +212,8 @@ class PersistentIdentityServiceTests {
val (bob, anonymousBob) = createConfidentialIdentity(BOB.name) val (bob, anonymousBob) = createConfidentialIdentity(BOB.name)
// Register well known identities // Register well known identities
identityService.verifyAndRegisterIdentity(alice) networkMapCache.verifyAndRegisterIdentity(alice)
identityService.verifyAndRegisterIdentity(bob) networkMapCache.verifyAndRegisterIdentity(bob)
// Register an anonymous identities // Register an anonymous identities
identityService.verifyAndRegisterIdentity(anonymousAlice) identityService.verifyAndRegisterIdentity(anonymousAlice)
identityService.verifyAndRegisterIdentity(anonymousBob) identityService.verifyAndRegisterIdentity(anonymousBob)
@ -270,7 +258,7 @@ class PersistentIdentityServiceTests {
@Test(timeout=300_000) @Test(timeout=300_000)
fun `resolve key to party for key without certificate`() { fun `resolve key to party for key without certificate`() {
// Register Alice's PartyAndCert as if it was done so via the network map cache. // Register Alice's PartyAndCert as if it was done so via the network map cache.
identityService.verifyAndRegisterIdentity(alice.identity) networkMapCache.verifyAndRegisterIdentity(alice.identity)
// Use a key which is not tied to a cert. // Use a key which is not tied to a cert.
val publicKey = Crypto.generateKeyPair().public val publicKey = Crypto.generateKeyPair().public
// Register the PublicKey to Alice's CordaX500Name. // Register the PublicKey to Alice's CordaX500Name.
@ -280,7 +268,7 @@ class PersistentIdentityServiceTests {
@Test(timeout=300_000) @Test(timeout=300_000)
fun `register incorrect party to public key `(){ fun `register incorrect party to public key `(){
database.transaction { identityService.verifyAndRegisterIdentity(ALICE_IDENTITY) } networkMapCache.verifyAndRegisterIdentity(ALICE_IDENTITY)
val (alice, anonymousAlice) = createConfidentialIdentity(ALICE.name) val (alice, anonymousAlice) = createConfidentialIdentity(ALICE.name)
identityService.registerKey(anonymousAlice.owningKey, alice.party) identityService.registerKey(anonymousAlice.owningKey, alice.party)
// Should have no side effect but logs a warning that we tried to overwrite an existing mapping. // Should have no side effect but logs a warning that we tried to overwrite an existing mapping.
@ -309,15 +297,18 @@ class PersistentIdentityServiceTests {
return Pair(issuer, PartyAndCertificate(txCertPath)) return Pair(issuer, PartyAndCertificate(txCertPath))
} }
private fun PersistentNetworkMapCache.verifyAndRegisterIdentity(identity: PartyAndCertificate) {
addOrUpdateNode(NodeInfo(listOf(NetworkHostAndPort("localhost", 12345)), listOf(identity), 1, 0))
}
/** /**
* Ensure if we feed in a full identity, we get the same identity back. * Ensure if we feed in a full identity, we get the same identity back.
*/ */
@Test(timeout=300_000) @Test(timeout=300_000)
fun `deanonymising a well known identity should return the identity`() { fun `deanonymising a well known identity should return the identity`() {
val service = makeTestIdentityService()
val expected = ALICE val expected = ALICE
service.verifyAndRegisterIdentity(ALICE_IDENTITY) networkMapCache.verifyAndRegisterIdentity(ALICE_IDENTITY)
val actual = service.wellKnownPartyFromAnonymous(expected) val actual = identityService.wellKnownPartyFromAnonymous(expected)
assertEquals(expected, actual) assertEquals(expected, actual)
} }
@ -326,10 +317,37 @@ class PersistentIdentityServiceTests {
*/ */
@Test(timeout=300_000) @Test(timeout=300_000)
fun `deanonymising a false well known identity should return null`() { fun `deanonymising a false well known identity should return null`() {
val service = makeTestIdentityService()
val notAlice = Party(ALICE.name, generateKeyPair().public) val notAlice = Party(ALICE.name, generateKeyPair().public)
service.verifyAndRegisterIdentity(ALICE_IDENTITY) networkMapCache.verifyAndRegisterIdentity(ALICE_IDENTITY)
val actual = service.wellKnownPartyFromAnonymous(notAlice) val actual = identityService.wellKnownPartyFromAnonymous(notAlice)
assertNull(actual) assertNull(actual)
} }
@Test(timeout = 300_000)
fun `rotate identity`() {
networkMapCache.verifyAndRegisterIdentity(ALICE_IDENTITY)
val anonymousParty = AnonymousParty(generateKeyPair().public)
identityService.registerKeyToParty(anonymousParty.owningKey)
assertEquals(ALICE, identityService.partyFromKey(anonymousParty.owningKey))
assertEquals(ALICE, identityService.wellKnownPartyFromAnonymous(anonymousParty))
assertEquals(ALICE, identityService.wellKnownPartyFromAnonymous(ALICE))
assertEquals(ALICE, identityService.wellKnownPartyFromX500Name(ALICE.name))
// Make sure that registration of CI with certificate doesn't disrupt well-known party resolution.
val (_, anonymousIdentityWithCert) = createConfidentialIdentity(ALICE.name)
identityService.verifyAndRegisterIdentity(anonymousIdentityWithCert)
assertEquals(ALICE, identityService.wellKnownPartyFromAnonymous(anonymousParty))
assertEquals(ALICE, identityService.wellKnownPartyFromAnonymous(ALICE))
assertEquals(ALICE, identityService.wellKnownPartyFromX500Name(ALICE.name))
val alice2 = getTestPartyAndCertificate(ALICE.name, generateKeyPair().public)
networkMapCache.verifyAndRegisterIdentity(alice2)
assertEquals(alice2.party, identityService.wellKnownPartyFromAnonymous(anonymousParty))
assertEquals(alice2.party, identityService.wellKnownPartyFromAnonymous(ALICE))
assertEquals(alice2.party, identityService.wellKnownPartyFromX500Name(ALICE.name))
assertEquals(alice2.party, identityService.wellKnownPartyFromAnonymous(alice2.party))
assertEquals(setOf(alice2.party), identityService.partiesFromName("Alice Corp", true))
}
} }

View File

@ -3,12 +3,18 @@ package net.corda.node.services.keys
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity import net.corda.testing.core.TestIdentity
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import org.junit.Rule
import org.junit.Test import org.junit.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
class FilterMyKeysTests { class FilterMyKeysTests {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
@Test(timeout=300_000) @Test(timeout=300_000)
fun test() { fun test() {
val name = CordaX500Name("Roger", "Office", "GB") val name = CordaX500Name("Roger", "Office", "GB")

View File

@ -9,11 +9,13 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.withoutDatabaseAccess import net.corda.nodeapi.internal.persistence.withoutDatabaseAccess
import net.corda.testing.common.internal.testNetworkParameters 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.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.TestingNamedCacheFactory import net.corda.testing.internal.TestingNamedCacheFactory
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Rule
import org.junit.Test import org.junit.Test
import java.security.PublicKey import java.security.PublicKey
import java.util.* import java.util.*
@ -21,6 +23,9 @@ import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors import java.util.concurrent.Executors
class PublicKeyToOwningIdentityCacheImplTest { class PublicKeyToOwningIdentityCacheImplTest {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
private lateinit var database: CordaPersistence private lateinit var database: CordaPersistence
private lateinit var testCache: PublicKeyToOwningIdentityCacheImpl private lateinit var testCache: PublicKeyToOwningIdentityCacheImpl

View File

@ -11,6 +11,7 @@ import net.corda.core.flows.StateMachineRunId
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.messaging.DataFeed import net.corda.core.messaging.DataFeed
import net.corda.core.messaging.FlowHandle import net.corda.core.messaging.FlowHandle
import net.corda.core.messaging.FlowProgressHandle import net.corda.core.messaging.FlowProgressHandle
@ -41,6 +42,7 @@ import net.corda.nodeapi.internal.persistence.contextTransaction
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.TestIdentity import net.corda.testing.core.TestIdentity
import net.corda.coretesting.internal.DEV_ROOT_CA import net.corda.coretesting.internal.DEV_ROOT_CA
import net.corda.node.services.network.PersistentNetworkMapCache
import net.corda.testing.internal.MockCordappProvider import net.corda.testing.internal.MockCordappProvider
import net.corda.testing.internal.TestingNamedCacheFactory import net.corda.testing.internal.TestingNamedCacheFactory
import net.corda.testing.internal.configureDatabase import net.corda.testing.internal.configureDatabase
@ -185,11 +187,14 @@ open class MockServices private constructor(
// Create a persistent identity service and add all the supplied identities. // Create a persistent identity service and add all the supplied identities.
identityService.apply { identityService.apply {
ourNames = setOf(initialIdentity.name)
database = persistence database = persistence
start(DEV_ROOT_CA.certificate, initialIdentity.identity, pkToIdCache = pkToIdCache) start(DEV_ROOT_CA.certificate, initialIdentity.identity, pkToIdCache = pkToIdCache)
persistence.transaction { identityService.loadIdentities(moreIdentities + initialIdentity.identity) } persistence.transaction { identityService.loadIdentities(moreIdentities + initialIdentity.identity) }
} }
val networkMapCache = PersistentNetworkMapCache(cacheFactory, persistence, identityService)
(moreIdentities + initialIdentity.identity).forEach {
networkMapCache.addOrUpdateNode(NodeInfo(listOf(NetworkHostAndPort("localhost", 0)), listOf(it), PLATFORM_VERSION, 0))
}
// Create a persistent key management service and add the key pair which was created for the TestIdentity. // Create a persistent key management service and add the key pair which was created for the TestIdentity.
// We only add the keypair for the initial identity and any other keys which this node may control. Note: We don't add the keys // We only add the keypair for the initial identity and any other keys which this node may control. Note: We don't add the keys