diff --git a/confidential-identities/src/main/kotlin/net/corda/confidential/SwapIdentitiesFlow.kt b/confidential-identities/src/main/kotlin/net/corda/confidential/SwapIdentitiesFlow.kt index b3da56637a..50f21923cc 100644 --- a/confidential-identities/src/main/kotlin/net/corda/confidential/SwapIdentitiesFlow.kt +++ b/confidential-identities/src/main/kotlin/net/corda/confidential/SwapIdentitiesFlow.kt @@ -1,118 +1,106 @@ package net.corda.confidential import co.paralleluniverse.fibers.Suspendable +import net.corda.core.CordaInternal import net.corda.core.crypto.DigitalSignature +import net.corda.core.crypto.verify import net.corda.core.flows.FlowException import net.corda.core.flows.FlowLogic -import net.corda.core.flows.InitiatingFlow -import net.corda.core.flows.StartableByRPC +import net.corda.core.flows.FlowSession import net.corda.core.identity.AnonymousParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate -import net.corda.core.node.services.IdentityService +import net.corda.core.internal.VisibleForTesting +import net.corda.core.node.ServiceHub import net.corda.core.serialization.CordaSerializable -import net.corda.core.serialization.SerializedBytes -import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.unwrap import java.security.PublicKey import java.security.SignatureException -import java.util.* /** * Very basic flow which generates new confidential identities for parties in a transaction and exchanges the transaction * key and certificate paths between the parties. This is intended for use as a sub-flow of another flow which builds a - * transaction. + * transaction. The flow running on the other side must also call this flow at the correct location. */ -@StartableByRPC -@InitiatingFlow -// TODO Make this non-initiating as otherwise any CorDapp using confidential identities will cause its node to have an -// open door where any counterparty will be able to swap identities at will. Instead SwapIdentitiesFlow and its counterpart, -// SwapIdentitiesHandler, should be in-lined and called by CorDapp specfic-flows. -class SwapIdentitiesFlow(private val otherParty: Party, - private val revocationEnabled: Boolean, - override val progressTracker: ProgressTracker) : FlowLogic>() { - constructor(otherParty: Party) : this(otherParty, false, tracker()) - +class SwapIdentitiesFlow @JvmOverloads constructor(private val otherSideSession: FlowSession, + override val progressTracker: ProgressTracker = tracker()) : FlowLogic() { companion object { - object AWAITING_KEY : ProgressTracker.Step("Awaiting key") + object GENERATING_IDENTITY : ProgressTracker.Step("Generating our anonymous identity") + object SIGNING_IDENTITY : ProgressTracker.Step("Signing our anonymous identity") + object AWAITING_IDENTITY : ProgressTracker.Step("Awaiting counterparty's anonymous identity") + object VERIFYING_IDENTITY : ProgressTracker.Step("Verifying counterparty's anonymous identity") + + @JvmStatic + fun tracker(): ProgressTracker = ProgressTracker(GENERATING_IDENTITY, SIGNING_IDENTITY, AWAITING_IDENTITY, VERIFYING_IDENTITY) - fun tracker() = ProgressTracker(AWAITING_KEY) /** * Generate the deterministic data blob the confidential identity's key holder signs to indicate they want to * represent the subject named in the X.509 certificate. Note that this is never actually sent between nodes, * but only the signature is sent. The blob is built independently on each node and the received signature * verified against the expected blob, rather than exchanging the blob. */ - fun buildDataToSign(confidentialIdentity: PartyAndCertificate): ByteArray { - val certOwnerAssert = CertificateOwnershipAssertion(confidentialIdentity.name, confidentialIdentity.owningKey) - return certOwnerAssert.serialize().bytes + @CordaInternal + @VisibleForTesting + internal fun buildDataToSign(identity: PartyAndCertificate): ByteArray { + return CertificateOwnershipAssertion(identity.name, identity.owningKey).serialize().bytes } - @Throws(SwapIdentitiesException::class) - fun validateAndRegisterIdentity(identityService: IdentityService, - otherSide: Party, - anonymousOtherSideBytes: PartyAndCertificate, - sigBytes: DigitalSignature): PartyAndCertificate { - val anonymousOtherSide: PartyAndCertificate = anonymousOtherSideBytes - if (anonymousOtherSide.name != otherSide.name) { + @CordaInternal + @VisibleForTesting + internal fun validateAndRegisterIdentity(serviceHub: ServiceHub, + otherSide: Party, + theirAnonymousIdentity: PartyAndCertificate, + signature: DigitalSignature): PartyAndCertificate { + if (theirAnonymousIdentity.name != otherSide.name) { throw SwapIdentitiesException("Certificate subject must match counterparty's well known identity.") } - val signature = DigitalSignature.WithKey(anonymousOtherSide.owningKey, sigBytes.bytes) try { - signature.verify(buildDataToSign(anonymousOtherSideBytes)) - } catch(ex: SignatureException) { + theirAnonymousIdentity.owningKey.verify(buildDataToSign(theirAnonymousIdentity), signature) + } catch (ex: SignatureException) { throw SwapIdentitiesException("Signature does not match the expected identity ownership assertion.", ex) } - // Validate then store their identity so that we can prove the key in the transaction is owned by the - // counterparty. - identityService.verifyAndRegisterIdentity(anonymousOtherSide) - return anonymousOtherSide + // Validate then store their identity so that we can prove the key in the transaction is owned by the counterparty. + serviceHub.identityService.verifyAndRegisterIdentity(theirAnonymousIdentity) + return theirAnonymousIdentity } } @Suspendable - override fun call(): LinkedHashMap { - progressTracker.currentStep = AWAITING_KEY - val legalIdentityAnonymous = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, revocationEnabled) - val serializedIdentity = SerializedBytes(legalIdentityAnonymous.serialize().bytes) - - // Special case that if we're both parties, a single identity is generated. - // TODO: for increased privacy, we should create one anonymous key per output state. - val identities = LinkedHashMap() - if (serviceHub.myInfo.isLegalIdentity(otherParty)) { - identities[otherParty] = legalIdentityAnonymous.party.anonymise() - } else { - val otherSession = initiateFlow(otherParty) - val data = buildDataToSign(legalIdentityAnonymous) - val ourSig: DigitalSignature.WithKey = serviceHub.keyManagementService.sign(data, legalIdentityAnonymous.owningKey) - val ourIdentWithSig = IdentityWithSignature(serializedIdentity, ourSig.withoutKey()) - val anonymousOtherSide = otherSession.sendAndReceive(ourIdentWithSig) - .unwrap { (confidentialIdentityBytes, theirSigBytes) -> - val confidentialIdentity: PartyAndCertificate = confidentialIdentityBytes.bytes.deserialize() - validateAndRegisterIdentity(serviceHub.identityService, otherParty, confidentialIdentity, theirSigBytes) - } - identities[ourIdentity] = legalIdentityAnonymous.party.anonymise() - identities[otherParty] = anonymousOtherSide.party.anonymise() + override fun call(): AnonymousResult { + progressTracker.currentStep = GENERATING_IDENTITY + val ourAnonymousIdentity = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, false) + val data = buildDataToSign(ourAnonymousIdentity) + progressTracker.currentStep = SIGNING_IDENTITY + val signature = serviceHub.keyManagementService.sign(data, ourAnonymousIdentity.owningKey).withoutKey() + val ourIdentWithSig = IdentityWithSignature(ourAnonymousIdentity, signature) + progressTracker.currentStep = AWAITING_IDENTITY + val theirAnonymousIdentity = otherSideSession.sendAndReceive(ourIdentWithSig).unwrap { theirIdentWithSig -> + progressTracker.currentStep = VERIFYING_IDENTITY + validateAndRegisterIdentity(serviceHub, otherSideSession.counterparty, theirIdentWithSig.identity, theirIdentWithSig.signature) } - return identities + return AnonymousResult(ourAnonymousIdentity.party.anonymise(), theirAnonymousIdentity.party.anonymise()) } + /** + * Result class containing the caller's anonymous identity ([ourIdentity]) and the counterparty's ([theirIdentity]). + */ @CordaSerializable - data class IdentityWithSignature(val identity: SerializedBytes, val signature: DigitalSignature) + data class AnonymousResult(val ourIdentity: AnonymousParty, val theirIdentity: AnonymousParty) + + @CordaSerializable + private data class IdentityWithSignature(val identity: PartyAndCertificate, val signature: DigitalSignature) + + /** + * Data class used only in the context of asserting that the owner of the private key for the listed key wants to use it + * to represent the named entity. This is paired with an X.509 certificate (which asserts the signing identity says + * the key represents the named entity) and protects against a malicious party incorrectly claiming others' + * keys. + */ + @CordaSerializable + private data class CertificateOwnershipAssertion(val name: CordaX500Name, val owningKey: PublicKey) } -/** - * Data class used only in the context of asserting that the owner of the private key for the listed key wants to use it - * to represent the named entity. This is paired with an X.509 certificate (which asserts the signing identity says - * the key represents the named entity) and protects against a malicious party incorrectly claiming others' - * keys. - */ -@CordaSerializable -data class CertificateOwnershipAssertion(val x500Name: CordaX500Name, - val publicKey: PublicKey) - -open class SwapIdentitiesException @JvmOverloads constructor(message: String, cause: Throwable? = null) - : FlowException(message, cause) \ No newline at end of file +open class SwapIdentitiesException @JvmOverloads constructor(message: String, cause: Throwable? = null) : FlowException(message, cause) diff --git a/confidential-identities/src/main/kotlin/net/corda/confidential/SwapIdentitiesHandler.kt b/confidential-identities/src/main/kotlin/net/corda/confidential/SwapIdentitiesHandler.kt deleted file mode 100644 index a5a17ccdf8..0000000000 --- a/confidential-identities/src/main/kotlin/net/corda/confidential/SwapIdentitiesHandler.kt +++ /dev/null @@ -1,36 +0,0 @@ -package net.corda.confidential - -import co.paralleluniverse.fibers.Suspendable -import net.corda.core.flows.FlowLogic -import net.corda.core.flows.FlowSession -import net.corda.core.identity.PartyAndCertificate -import net.corda.core.serialization.SerializedBytes -import net.corda.core.serialization.deserialize -import net.corda.core.serialization.serialize -import net.corda.core.utilities.ProgressTracker -import net.corda.core.utilities.unwrap - -class SwapIdentitiesHandler(val otherSideSession: FlowSession, val revocationEnabled: Boolean) : FlowLogic() { - constructor(otherSideSession: FlowSession) : this(otherSideSession, false) - - companion object { - object SENDING_KEY : ProgressTracker.Step("Sending key") - } - - override val progressTracker: ProgressTracker = ProgressTracker(SENDING_KEY) - - @Suspendable - override fun call() { - val revocationEnabled = false - progressTracker.currentStep = SENDING_KEY - val ourConfidentialIdentity = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, revocationEnabled) - val serializedIdentity = SerializedBytes(ourConfidentialIdentity.serialize().bytes) - val data = SwapIdentitiesFlow.buildDataToSign(ourConfidentialIdentity) - val ourSig = serviceHub.keyManagementService.sign(data, ourConfidentialIdentity.owningKey) - otherSideSession.sendAndReceive(SwapIdentitiesFlow.IdentityWithSignature(serializedIdentity, ourSig.withoutKey())) - .unwrap { (theirConfidentialIdentityBytes, theirSigBytes) -> - val theirConfidentialIdentity = theirConfidentialIdentityBytes.deserialize() - SwapIdentitiesFlow.validateAndRegisterIdentity(serviceHub.identityService, otherSideSession.counterparty, theirConfidentialIdentity, theirSigBytes) - } - } -} \ No newline at end of file diff --git a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt index 7023242a07..a4ccae45a9 100644 --- a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt +++ b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt @@ -1,26 +1,40 @@ package net.corda.confidential +import co.paralleluniverse.fibers.Suspendable import com.natpryce.hamkrest.MatchResult import com.natpryce.hamkrest.Matcher +import com.natpryce.hamkrest.assertion.assert import com.natpryce.hamkrest.equalTo -import net.corda.core.identity.* +import net.corda.core.crypto.DigitalSignature +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowSession +import net.corda.core.flows.InitiatedBy +import net.corda.core.flows.InitiatingFlow +import net.corda.core.identity.AbstractParty +import net.corda.core.identity.AnonymousParty +import net.corda.core.identity.Party +import net.corda.core.identity.PartyAndCertificate +import net.corda.core.internal.packageName import net.corda.testing.core.* import net.corda.testing.internal.matchers.allOf import net.corda.testing.internal.matchers.flow.willReturn -import net.corda.testing.node.internal.InternalMockNetwork -import net.corda.testing.node.internal.startFlow -import org.junit.Test -import kotlin.test.* -import com.natpryce.hamkrest.assertion.assert -import net.corda.core.crypto.DigitalSignature import net.corda.testing.internal.matchers.hasOnlyEntries +import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.TestStartedNode +import net.corda.testing.node.internal.cordappsForPackages +import net.corda.testing.node.internal.startFlow import org.junit.AfterClass +import org.junit.Test import java.security.PublicKey +import kotlin.test.assertFailsWith class SwapIdentitiesFlowTests { companion object { - private val mockNet = InternalMockNetwork(networkSendManuallyPumped = false, threadPerNode = true) + private val mockNet = InternalMockNetwork( + networkSendManuallyPumped = false, + threadPerNode = true, + cordappsForAllNodes = cordappsForPackages(this::class.packageName) + ) @AfterClass @JvmStatic @@ -36,7 +50,7 @@ class SwapIdentitiesFlowTests { @Test fun `issue key`() { assert.that( - aliceNode.services.startFlow(SwapIdentitiesFlow(bob)), + aliceNode.services.startFlow(SwapIdentitiesInitiator(bob)), willReturn( hasOnlyEntries( alice to allOf( @@ -102,21 +116,20 @@ class SwapIdentitiesFlowTests { services.keyManagementService.freshKeyAndCert(services.myInfo.singleIdentityAndCert(), false) } - private fun TestStartedNode.signSwapIdentitiesFlowData(party: PartyAndCertificate, owningKey: PublicKey) = - services.keyManagementService.sign( - SwapIdentitiesFlow.buildDataToSign(party), - owningKey) + private fun TestStartedNode.signSwapIdentitiesFlowData(party: PartyAndCertificate, owningKey: PublicKey): DigitalSignature.WithKey { + return services.keyManagementService.sign(SwapIdentitiesFlow.buildDataToSign(party), owningKey) + } - private fun TestStartedNode.validateSwapIdentitiesFlow( - party: Party, - counterparty: PartyAndCertificate, - signature: DigitalSignature.WithKey) = - SwapIdentitiesFlow.validateAndRegisterIdentity( - services.identityService, - party, - counterparty, - signature.withoutKey() - ) + private fun TestStartedNode.validateSwapIdentitiesFlow(party: Party, + counterparty: PartyAndCertificate, + signature: DigitalSignature.WithKey): PartyAndCertificate { + return SwapIdentitiesFlow.validateAndRegisterIdentity( + services, + party, + counterparty, + signature.withoutKey() + ) + } //endregion //region Matchers @@ -141,21 +154,36 @@ class SwapIdentitiesFlowTests { override val description = "has an owning key which is ${sayNotIf(negated)}held by ${node.info.singleIdentity().name}" - override fun invoke(actual: AnonymousParty) = - if (negated != actual.owningKey in node.services.keyManagementService.keys) { - MatchResult.Match - } else { - MatchResult.Mismatch(""" - had an owning key which was ${sayNotIf(!negated)}held by ${node.info.singleIdentity().name} - """.trimIndent()) - } - - override fun not(): Matcher { - return copy(negated=!negated) + override fun invoke(actual: AnonymousParty): MatchResult { + return if (negated != actual.owningKey in node.services.keyManagementService.keys) { + MatchResult.Match + } else { + MatchResult.Mismatch(""" + had an owning key which was ${sayNotIf(!negated)}held by ${node.info.singleIdentity().name} + """.trimIndent()) + } } + + override fun not(): Matcher = copy(negated=!negated) } private fun TestStartedNode.holdsOwningKey() = HoldsOwningKeyMatcher(this) //endregion - +} + +@InitiatingFlow +private class SwapIdentitiesInitiator(private val otherSide: Party) : FlowLogic>() { + @Suspendable + override fun call(): Map { + val (anonymousUs, anonymousThem) = subFlow(SwapIdentitiesFlow(initiateFlow(otherSide))) + return mapOf(ourIdentity to anonymousUs, otherSide to anonymousThem) + } +} + +@InitiatedBy(SwapIdentitiesInitiator::class) +private class SwapIdentitiesResponder(private val otherSide: FlowSession) : FlowLogic() { + @Suspendable + override fun call() { + subFlow(SwapIdentitiesFlow(otherSide)) + } } diff --git a/core/src/main/kotlin/net/corda/core/crypto/DigitalSignature.kt b/core/src/main/kotlin/net/corda/core/crypto/DigitalSignature.kt index 24e4ce1587..da48067fb1 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/DigitalSignature.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/DigitalSignature.kt @@ -7,9 +7,6 @@ import java.security.InvalidKeyException import java.security.PublicKey import java.security.SignatureException -// TODO: Is there a use-case for bare DigitalSignature, or is everything a DigitalSignature.WithKey? If there's no -// actual use-case, we should merge the with key version into the parent class. In that case CompositeSignatureWithKeys -// should be renamed to match. /** A wrapper around a digital signature. */ @CordaSerializable @KeepForDJVM diff --git a/core/src/main/kotlin/net/corda/core/crypto/SignedData.kt b/core/src/main/kotlin/net/corda/core/crypto/SignedData.kt index 9a914405e6..c33ac597fd 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/SignedData.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/SignedData.kt @@ -25,7 +25,7 @@ open class SignedData(val raw: SerializedBytes, val sig: DigitalSign */ @Throws(SignatureException::class) fun verified(): T { - sig.by.verify(raw.bytes, sig) + sig.verify(raw) val data: T = uncheckedCast(raw.deserialize()) verifyData(data) return data diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index bcca35addc..507e03100e 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -7,6 +7,17 @@ release, see :doc:`upgrade-notes`. Unreleased ---------- +* ``SwapIdentitiesFlow``, from the experimental confidential-identities module, is now an inlined flow. Instead of passing in a ``Party`` with + whom to exchange the anonymous identity, a ``FlowSession`` to that party is required instead. The flow running on the other side must + also call ``SwapIdentitiesFlow``. This change was required as the previous API allowed any counterparty to generate anonoymous identities + with a node at will with no checks. + + The result type has changed to a simple wrapper class, instead of a Map, to make extracting the identities easier. Also, the wire protocol + of the flow has slightly changed. + + .. note:: V3 and V4 of confidential-identities are not compatible and confidential-identities V3 will not work with a V4 Corda node. CorDapps + in such scenarios using confidential-identities must be updated. + * Marked the ``Attachment`` interface as ``@DoNotImplement`` because it is not meant to be extended by CorDapp developers. If you have already done so, please get in contact on the usual communication channels. @@ -33,10 +44,10 @@ Unreleased un-acknowledged in the message broker. This enables the recovery scenerio whereby any missing CorDapp can be installed and retried on node restart. As a consequence the initiating flow will be blocked until the receiving node has resolved the issue. -* ``FinalityFlow`` is now an inlined flow and no longer requires a handler flow in the counterparty. This is to fix the - security problem with the handler flow as it accepts any transaction it receives without any checks. Existing CorDapp - binaries relying on this old behaviour will continue to function as previously. However, it is strongly recommended that - CorDapps switch to this new API. See :doc:`upgrade-notes` for further details. +* ``FinalityFlow`` is now an inlined flow and requires ``FlowSession`` s to each party intended to receive the transaction. This is to fix the + security problem with the old API that required every node to accept any transaction it received without any checks. Existing CorDapp + binaries relying on this old behaviour will continue to function as previously. However, it is strongly recommended that CorDapps switch to + this new API. See :doc:`upgrade-notes` for further details. * Introduced new optional network bootstrapper command line option (--minimum-platform-version) to set as a network parameter diff --git a/finance/src/main/kotlin/net/corda/finance/flows/CashPaymentFlow.kt b/finance/src/main/kotlin/net/corda/finance/flows/CashPaymentFlow.kt index 9eca7ca3df..bed2aaadd1 100644 --- a/finance/src/main/kotlin/net/corda/finance/flows/CashPaymentFlow.kt +++ b/finance/src/main/kotlin/net/corda/finance/flows/CashPaymentFlow.kt @@ -5,11 +5,11 @@ import net.corda.confidential.SwapIdentitiesFlow import net.corda.core.contracts.Amount import net.corda.core.contracts.InsufficientBalanceException import net.corda.core.flows.* -import net.corda.core.identity.AnonymousParty import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.ProgressTracker +import net.corda.core.utilities.unwrap import net.corda.finance.contracts.asset.Cash import net.corda.finance.flows.AbstractCashFlow.Companion.FINALISING_TX import net.corda.finance.flows.AbstractCashFlow.Companion.GENERATING_ID @@ -49,23 +49,26 @@ open class CashPaymentFlow( @Suspendable override fun call(): AbstractCashFlow.Result { progressTracker.currentStep = GENERATING_ID - val txIdentities = if (anonymous) { - subFlow(SwapIdentitiesFlow(recipient)) + val recipientSession = initiateFlow(recipient) + recipientSession.send(anonymous) + val anonymousRecipient = if (anonymous) { + subFlow(SwapIdentitiesFlow(recipientSession)).theirIdentity } else { - emptyMap() + recipient } - val anonymousRecipient = txIdentities[recipient] ?: recipient progressTracker.currentStep = GENERATING_TX val builder = TransactionBuilder(notary = notary ?: serviceHub.networkMapCache.notaryIdentities.first()) logger.info("Generating spend for: ${builder.lockId}") // TODO: Have some way of restricting this to states the caller controls val (spendTX, keysForSigning) = try { - Cash.generateSpend(serviceHub, + Cash.generateSpend( + serviceHub, builder, amount, ourIdentityAndCert, anonymousRecipient, - issuerConstraint) + issuerConstraint + ) } catch (e: InsufficientBalanceException) { throw CashException("Insufficient cash for spend: ${e.message}", e) } @@ -76,8 +79,8 @@ open class CashPaymentFlow( progressTracker.currentStep = FINALISING_TX logger.info("Finalising transaction for: ${tx.id}") - val sessions = if (serviceHub.myInfo.isLegalIdentity(recipient)) emptyList() else listOf(initiateFlow(recipient)) - val notarised = finaliseTx(tx, sessions, "Unable to notarise spend") + val sessionsForFinality = if (serviceHub.myInfo.isLegalIdentity(recipient)) emptyList() else listOf(recipientSession) + val notarised = finaliseTx(tx, sessionsForFinality, "Unable to notarise spend") logger.info("Finalised transaction for: ${notarised.id}") return Result(notarised, anonymousRecipient) } @@ -91,9 +94,16 @@ open class CashPaymentFlow( } @InitiatedBy(CashPaymentFlow::class) -class CashPaymentResponderFlow(private val otherSide: FlowSession) : FlowLogic() { +class CashPaymentReceiverFlow(private val otherSide: FlowSession) : FlowLogic() { @Suspendable override fun call() { - subFlow(ReceiveFinalityFlow(otherSide)) + val anonymous = otherSide.receive().unwrap { it } + if (anonymous) { + subFlow(SwapIdentitiesFlow(otherSide)) + } + // Not ideal that we have to do this check, but we must as FinalityFlow does not send locally + if (!serviceHub.myInfo.isLegalIdentity(otherSide.counterparty)) { + subFlow(ReceiveFinalityFlow(otherSide)) + } } } diff --git a/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyDealFlow.kt b/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyDealFlow.kt index 413937aaa9..3bfff54f01 100644 --- a/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyDealFlow.kt +++ b/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyDealFlow.kt @@ -55,9 +55,7 @@ object TwoPartyDealFlow { @Suspendable override fun call(): SignedTransaction { progressTracker.currentStep = GENERATING_ID - val txIdentities = subFlow(SwapIdentitiesFlow(otherSideSession.counterparty)) - val anonymousMe = txIdentities[ourIdentity] ?: ourIdentity.anonymise() - val anonymousCounterparty = txIdentities[otherSideSession.counterparty] ?: otherSideSession.counterparty.anonymise() + val (anonymousMe, anonymousCounterparty) = subFlow(SwapIdentitiesFlow(otherSideSession)) // DOCEND 2 progressTracker.currentStep = SENDING_PROPOSAL // Make the first message we'll send to kick off the flow. @@ -131,6 +129,7 @@ object TwoPartyDealFlow { @Suspendable private fun receiveAndValidateHandshake(): Handshake { + subFlow(SwapIdentitiesFlow(otherSideSession)) progressTracker.currentStep = RECEIVING // Wait for a trade request to come in on our pre-provided session ID. val handshake = otherSideSession.receive>() diff --git a/node/build.gradle b/node/build.gradle index e540562221..df942c3b35 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -68,7 +68,6 @@ processTestResources { dependencies { compile project(':node-api') - compile project(":confidential-identities") compile project(':client:rpc') compile project(':tools:shell') compile project(':tools:cliutils') diff --git a/node/src/integration-test/kotlin/net/corda/node/services/distributed/DistributedServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/distributed/DistributedServiceTests.kt index 1bb2148e96..07ecdf2999 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/distributed/DistributedServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/distributed/DistributedServiceTests.kt @@ -42,13 +42,12 @@ class DistributedServiceTests { invokeRpc(CordaRPCOps::stateMachinesFeed)) ) driver(DriverParameters( - extraCordappPackagesToScan = listOf("net.corda.finance.contracts", "net.corda.finance.schemas", "net.corda.notary.raft"), - notarySpecs = listOf( - NotarySpec( - DUMMY_NOTARY_NAME, - rpcUsers = listOf(testUser), - cluster = DummyClusterSpec(clusterSize = 3, compositeServiceIdentity = compositeIdentity)) - ) + extraCordappPackagesToScan = listOf("net.corda.finance", "net.corda.notary.raft"), + notarySpecs = listOf(NotarySpec( + DUMMY_NOTARY_NAME, + rpcUsers = listOf(testUser), + cluster = DummyClusterSpec(clusterSize = 3, compositeServiceIdentity = compositeIdentity) + )) )) { alice = startNode(providedName = ALICE_NAME, rpcUsers = listOf(testUser)).getOrThrow() raftNotaryIdentity = defaultNotaryIdentity diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index f5b3b87afb..e0b57a1a0f 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -4,8 +4,6 @@ import com.codahale.metrics.MetricRegistry import com.google.common.collect.MutableClassToInstanceMap import com.google.common.util.concurrent.MoreExecutors import com.zaxxer.hikari.pool.HikariPool -import net.corda.confidential.SwapIdentitiesFlow -import net.corda.confidential.SwapIdentitiesHandler import net.corda.core.CordaException import net.corda.core.concurrent.CordaFuture import net.corda.core.context.InvocationContext @@ -660,8 +658,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration, installFinalityHandler() flowManager.registerInitiatedCoreFlowFactory(NotaryChangeFlow::class, NotaryChangeHandler::class, ::NotaryChangeHandler) flowManager.registerInitiatedCoreFlowFactory(ContractUpgradeFlow.Initiate::class, NotaryChangeHandler::class, ::ContractUpgradeHandler) - // TODO Make this an inlined flow (and remove this flow mapping!), which should be possible now that FinalityFlow is also inlined - flowManager.registerInitiatedCoreFlowFactory(SwapIdentitiesFlow::class, SwapIdentitiesHandler::class, ::SwapIdentitiesHandler) } // The FinalityHandler is insecure as it blindly accepts any and all transactions into the node's local vault without doing any checks. diff --git a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt index 42836c56e7..f338dc60b7 100644 --- a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt @@ -14,7 +14,6 @@ import net.corda.core.flows.FlowLogic import net.corda.core.flows.StartableByRPC import net.corda.core.flows.StateMachineRunId import net.corda.core.identity.Party -import net.corda.core.internal.extractFile import net.corda.core.internal.uncheckedCast import net.corda.core.messaging.* import net.corda.core.node.services.Vault @@ -31,6 +30,7 @@ import net.corda.finance.GBP import net.corda.finance.contracts.asset.Cash import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow +import net.corda.node.internal.security.AuthorizingSubject import net.corda.node.internal.security.RPCSecurityManagerImpl import net.corda.node.services.Permissions.Companion.invokeRpc import net.corda.node.services.Permissions.Companion.startFlow @@ -56,20 +56,22 @@ import org.junit.Before import org.junit.Test import rx.Observable import java.io.ByteArrayOutputStream -import java.util.jar.JarInputStream import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNull import kotlin.test.assertTrue // Mock an AuthorizingSubject instance sticking to a fixed set of permissions -private fun buildSubject(principal: String, permissionStrings: Set) = - RPCSecurityManagerImpl.fromUserList( - id = AuthServiceId("TEST"), - users = listOf(User(username = principal, - password = "", - permissions = permissionStrings))) - .buildSubject(principal) +private fun buildSubject(principal: String, permissionStrings: Set): AuthorizingSubject { + return RPCSecurityManagerImpl.fromUserList( + id = AuthServiceId("TEST"), + users = listOf(User( + username = principal, + password = "", + permissions = permissionStrings + )) + ).buildSubject(principal) +} class CordaRPCOpsImplTest { private companion object { @@ -87,7 +89,7 @@ class CordaRPCOpsImplTest { @Before fun setup() { - mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.finance.contracts.asset", "net.corda.finance.schemas")) + mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.finance")) aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME)) rpc = aliceNode.rpcOps CURRENT_RPC_CONTEXT.set(RpcAuthContext(InvocationContext.rpc(testActor()), buildSubject("TEST_USER", emptySet()))) @@ -163,11 +165,13 @@ class CordaRPCOpsImplTest { @Test fun `issue and move`() { @Suppress("DEPRECATION") - withPermissions(invokeRpc(CordaRPCOps::stateMachinesFeed), + withPermissions( + invokeRpc(CordaRPCOps::stateMachinesFeed), invokeRpc(CordaRPCOps::internalVerifiedTransactionsFeed), invokeRpc("vaultTrackBy"), startFlow(), - startFlow()) { + startFlow() + ) { aliceNode.database.transaction { stateMachineUpdates = rpc.stateMachinesFeed().updates transactions = rpc.internalVerifiedTransactionsFeed().updates @@ -183,7 +187,8 @@ class CordaRPCOpsImplTest { mockNet.runNetwork() var issueSmId: StateMachineRunId? = null - var moveSmId: StateMachineRunId? = null + var paymentSmId: StateMachineRunId? = null + var paymentRecSmId: StateMachineRunId? = null stateMachineUpdates.expectEvents { sequence( // ISSUE @@ -191,14 +196,20 @@ class CordaRPCOpsImplTest { issueSmId = add.id }, expect { remove: StateMachineUpdate.Removed -> - require(remove.id == issueSmId) + assertThat(remove.id).isEqualTo(issueSmId) }, - // MOVE + // PAYMENT expect { add: StateMachineUpdate.Added -> - moveSmId = add.id + paymentSmId = add.id + }, + expect { add: StateMachineUpdate.Added -> + paymentRecSmId = add.id }, expect { remove: StateMachineUpdate.Removed -> - require(remove.id == moveSmId) + assertThat(remove.id).isEqualTo(paymentRecSmId) + }, + expect { remove: StateMachineUpdate.Removed -> + assertThat(remove.id).isEqualTo(paymentSmId) } ) }