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

Should/could isSecurePaymentConfirmationAvailable return an enum rather than a boolean? #284

Open
stephenmcgruer opened this issue Feb 24, 2025 · 6 comments

Comments

@stephenmcgruer
Copy link
Collaborator

Currently, isSecurePaymentConfirmationAvailable returns a Promise<boolean>, indicating "if the Secure Payment Confirmation feature is available, or false otherwise".

We have been recently implementing this feature in Chrome, and during implementation found that it may be useful to know more than just true or false, but also some details on why SPC isn't available. For us during implementation this would have helped us debug code bugs, but for web developers we could imagine being able to understand + explain to users why SPC isn't available to them on the current device, or even just to record statistics across their users.

This might also help address reasonable concerns that were raised on the issue relating to being clearer in the spec as to how this API should operate.

At the same time, we should be aware of potential privacy risks here, so will need to evaluate the potential for fingerprinting.

@stephenmcgruer
Copy link
Collaborator Author

stephenmcgruer commented Feb 24, 2025

Using the current Chrome implementation as a guide, when isSecurePaymentConfirmationAvailable is called, we check:

  1. If the SecurePaymentConfirmation feature is not enabled, return false (payment_request.cc:836)

    • This feature is technically controllable by users via a command-line flag, but for most users it corresponds to what device they are on. It is enabled for Mac, Windows, and Android builds of Chromium, and disabled otherwise (content_features.cc:991)
  2. If the "payment" Permission Policy is not enabled on the current frame, return false (payment_request.cc:842)

  3. If the SecurePaymentConfirmationDebug feature is enabled, return true (secure_payment_confirmation_service.cc:64).

    • This feature is technically controllable by users via a command-line flag or chrome://flags flag, but should really only be set by browser or website develops that are testing SPC on devices which do not have an authenticator normally. (The feature allows SPC to function without a platform authenticator available).
  4. If there is no authenticator_, return false (secure_payment_confirmation_service.cc:70).

    • This should not be hit for any platform which came through checks 1-3, so is not really relevant (it's mostly a catch-all in case of problems).
  5. If the SecurePaymentConfirmationUseCredentialStoreAPIs feature is enabled, but the authenticator controller in the browser reports that GetMatchingCredentialIds is unsupported (secure_payment_confirmation_service.cc:75)

    • This feature causes Chrome to rely on authenticator APIs to determine whether a credential is available or not and if it has the third-party payment bit or not.
    • It is technically controllable by users via a command-line flag, but for most users it corresponds to what device they are on. It is currently enabled for Android and disabled otherwise.
    • For Android, GetMatchingCredentialIds is supported for any GMS Core (Play Services Library) version equal to or above 223300000. In practice, this is always true (GMS Core is wayyy above that version now).
  6. Finally, if we haven't early returned above, we call into and return the value of WebAuthn's isUserVerifyingPlatformAuthenticatorAvailable (secure_payment_confirmation_service.cc:84).

@stephenmcgruer
Copy link
Collaborator Author

So if we were to sketch out a set of enums (bikeshedding needed):

enum IsSecurePaymentConfirmationAvailableResult {
  'available',
  'unavailable-unknown-reason',
  'unavailable-feature-not-enabled',
  'unavailable-no-permission-policy',
  'unavailable-no-user-verifying-platform-authenticator',
}

Where they map to (in the above):

  • Step 1 fails => return 'unavailable-feature-not-enabled'
  • Step 2 fails => return 'unavailable-no-permission-policy'
  • (Step 3 is a return-true only case, would return 'available')
  • Step 4 fails => return 'unavailable-unknown-reason'
  • Step 5 fails => return 'unavailable-unknown-reason'
  • Step 6 fails => return 'unavailable-no-user-verifying-platform-authenticator'
  • (If Step 6 succeeds, return 'available')

Would these present a fingerprinting risk? Let's examine:

unavailable-feature-not-enabled - Arguable, but probably fine.

If a user has flipped no flags, nothing is leaked on Chrome today, as it's equivalent to an OS check (which can be done silently via user agent). However, for users that have flipped flags, and for another user agent, it might technically leak something to allow this silently. One can check for it today by calling show() (step 18 of https://w3c.github.io/payment-request/#show-method):

await request.show();
=> NotSupportedError: The payment method "secure-payment-confirmation" is not supported.

But this isn't silent, because if SPC is available then you show something at this point.

However, given all the other states are already silently detectable and that canMakePayment already does what isSecurePaymentConfirmation does today, this seems ok. (It'll have to go via our internal privacy review of course :)).

unavailable-no-permission-policy - nothing (additionally) leaked, one can trivially + silently check for the permission policy today, because you cannot construct a Payment Request object without it.

try {
    const x = new PaymentRequest([], null);
    console.log('Permission policy is present');
} catch (e) {
    // Technically you need to check the actual `e.message`, but this suffices for the example.
    if (e.name === 'SecurityError') {
        console.log('Payment Request not allowed due to permission policy');
    } else {
        throw e;
    }
}
VM324:5 Payment Request not allowed due to permission policy

unavailable-no-user-verifying-platform-authenticator - nothing (additionally) leaked, one can trivially + silently check this today by just calling the WebAuthn `isUserVerifyingPlatformAuthenticatiorAvailable' anyway.


TL;DR - I think we should be fine to do this set of enums, modulo a minor concern on the unavailable-feature-not-enabled case.

@stephenmcgruer
Copy link
Collaborator Author

cc @rsolomakhin @pejic - I would appreciate your eyes on the above, to see if you agree/disagree with me on this being privacy-safe (or reasonably so :)).

@rsolomakhin
Copy link
Collaborator

Thank you for writing this up, @stephenmcgruer! I think that this is reasonably privacy preserving. If @pejic is in agreement, then we may want to run this Chrome's Privacy Working Group.

@pejic
Copy link

pejic commented Feb 26, 2025

Thanks @stephenmcgruer. I think this sufficiently privacy preserving.

None-the-less would user agents be free to choose to return 'unavailable-unknown-reason' if they decided that specific error codes should not be returned?

Will clients that check only for 'available' be future proof? (I.e. updates the the standard won't introduce another available code.)

@stephenmcgruer
Copy link
Collaborator Author

None-the-less would user agents be free to choose to return 'unavailable-unknown-reason' if they decided that specific error codes should not be returned?

Probably... I'm a little undecided there between "optional user agent behavior allows them to protect against privacy concerns" and "optional user agent behavior causes inconsistent and hard to understand behavior across implementations" :). But since its only for 'non-available' cases, maybe it's fine.

Will clients that check only for 'available' be future proof? (I.e. updates the the standard won't introduce another available code.)

Yes; technically we should always consider backwards compatibility in any future update of a specification (and adding another form of 'available' would be immediately non-backwards compatible, I would argue), but we can also mention that explicitly in spec text.

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

3 participants