CORDA-2347: Added backwards compatibility to SwapIdentitiesFlow (#4548)

The API has been reverted to be completely ABI compatible with V3, and the small changes that were made to the wire format in https://github.com/corda/corda/pull/4260 have also been reverted.
This commit is contained in:
Shams Asari 2019-01-12 14:23:20 +00:00 committed by GitHub
parent 8785bc1b84
commit caad18f6db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 71 additions and 46 deletions

View File

@ -4,29 +4,49 @@ 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.FlowSession
import net.corda.core.flows.*
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.internal.VisibleForTesting
import net.corda.core.internal.cordapp.CordappResolver
import net.corda.core.internal.warnOnce
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. The flow running on the other side must also call this flow at the correct location.
*
* NOTE: This is an inlined flow but for backwards compatibility is annotated with [InitiatingFlow].
*/
class SwapIdentitiesFlow @JvmOverloads constructor(private val otherSideSession: FlowSession,
override val progressTracker: ProgressTracker = tracker()) : FlowLogic<SwapIdentitiesFlow.AnonymousResult>() {
@InitiatingFlow
class SwapIdentitiesFlow
private constructor(private val otherSideSession: FlowSession?,
private val otherParty: Party?,
override val progressTracker: ProgressTracker) : FlowLogic<LinkedHashMap<Party, AnonymousParty>>() {
@JvmOverloads
constructor(otherSideSession: FlowSession, progressTracker: ProgressTracker = tracker()) : this(otherSideSession, null, progressTracker)
@Deprecated("It is unsafe to use this constructor as it requires nodes to automatically vend anonymous identities without first " +
"checking if they should. Instead, use the constructor that takes in an existing FlowSession.")
constructor(otherParty: Party, revocationEnabled: Boolean, progressTracker: ProgressTracker) : this(null, otherParty, progressTracker)
@Deprecated("It is unsafe to use this constructor as it requires nodes to automatically vend anonymous identities without first " +
"checking if they should. Instead, use the constructor that takes in an existing FlowSession.")
constructor(otherParty: Party) : this(null, otherParty, tracker())
companion object {
object GENERATING_IDENTITY : ProgressTracker.Step("Generating our anonymous identity")
object SIGNING_IDENTITY : ProgressTracker.Step("Signing our anonymous identity")
@ -69,38 +89,49 @@ class SwapIdentitiesFlow @JvmOverloads constructor(private val otherSideSession:
}
@Suspendable
override fun call(): AnonymousResult {
override fun call(): LinkedHashMap<Party, AnonymousParty> {
val session = otherSideSession ?: run {
logger.warnOnce("The current usage of SwapIdentitiesFlow is unsafe. Please consider upgrading your CorDapp to use " +
"SwapIdentitiesFlow with FlowSessions. (${CordappResolver.currentCordapp?.info})")
initiateFlow(otherParty!!)
}
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)
val ourIdentWithSig = IdentityWithSignature(ourAnonymousIdentity.serialize(), signature)
progressTracker.currentStep = AWAITING_IDENTITY
val theirAnonymousIdentity = otherSideSession.sendAndReceive<IdentityWithSignature>(ourIdentWithSig).unwrap { theirIdentWithSig ->
val theirAnonymousIdentity = session.sendAndReceive<IdentityWithSignature>(ourIdentWithSig).unwrap { theirIdentWithSig ->
progressTracker.currentStep = VERIFYING_IDENTITY
validateAndRegisterIdentity(serviceHub, otherSideSession.counterparty, theirIdentWithSig.identity, theirIdentWithSig.signature)
validateAndRegisterIdentity(serviceHub, session.counterparty, theirIdentWithSig.identity.deserialize(), theirIdentWithSig.signature)
}
return AnonymousResult(ourAnonymousIdentity.party.anonymise(), theirAnonymousIdentity.party.anonymise())
val identities = LinkedHashMap<Party, AnonymousParty>()
identities[ourIdentity] = ourAnonymousIdentity.party.anonymise()
identities[session.counterparty] = theirAnonymousIdentity.party.anonymise()
return identities
}
/**
* Result class containing the caller's anonymous identity ([ourIdentity]) and the counterparty's ([theirIdentity]).
*/
@CordaSerializable
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 IdentityWithSignature(val identity: SerializedBytes<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
data class CertificateOwnershipAssertion(val x500Name: CordaX500Name, val publicKey: PublicKey)
open class SwapIdentitiesException @JvmOverloads constructor(message: String, cause: Throwable? = null) : FlowException(message, cause)
// This only exists for backwards compatibility
@InitiatedBy(SwapIdentitiesFlow::class)
private class SwapIdentitiesHandler(private val otherSide: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
subFlow(SwapIdentitiesFlow(otherSide))
logger.warnOnce("Insecure API to swap anonymous identities was used by ${otherSide.counterparty} (${otherSide.getCounterpartyFlowInfo()})")
}
}

View File

@ -171,10 +171,7 @@ class SwapIdentitiesFlowTests {
@InitiatingFlow
private class SwapIdentitiesInitiator(private val otherSide: Party) : FlowLogic<Map<Party, AnonymousParty>>() {
@Suspendable
override fun call(): Map<Party, AnonymousParty> {
val (anonymousUs, anonymousThem) = subFlow(SwapIdentitiesFlow(initiateFlow(otherSide)))
return mapOf(ourIdentity to anonymousUs, otherSide to anonymousThem)
}
override fun call(): Map<Party, AnonymousParty> = subFlow(SwapIdentitiesFlow(initiateFlow(otherSide)))
}
@InitiatedBy(SwapIdentitiesInitiator::class)

View File

@ -7,6 +7,7 @@ import net.corda.core.identity.Party
import net.corda.core.identity.groupAbstractPartyByWellKnownParty
import net.corda.core.internal.cordapp.CordappResolver
import net.corda.core.internal.pushToLoggingContext
import net.corda.core.internal.warnOnce
import net.corda.core.node.StatesToRecord
import net.corda.core.node.StatesToRecord.ONLY_RELEVANT
import net.corda.core.transactions.LedgerTransaction
@ -117,8 +118,8 @@ class FinalityFlow private constructor(val transaction: SignedTransaction,
"A flow session for each external participant to the transaction must be provided. If you wish to continue " +
"using this insecure API then specify a target platform version of less than 4 for your CorDapp."
}
logger.warn("The current usage of FinalityFlow is unsafe. Please consider upgrading your CorDapp to use " +
"FinalityFlow with FlowSessions.")
logger.warnOnce("The current usage of FinalityFlow is unsafe. Please consider upgrading your CorDapp to use " +
"FinalityFlow with FlowSessions. (${CordappResolver.currentCordapp?.info})")
} else {
require(sessions.none { serviceHub.myInfo.isLegalIdentity(it.counterparty) }) {
"Do not provide flow sessions for the local node. FinalityFlow will record the notarised transaction locally."

View File

@ -55,17 +55,6 @@ Version 4.0
* The experimental confidential-identities is now a separate CorDapp and must now be loaded onto the node alongside any CorDapp that needs it.
This also means your gradle dependency for it should be ``cordapp`` and not ``cordaCompile``.
* ``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.
* Fixed a bug resulting in poor vault query performance and incorrect results when sorting.
* Improved exception thrown by `AttachmentsClassLoader` when an attachment cannot be used because its uploader is not trusted.
@ -103,9 +92,12 @@ Version 4.0
* ``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
binaries relying on this old behaviour will continue to function as previously. However, it is strongly recommended CorDapps switch to
this new API. See :doc:`app-upgrade-notes` for further details.
* For similar reasons, ``SwapIdentitiesFlow``, from confidential-identities, is also now an inlined flow. The old API has been preserved but
it is strongly recommended CorDapps switch to this new API. See :doc:`app-upgrade-notes` for further details.
* Introduced new optional network bootstrapper command line option (--minimum-platform-version) to set as a network parameter
* Vault storage of contract state constraints metadata and associated vault query functions to retrieve and sort by constraint type.

View File

@ -52,7 +52,7 @@ open class CashPaymentFlow(
val recipientSession = initiateFlow(recipient)
recipientSession.send(anonymous)
val anonymousRecipient = if (anonymous) {
subFlow(SwapIdentitiesFlow(recipientSession)).theirIdentity
subFlow(SwapIdentitiesFlow(recipientSession))[recipient]!!
} else {
recipient
}

View File

@ -55,7 +55,9 @@ object TwoPartyDealFlow {
@Suspendable
override fun call(): SignedTransaction {
progressTracker.currentStep = GENERATING_ID
val (anonymousMe, anonymousCounterparty) = subFlow(SwapIdentitiesFlow(otherSideSession))
val txIdentities = subFlow(SwapIdentitiesFlow(otherSideSession))
val anonymousMe = txIdentities[ourIdentity]!!
val anonymousCounterparty = txIdentities[otherSideSession.counterparty]!!
// DOCEND 2
progressTracker.currentStep = SENDING_PROPOSAL
// Make the first message we'll send to kick off the flow.

View File

@ -7,6 +7,7 @@ import net.corda.core.contracts.requireThat
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.internal.ContractUpgradeUtils
import net.corda.core.internal.warnOnce
import net.corda.core.node.StatesToRecord
import net.corda.core.transactions.ContractUpgradeWireTransaction
import net.corda.core.transactions.SignedTransaction
@ -15,6 +16,7 @@ class FinalityHandler(val sender: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
subFlow(ReceiveTransactionFlow(sender, true, StatesToRecord.ONLY_RELEVANT))
logger.warnOnce("Insecure API to record finalised transaction was used by ${sender.counterparty} (${sender.getCounterpartyFlowInfo()})")
}
}