mirror of
https://github.com/corda/corda.git
synced 2025-02-20 17:33:15 +00:00
Change IdentitySyncFlow to only offer own identities (#1740)
Change IdentitySyncFlow to only offer confidential identities matching the identity the flow is run as. This avoids risks of nodes being convinced to include a state with a confidential identity in it, by a remote node, then feeding the well known identity to the node during identity sync.
This commit is contained in:
parent
46532ccbcb
commit
257756d862
@ -12,14 +12,12 @@ import net.corda.core.utilities.unwrap
|
||||
|
||||
object IdentitySyncFlow {
|
||||
/**
|
||||
* Flow for ensuring that one or more counterparties to a transaction have the full certificate paths of confidential
|
||||
* identities used in the transaction. This is intended for use as a subflow of another flow, typically between
|
||||
* Flow for ensuring that our counterparties in a transaction have the full certificate paths for *our* confidential
|
||||
* identities used in states present in the transaction. This is intended for use as a subflow of another flow, typically between
|
||||
* transaction assembly and signing. An example of where this is useful is where a recipient of a [Cash] state wants
|
||||
* to know that it is being paid by the correct party, and the owner of the state is a confidential identity of that
|
||||
* party. This flow would send a copy of the confidential identity path to the recipient, enabling them to verify that
|
||||
* identity.
|
||||
*
|
||||
* @return a mapping of well known identities to the confidential identities used in the transaction.
|
||||
*/
|
||||
// TODO: Can this be triggered automatically from [SendTransactionFlow]
|
||||
class Send(val otherSideSessions: Set<FlowSession>,
|
||||
@ -36,17 +34,10 @@ object IdentitySyncFlow {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
progressTracker.currentStep = SYNCING_IDENTITIES
|
||||
val states: List<ContractState> = (tx.inputs.map { serviceHub.loadState(it) }.requireNoNulls().map { it.data } + tx.outputs.map { it.data })
|
||||
val identities: Set<AbstractParty> = states.flatMap { it.participants }.toSet()
|
||||
// Filter participants down to the set of those not in the network map (are not well known)
|
||||
val confidentialIdentities = identities
|
||||
.filter { serviceHub.networkMapCache.getNodesByLegalIdentityKey(it.owningKey).isEmpty() }
|
||||
.toList()
|
||||
val identityCertificates: Map<AbstractParty, PartyAndCertificate?> = identities
|
||||
.map { Pair(it, serviceHub.identityService.certificateFromKey(it.owningKey)) }.toMap()
|
||||
val identityCertificates: Map<AbstractParty, PartyAndCertificate?> = extractOurConfidentialIdentities()
|
||||
|
||||
otherSideSessions.forEach { otherSideSession ->
|
||||
val requestedIdentities: List<AbstractParty> = otherSideSession.sendAndReceive<List<AbstractParty>>(confidentialIdentities).unwrap { req ->
|
||||
val requestedIdentities: List<AbstractParty> = otherSideSession.sendAndReceive<List<AbstractParty>>(identityCertificates.keys.toList()).unwrap { req ->
|
||||
require(req.all { it in identityCertificates.keys }) { "${otherSideSession.counterparty} requested a confidential identity not part of transaction: ${tx.id}" }
|
||||
req
|
||||
}
|
||||
@ -61,6 +52,20 @@ object IdentitySyncFlow {
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractOurConfidentialIdentities(): Map<AbstractParty, PartyAndCertificate?> {
|
||||
val states: List<ContractState> = (tx.inputs.map { serviceHub.loadState(it) }.requireNoNulls().map { it.data } + tx.outputs.map { it.data })
|
||||
val identities: Set<AbstractParty> = states.flatMap(ContractState::participants).toSet()
|
||||
// Filter participants down to the set of those not in the network map (are not well known)
|
||||
val confidentialIdentities = identities
|
||||
.filter { serviceHub.networkMapCache.getNodesByLegalIdentityKey(it.owningKey).isEmpty() }
|
||||
.toList()
|
||||
return confidentialIdentities
|
||||
.map { Pair(it, serviceHub.identityService.certificateFromKey(it.owningKey)) }
|
||||
// Filter down to confidential identities of our well known identity
|
||||
// TODO: Consider if this too restrictive - we perhaps should be checking the name on the signing certificate in the certificate path instead
|
||||
.filter { it.second?.name == ourIdentity.name }
|
||||
.toMap()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -13,12 +13,14 @@ import net.corda.core.utilities.unwrap
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.finance.flows.CashIssueAndPaymentFlow
|
||||
import net.corda.finance.flows.CashPaymentFlow
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.node.MockNetwork
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
|
||||
class IdentitySyncFlowTests {
|
||||
@ -45,12 +47,12 @@ class IdentitySyncFlowTests {
|
||||
val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name)
|
||||
val alice: Party = aliceNode.services.myInfo.chooseIdentity()
|
||||
val bob: Party = bobNode.services.myInfo.chooseIdentity()
|
||||
val notary = notaryNode.services.getDefaultNotary()
|
||||
bobNode.internals.registerInitiatedFlow(Receive::class.java)
|
||||
|
||||
// Alice issues then pays some cash to a new confidential identity that Bob doesn't know about
|
||||
val anonymous = true
|
||||
val ref = OpaqueBytes.of(0x01)
|
||||
val notary = aliceNode.services.getDefaultNotary()
|
||||
val issueFlow = aliceNode.services.startFlow(CashIssueAndPaymentFlow(1000.DOLLARS, ref, alice, anonymous, notary))
|
||||
val issueTx = issueFlow.resultFuture.getOrThrow().stx
|
||||
val confidentialIdentity = issueTx.tx.outputs.map { it.data }.filterIsInstance<Cash.State>().single().owner
|
||||
@ -67,6 +69,40 @@ class IdentitySyncFlowTests {
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `don't offer other's identities confidential identities`() {
|
||||
// Set up values we'll need
|
||||
val notaryNode = mockNet.createNotaryNode()
|
||||
val aliceNode = mockNet.createPartyNode(notaryNode.network.myAddress, ALICE.name)
|
||||
val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name)
|
||||
val charlieNode = mockNet.createPartyNode(notaryNode.network.myAddress, CHARLIE.name)
|
||||
val alice: Party = aliceNode.services.myInfo.chooseIdentity()
|
||||
val bob: Party = bobNode.services.myInfo.chooseIdentity()
|
||||
val charlie: Party = charlieNode.services.myInfo.chooseIdentity()
|
||||
val notary = notaryNode.services.getDefaultNotary()
|
||||
bobNode.internals.registerInitiatedFlow(Receive::class.java)
|
||||
|
||||
// Charlie issues then pays some cash to a new confidential identity
|
||||
val anonymous = true
|
||||
val ref = OpaqueBytes.of(0x01)
|
||||
val issueFlow = charlieNode.services.startFlow(CashIssueAndPaymentFlow(1000.DOLLARS, ref, charlie, anonymous, notary))
|
||||
val issueTx = issueFlow.resultFuture.getOrThrow().stx
|
||||
val confidentialIdentity = issueTx.tx.outputs.map { it.data }.filterIsInstance<Cash.State>().single().owner
|
||||
val confidentialIdentCert = charlieNode.services.identityService.certificateFromKey(confidentialIdentity.owningKey)!!
|
||||
|
||||
// Manually inject this identity into Alice's database so the node could leak it, but we prove won't
|
||||
aliceNode.database.transaction { aliceNode.services.identityService.verifyAndRegisterIdentity(confidentialIdentCert) }
|
||||
assertNotNull(aliceNode.database.transaction { aliceNode.services.identityService.wellKnownPartyFromAnonymous(confidentialIdentity) })
|
||||
|
||||
// Generate a payment from Charlie to Alice, including the confidential state
|
||||
val payTx = charlieNode.services.startFlow(CashPaymentFlow(1000.DOLLARS, alice, anonymous)).resultFuture.getOrThrow().stx
|
||||
|
||||
// Run the flow to sync up the identities, and confirm Charlie's confidential identity doesn't leak
|
||||
assertNull(bobNode.database.transaction { bobNode.services.identityService.wellKnownPartyFromAnonymous(confidentialIdentity) })
|
||||
aliceNode.services.startFlow(Initiator(bob, payTx.tx)).resultFuture.getOrThrow()
|
||||
assertNull(bobNode.database.transaction { bobNode.services.identityService.wellKnownPartyFromAnonymous(confidentialIdentity) })
|
||||
}
|
||||
|
||||
/**
|
||||
* Very lightweight wrapping flow to trigger the counterparty flow that receives the identities.
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user