Open balfanz opened 4 years ago
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:
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).
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.
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
@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
(akadetails.id
) and then used as theCollectedClientData.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 ]
Thanks @equalsJeffH
I was trying to avoid a server round-trip to the RP but it seems like that's unavoidable.
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:
clientData
:
{
type: "webauthn.get",
challenge: Hash(tx_UUID,"$100.00", "https://fancychair.com"),
origin: "some_payment_handler.com",
}
ongetcredential
event.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.
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!
@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 bysome_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).
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
@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.
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:
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:
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!