mojaloop / pisp-project

PISP - Payment Initiation Service Provider integration with Mojaloop
Other
9 stars 12 forks source link

transaction authorizations and challenge stringify problem #66

Open eoln opened 4 years ago

eoln commented 4 years ago

There is a need to clarify some details in the process of transaction authorization agreement phase described here: https://github.com/mojaloop/pisp/blob/master/docs/out/transfer/api_calls_detailed/PISPTransferDetailedAPI-page2.png

THE PROBLEM:

PROPOSED SOLUTION:

CONS:

PROS:

ANOTHER APPROACH:

lewisdaly commented 4 years ago

Thanks for this @eoln. I know we have dismissed this before, but this is exactly what the ilpPacket is designed for, and using that for our challenge would make life much easier. (see: https://docs.mojaloop.io/mojaloop-specification/documents/API%20Definition%20v1.0.html#45-ilp-packet)

Perhaps our friend @adrianhopebailie can tune in as he will likely have strong feelings about what should be signed as our 'challenge'.

lewisdaly commented 4 years ago

How big is the ilpPacket? If it's not too big, we should use it, otherwise just the condition.

We specifically want something that can be reconstituted from the quote/transfer. So the condition may not be suitable.

lewisdaly commented 4 years ago

@jgeewax 's decision: hash the ilpPacket, and then sign that as the challenge.

lewisdaly commented 4 years ago

Perhaps we need to add a challenge field to POST /authorizations, which contains the hash of the ilpPacket

mjbrichards commented 4 years ago

The ILP packet contains an encoded version of the Transaction object, which contains all the information about the transfer; so it is rich, but potentially quite large. If we want to sign something small, it might be better to use the condition, which is the signed and hashed version of the Transaction object created by the payee DFSP.

elnyry-sam-k commented 4 years ago

Yes, I think the 'condition' makes sense here..

jgeewax commented 4 years ago

We spoke about that but I think we were lacking some context on how the condition is derived.

What's important is proving after-the-fact that a signature from an end user signed something that was "intrinsically representative of the transaction". This means that it must be completely unique (no two transactions can share the same one), and it should ideally be something that we can derive from some human-understandable data. If we have signature(condition) and someone says "I never authorized the transaction", I want to be able to take the transaction details, use that to compute the condition, and then show them that they digitally signed that and therefore cannot argue that Mojaloop did anything wrong. If the condition works, then that's fine. If we can't easily reproduce it from a set of transaction details (or decode it to see that it represents a transfer of $X from A to B), then we should probably use hash(transactionObject).

In other words, what I'm trying to avoid is a signature of something arbitrary like the transaction ID. Having a signed transaction ID does nothing after the fact to prove that A authorized sending $X to B.

mjbrichards commented 4 years ago

The Condition is a signature generated by the Payee DFSP over the Transaction object using its private key. It can't be regenerated by any other party; but it is a reliable representation of the Transaction object which is otherwise accepted by all parties to the transfer, even though none of them could regenerate it independently. So I think that kind of meets your requirements, provided it's OK to substitute "the payer DFSP" for "I" in confirming that the thing signed was in fact a representation of the Transaction object. In addition, the Payee DFSP returns a fulfilment as proof that it has confirmed the transfer. This can be confirmed against the condition by any party who has both values by applying a SHA-256 hash to the fulfilment: the result should be the condition. This is the current form of security between parties in Mojaloop, and I'd like to retain it if we can...

adrianhopebailie commented 3 years ago

condition = SHA256(fulfillment) - i.e. Always 32 bytes

fulfillment is derived by the Payee DFSP when constructing the quote response. It MUST be unique per transaction and we recommend that it is derived from the Transaction object (fulfillment = HMAC_SHA256(secret, Transaction).

TL:DR; 👍 to using the Condition

jgeewax commented 3 years ago

Just be super clear... If someone says "hey I didn't do that", we need to be able to say "Yes you did. here's what you signed: . if you don't believe it, try it yourself (or verify it yourself)".

My memory is a little hazy on HMAC, but I believe the secret must be kept secret. So when they say "prove that i signed that transaction" we have to say "welp.. we cant because we cant give you that secret", right? If so , I think that's bad...

mjbrichards commented 3 years ago

So we have three pieces of information: the Transaction object itself, the condition and the fulfilment. The payee DFSP creates the fulfilment from the Transaction object, and the condition from the fulfilment; and all parties can be confident that it was the Payee DFSP that created the condition because of non-repudiation. This is the basis which the payer DFSP uses to reserve the funds for its customer; and matching the condition with the fulfilment is the basis on which the switch and the payer DFSP will clear funds and record the transfer for settlement. So I think that the condition is a representation of the terms of the transfer which is accepted by all other parties, and should therefore be sufficient for the PISP. True, the condition was produced using a private key which nobody but the payer DFSP knows; but the object itself will be signed by the PISP using the FIDO private key that it holds, and verified by the FIDO server using the matching public key that it holds. So in order to prove that the PISP signed the object, all we need to know is the FIDO keys, since all parties have already accepted the condition as a valid representation of the terms of the transfer... At least, that's what I think

eoln commented 3 years ago

I am not sure we are still on track. Correct me if I am wrong: User is signing the challenge with his private key, so everybody who knows User's public key can verify pinValue (the challenge signing), especially the User's Payer DFSP. The User can't deny his signing because of the nature of asymmetric cryptography (Priv/Pub key) and challenge which is well known to all involved parties. The problem of this story is what we select as a challenge - am I right?

mjbrichards commented 3 years ago

Absolutely and completely (I'm tempted to add, of course.) And please excuse my typo in l. 8, which should read "payee DFSP", not "payer DFSP". So I think I'd want to phrase the question as follows: we want to be able to verify that the PISP agreed to the terms of the transfer by signing the challenge (and, in principle, that it displayed those terms to its customer and obtained the customer's consent, but we have no way of verifying that.) Given that this is the case, is it sufficiently reliable for the PISP to sign a value (the condition) which is accepted as a valid representation of the terms of the transfer by all the other parties to the transfer, or do we want to insist that it should sign the actual representation of the terms in the Transaction object? I think that the first is sufficient, but I can certainly see the point of the second...

eoln commented 3 years ago

I agree that condition which is accepted by all parties before requesting the authorization from PISP is a good (small enough) candidate, so the execution of cryptographic functions will not be expensive. But @jgeewax pointed out also the problem of how complex could be the transaction verification process. So using the whole Transaction object in the canonically stringified form looks better in this context - but probably it is a little more expensive.

jgeewax commented 3 years ago

Right, my focus is on the verification process for a PISP. The thing that the user is signing should be something that they themselves could reconstruct from the information available without having to trust anything asserted by the DFSP.

When I think through the verification process, using the condition for this purpose seems like the PISP will still need to trust the DFSP, whereas in this scenario, the PISP is actively asserting that they DID NOT send the transaction in question. Here's the conversation I expect to happen in that case:

PISP: Hi. You said I sent $500 USD from Acct 1 @ Bank A to Acct 2 @ Bank B. That definitely wasn't me! Switch/DFSP: Yes it was! You signed Cheque #1234. See? Here's Cheque 1234 with your signature on it. PISP: Right.. but that doesn't have the source account, the amount, and the destination account. Switch/DFSP: Well it does, it's right here as a HMAC I generated. What I did was take the details of the transaction and use HMAC with my secret key and I get these bytes. It's right there on the Cheque! PISP: How do I know that that's what those bytes mean? How can I verify that you're not lying to me...? Switch/DFSP: I'm not! Trust me! I can't give you the secret I used to get those bytes, but I promise that they're representative of your transaction!

At the end of this interaction, we've gone from "I don't trust you, prove it", to "You have paperwork, but none of that is true proof".

If, instead, we just signed a sha256(transaction details), the conversation would instead be something like the following:

PISP: Hi. You said I sent $500 USD from Acct 1 @ Bank A to Acct 2 @ Bank B. That definitely wasn't me! Switch/DFSP: Yes it was! You signed Cheque #1234. See? Here's Cheque 1234 with your signature on it. PISP: Right, but how do I know that signature represents me sending $500 ? Switch/DFSP: Oh that's easy. See these bytes here you signed? Just take the transaction ($500, Bank A, Acct 1, Bank B, Acct 2, etc etc), SHA-256 them. If we use your public key to decrypt your signature (decrypt(sig, key[pub])) we end up with that exact SHA-256 hash! That's how we know you signed and approved of exactly these terms! PISP: Oh. Damn. I must've made a mistake somewhere. Sorry about that.


In short, since HMAC (again, correct me if I'm wrong -- I'm a bit rusty on this...) is unidirectional, it's really only verifiable by those with the secret. We need something that can be generated by both sides, signed, and then that signature used to verify that it, indeed, was sent by an authoritative source (e.g., was definitely sent by the end user). I think that's the transaction itself...

mjbrichards commented 3 years ago

I think I'd like to get a bit provocative here, because I think that the emphasis has shifted. In particular, the thing that's being contested here is not whether or not the PISP "sent" the money (because it can't,) but whether or not it warranted to the institution that could send the money (the payer DFSP) that the PISP had displayed the agreed terms of the transfer to its customer, and the customer had confirmed that the transfer should go ahead.... Now, that might not make a difference, but let's see how this plays out...

PISP: Hi. You said I told Bank A that it was OK to send $500 USD from Acct 1 @ Bank A to Acct 2 @ Bank B. That definitely wasn't me! Switch: Wow, that's pretty strange. So you didn't request a transfer of $500 from Bank A to Bank B, then? Who are the customers involved? PISP: Err.. Switch: Because I've got a request from you asking Bank A to transfer $100 from Ayesha's account at Bank A to Bhavesh's account at Bank B. The identifier is 8b72782a-1d81-4be5-a94a-64a99be49072. And I know it came from you, of course, because I checked the JWS signature with your public key. Do you recognise that identifier?

mjbrichards commented 3 years ago

PISP: Well, might do. But then those aren't the eventual terms of the transfer, are they? And I didn't sign them... Switch: Gosh, well let me check. I think I sent you the quote approval, didn't I? PISP: Can't remember. Switch: Well, you sent me back an acknowledgement saying you'd received it. And I'm assuming you wouldn't have been able to check the terms with Ayesha if you hadn't got them back? PISP: Oh, yes, there they are. I mislaid them for a bit. What am I like? Switch: OK, so you got the terms. Here they are so you can check we're on the same page. And I see the terms include that transfer request Id that you originally sent. And those terms included the condition, and were signed by the payee DFSP so you knew they were kosher, right? PISP: Spose so. Switch: So now, when the FIDO server sent you a challenge, that was the condition, wasn't it? Here it is, just so's you can remind yourself. PISP: Well, I guess they look kind of like each other. Switch: Well, you could check by looking at the quote response you already received. Was the condition you got from the FIDO server the same as the condition you got from the payee DFSP? PISP: Sort of... Switch: Right, so what did you show Ayesha when you asked her to approve the transfer? PISP: Well, the terms of the transfer, of course. Switch: Right, the ones you got back from the payee DFSP. And how did you find them? Was it by looking up the condition? PISP: Might have been. Switch: The ones with the condition attached, the same condition you used to find them and the same condition as the FIDO server asked you to sign? PISP: Yeah, that one. Switch: OK. So where is it you're saying that the discrepancy occurred? PISP: Errr... Switch: Because as far as I can see, the only way this can have broken down is if you showed Ayesha some different set of terms. Would you agree? PISP: Anyone can make a mistake...

Am I misrepresenting the state of things?

jgeewax commented 3 years ago

While I think the interaction you have there will work, it is quite involved and requires lots of external information. And given the issue we have here is "someone's lying", the fewer things we have to trust, and the simpler the verification process, the better IMO. In this case, we have an ID and assumption that quotes were all true to their purpose and that all parties are telling the truth. If we know someone is lying, just not sure who, it's quite involved to figure this out and involves lots of investigation and communication.

If the PISP was signing the entire transaction hash, we could say very quickly: here's your public credential you registered, here's the plaintext transaction info, and here's your signature of that transaction that the DFSP honored.

If the credential isn't right (but all else is fine), we know the PISP cheated and intercepted the credential during linking. They are signing things on our behalf, not getting it signed by our device.

If the plaintext transaction info isn't right (but all else is fine), the user knows the PISP cheated by lying to the user about where the money is going or how much was being sent and getting a signature on something without true consent.

If the signature doesn't check out (but all else is fine), we know the PISP didn't do anything wrong but instead the DFSP honored a transaction it shouldn't have, perhaps tampering with the transaction after it was signed.

There are no IDs to deal with. No extra information to double check. No one else we need to trust that their data wasn't tampered with or take their word that everything checks out (since they can't give us any secrets to check ourselves).

And keep in mind that no one is asserting intentional malice by the parties themselves. They might have been hacked and we need to be able to prove that quickly and simply.

lewisdaly commented 3 years ago

Hi All, thanks for this insightful conversation. I think I'm coming around to @jgeewax 's opinion - signing the actual transaction wherein anybody can do the maths and verify the transaction at a later date does make more sense.

I think the allure of using the condition is that we already have it. To generate a sha256(transaction details), we need to do some more specification work, and we also open up a the possibility that the PISP and DFSP mis-interprets or incorrectly implements these instructions.

Perhaps we can simply use a hash of the ilpPacket (see PUT /quotes/{id}), since that is a shared value that contains the transaction details.

lewisdaly commented 3 years ago

@jgeewax Reading through Adrian's spec change proposal, it looks like v2 of the API might fix the issue where participants can't verify that the fulfilment is in fact related to the ilppacket without revealing the payee's secret.

this section contains more info about the proposed changes

lewisdaly commented 3 years ago

@jgeewax and I talked about this yesterday.

The proposed implementation for generating a challenge will require the generation of txDigest, which is a SHA-256(canonicalJSON(Transaction))

Instead of waiting (or requiring) v2.0 of the FSPIOP-API for this, let's just use txDigest as our challenge.

mjbrichards commented 3 years ago

It's probably worth adding that, when we presented the PISP API proposals at the CCB, the Open API for FSP Interoperability people pushed back on the idea of including the Transaction object in open text in the messages. Assuming that nothing moves terribly quickly in this area, an alternative which would allow us to move forward might be:

This doesn't help us, however, with the Participants list. If we want to include the Participants list, and the Open API people don't see the need, then I don't see a realistic alternative to including payee DFSP messages in the PISP API, and hence to insisting that all DFSP participants in a scheme will need to support the PISP API if any of them do.

adrianhopebailie commented 3 years ago

the Open API for FSP Interoperability people pushed back on the idea of including the Transaction object in open text in the messages

what was the rationale?

mjbrichards commented 3 years ago

Their view (as articulated by Henrik) was that it was a "nice-to-have" rather than "needed-needed". I had a talk with Matt and will be exploring ways round this; but I didn't want this to hold up PISP progress

henrka commented 3 years ago

Hi @mjbrichards, I did not object to sending the transaction object in open? The requirement that was discussed was being able to link a quote from a transfer, which I said was already possible by reading the ILP Packet, why this specific request of being able link a quote from a transfer could already be done. That the transaction object will be sent in the open in the future has already been agreed in https://github.com/mojaloop/mojaloop-specification/issues/13.

I would appreciate if I'm notified in the future if there is anything that we need to discuss in more detail, instead of just referring to me. It is not my purpose to hold up PISP progress, but I would also like to avoid making immediate changes in the FSPIOP API that are not really needed. It would be great if you could explain in more detail in https://github.com/mojaloop/mojaloop-specification/issues/68 why these changes are needed.

Kind regards, Henrik

lewisdaly commented 3 years ago

Hi All,

Regarding this issue, I think we have a resolution for either FSPIOP API v1.1 or v2.0:

@ehenrka apologies for not updating mojaloop/mojaloop-specification#68 earlier, but I assure you it's on my list of things to do. I want to make sure I have all my ducks in a row so I'm not wasting anyone's time going around in circles.

henrka commented 3 years ago

Thanks for the update @lewisdaly!

I just want to make sure that you (not you personally, you as in PISP) to see the CCB as holding up your progress. For me, an important function of the CCB is to protect existing implementers from "unnecessary" changes as well as allowing new features that are well aligned with the existing API. As long as you can explain why something is needed, and that it cannot be done in a proper way in the existing API, and that it aligns well with the existing API, then I'm happy to approve changes.