Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discussion of Payment AuthZ proposal #1

Open
balfanz opened this issue Apr 16, 2020 · 8 comments
Open

Discussion of Payment AuthZ proposal #1

balfanz opened this issue Apr 16, 2020 · 8 comments

Comments

@balfanz
Copy link

balfanz commented Apr 16, 2020

Hi @adrianhopebailie - thanks for writing up this interesting proposal.

May I ask a couple of questions?

This proposal covers use cases where merchants use the webpayments API (and payments handlers) to initiate the transaction, yes?

In such a scenario, couldn't the payment handler make the webauthn call and include the transaction information in the challenge? Let me explain:

If I understand correctly, in your proposal the browser would create a clientData object that might look something like this:

{
  type: "webauthn.get",
  challenge: tx_UUID,
  origin: "some_payment_handler.com",
  // new fields added when invoked through payment handler:
  transaction: {
    amount: "$100.0",
    payee: "Fancy Chairs Incorporated"
  }
}

which would then be signed by a FIDO credential scoped to some_payment_handler.com. The browser would make sure that the information in the transaction piece of the object is displayed to the user before asking the authenticator to sign this object. Do I understand this correctly?

How is this different from letting the payment handler display the information, hash it into the challenge, and call good ol' fashioned webauthn? In that case, the clientData object might look like this:

{
  type: "webauthn.get",
  challenge: Hash(tx_UUID,"$100.00", "Fancy Chairs Incorporated"),
  origin: "some_payment_handler.com",
}

Again, this would then be signed by a FIDO credential scoped to some_payment_handler.com. Presumably, the payment handler trusts itself, so when it gets a signed FIDO assertion over such a clientData object, it knows that the transaction amount and payee information were shown to the user.

What attacks would this way of doing it permit that your proposal rules out? Are there other advantages of your proposal?

Thanks!

@adrianhopebailie
Copy link
Collaborator

Hi @balfanz ,

Thanks for the code snippet, it helps to get the conversation focused on the right interfaces.
I amended your first snippet slightly to align with what I am proposing:

{
  type: "webauthn.get",
  challenge: tx_UUID,
  origin: "some_payment_handler.com",
  // new fields added when invoked through payment handler:
  transaction: {
    amount: { 
        currency: "USD",
        value: "10000"
    },
    payee: "https://fancychairs.com"
  }
}

The values in the transaction come directly from the Payment Request object which already has a well-structured amount that should be easy for platforms to render in a locale specific way.

Also browser can only be certain of the origin of the requestor, not the company name so I'd only include that.

Question: The browser generates a random UUID as an identifier of the Payment Request object, could this be used for the tx_UUID above? i.e. It's not generated by the RP but is random and unique for each request...

To answer your question as to why I think the first case is better:

  1. No Payment Handler UI

In the first case the Payment Handler never needs to show any UI. This is a major plus for many potential RPs (such as banks) who don't want to have to maintain consumer facing UI for this. Hosting a small Javascript-based service worker that simply invokes WebAuthn and returns the result is a much lower barrier. It can be installed when the user logs into their Internet banking at the time they register their authenticator (for example).

  1. Consistent low-friction flow

If the merchant invoking the payment request API knows that there is a possibility of invoking WebAuthn but only if it will be very low friction (instant, with no additional UI) then they are more likely to adopt this flow.

We have heard that in most 3DS cases WebAuthn is only likely to be used for "step-up" authN i.e. after the merchant has submitted the tx to the network and the risk has been assessed to be too high to proceed without it. This is a terrible UX and the workaround basically amounts to fingerprinting to avoid step-up.

If this level of authN were possible at such low friction it is much more likely to be used pre-emptively instead of browser fingerprinting.

  1. Fewer dependencies

If the system validating the signed assertion needs to also trust the system that renders the UI to the user has not been compromised then that is one more attack vector to worry about. Web content can be manipulated (via rogue extensions, compromised browsers etc) but if I understand the way the FIDO client platform works this is much less likely to be compromised. I appreciate that this is a weaker argument than the others but I think is still worth considering

@equalsJeffH
Copy link
Contributor

equalsJeffH commented Apr 16, 2020

@adrianhopebailie asked

The browser generates a random UUID as an identifier of the Payment Request object, could this be used for the tx_UUID above? i.e. It's not generated by the RP but is random and unique for each request...

{
  type: "webauthn.get",
  challenge: tx_UUID,
  origin: "some_payment_handler.com",
  ...
}

yes, but the RP must additionally continue to supply a random challenge (essentially a nonce) as per the webauthn spec, though it can be hashed together with the tx_UUID (aka details.id) and then used as the CollectedClientData.challenge value.

Also, the verifier will need to receive the hashed-together challenge value (or the distinct values that were hashed to create it) in order to be able to verify the webauthn assertion's signature.

[ CollectedClientData is subsequently hashed and sent to the authenticator with other input params as a part of invoking the authenticatorGetAssertion operation ]

@adrianhopebailie
Copy link
Collaborator

Thanks @equalsJeffH

I was trying to avoid a server round-trip to the RP but it seems like that's unavoidable.

@danyao
Copy link

danyao commented Apr 22, 2020

I think we can realize the UX benefit of your proposal with the data model Dirk proposes. It will look something like this:

Payment handler registers with the browser that it wants to support integrated WebAuthn:

registration.paymentManager.enableIntegratedWebAuthn();

Merchant (fancychair.com) creates a PaymentRequest and checks if integrated WebAuthn is supported:

let request = new PaymentRequest(
  [{supported_methods: "https://some_payment_handler.com", data: phSpecificData}],
  {total: {label: "Total", amount: { currency: "USD", amount: "100.00"}}});

// Assumes there's an extension to canMakePayment for integrated WebAuthn
let canMakePayment = await request.canMakePayment();
if (canMakePayment.supportsIntegratedWebAuthn) {
  // Merchant can be sure that the low-friction integrated WebAuthn flow is shown.
  let response = await request.show();
} else {
  // Integrated WebAuthn not supported. Fallback to some other flow.
}

Browser responds to request.show() by launching the Minimal UI [1] instead of regular web-based payment handler flow. As part of this flow:

  • Browser shows the "100.00 USD" amount from the merchant.
  • When user touches the biometrics scanner, the browser process makes a WebAuthn request on behalf of the payment handler with the following clientData:
{
  type: "webauthn.get",
  challenge: Hash(tx_UUID,"$100.00", "https://fancychair.com"),
  origin: "some_payment_handler.com",
}
  • Browser forwards the returned credential to the payment handler via a new ongetcredential event.
  • Payment handler can verify the credential and return a confirmation to the merchant. Or it can directly return the credential back to the merchant which then sends it off to an acquirer to verify.

In this scenario, the RP ("some_payment_handler.com") trusts that the browser has displayed the correct amount and payer information when the user authorized the payment. And we don't need to modify FIDO clientData format.

@btidor-stripe
Copy link
Collaborator

Using Minimal UI for this flow sounds great, but I think Adrian's original proposal has an additional security property that's important.

The specific scenario is when an assertion is shared with parties other than the RP / payment handler (Looking at the "Use Cases" doc, this could be the issuing bank sharing the assertion with the merchant or vice versa, or a PSIP sharing the assertion with either the issuing bank or the merchant as in Dirk's other proposal re: enrollment.)

In the flow proposed here, the browser shows the transaction information in trusted chrome and creates the WebAuthn request itself (on behalf of the payment handler) by hashing the transaction information into the challenge. But if some_payment_handler.com misbehaves, it can construct a malicious WebAuthn request outside of the payment flow with a challenge that reflects a changed or invented amount and origin. By putting the transaction information into a separate field in clientData that can only be set by the browser, anyone looking at the assertion can verify that the information was shown to the user in browser chrome in a way that can't be spoofed by some_payment_handler.com.

Other than that, the proposal looks great!

@danyao
Copy link

danyao commented May 6, 2020

@btidor-stripe

By putting the transaction information into a separate field in clientData that can only be set by the browser, anyone looking at the assertion can verify that the information was shown to the user in browser chrome in a way that can't be spoofed by some_payment_handler.com.

This brings up an interesting question: should a legitimate RP (either a payment handler or merchange) be able to invoke a "payment authz flow" directly from their origin? "payment authz flow" is defined as an authenticator showing UI with transaction information.

My naive assumption was that this flow needs to be possible because some payment flows may not involve Payment Request (e.g. card-on-file). So I understood the original proposal as asking for an extension of clientData that any RP can use to pass transaction information to authenticators, and the browser is merely acting on behalf of an RP/payment handler in the minimal UI flow. Is the actual proposal to make "a separate field in clientData that can only be set by the browser"?

The specific scenario is when an assertion is shared with parties other than the RP / payment handler (Looking at the "Use Cases" doc, this could be the issuing bank sharing the assertion with the merchant or vice versa, or a PSIP sharing the assertion with either the issuing bank or the merchant as in Dirk's other proposal re: enrollment.)

Is the assumption here that the issuing bank does not trust the RP / payment handler to properly display the transaction information? Why not? If the issuing bank is coordinating with the PISP to do re:enrollment, it seems to me that a high level of trust already exists between issuing bank and PISP (which I assume is the RP / payment handler).

@yackermann
Copy link

  transaction: {
    amount: { 
        currency: "USD",
        value: "10000"
    },
    payee: "https://fancychairs.com"
    type: "transaction-payment" || "creditcard" || "token" || "crypto"
  }

Unless the premise of "transaction" field is just for payment

@btidor-stripe
Copy link
Collaborator

@danyao

This brings up an interesting question: should a legitimate RP (either a payment handler or merchange) be able to invoke a "payment authz flow" directly from their origin? "payment authz flow" is defined as an authenticator showing UI with transaction information.

I don't have a strong opinion here, but I would lean towards "no" -- if the "payment authz flow" can take advantage of its restricted interface to provide extra assurance, that would make it a more compelling feature than just a prebuilt UI. Though I would love @adrianhopebailie to weigh in.

Is the assumption here that the issuing bank does not trust the RP / payment handler to properly display the transaction information? Why not? If the issuing bank is coordinating with the PISP to do re:enrollment, it seems to me that a high level of trust already exists between issuing bank and PISP (which I assume is the RP / payment handler).

The idea is that the flow could address use cases similar to the ones in @balfanz's "3P Credential Creation" presentation. Generally speaking, issuing banks do not fully trust PISPs, so at enrollment the PISP will redirect the user to the issuing bank to confirm the user's identify and confirm the FIDO enrollment data presented by the PISP (shown in slide 12). At transaction time, issuing banks would similarly want to verify the signature on their own, and including assurance around the dynamic linking elements (amount, currency, origin) would make the system a good deal more attractive in that regard.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants