kanidm / webauthn-rs

An implementation of webauthn components for Rustlang servers
Mozilla Public License 2.0
485 stars 80 forks source link

Can't use Passkeys registered on devices with preferred authorization on devices without preferred authorization #281

Closed Syfaro closed 1 year ago

Syfaro commented 1 year ago

Hi there, I'm running into an issue with using Passkeys between devices. Here's a quick way to reproduce it:

It seems like it's caused by this logic: https://github.com/kanidm/webauthn-rs/blob/09ff14cec763863cbe2122a48fa72e36efc24e09/webauthn-rs-core/src/core.rs#L757-L764

As far as I can tell, this causes the passkey to get "upgraded" into a state where it can only be used with user verification. However, it seems to be difficult to work around this behavior.

My current solution is enabling the "danger-credential-internals" feature, checking if the passkey Credential has user_verified set to true, and if so, setting the user verification of the RequestChallengeResponse to Required. Does this make sense, is there anything else I should be doing instead?

Thanks!

Firstyear commented 1 year ago

The reason this feature exists, is that without it uv=preferred can be arbitrarily removed and lowered to discouraged stripping uv from requests. Given that passkeys are meant to be a self contained MFA, the ability to strip UV is a significant security breach. This stripping can be done by js injection or userside scripting, allowing uv bypass since the content of the request to navigator.credentials.get is not hashed or reflected into the outputs the RP receives. This has been noted as CVE's in nextcloud, as well as receiving bugbounties/fixes from okta, azure and others. The reason why uv preferred is used at all over uv required is firefox, which has been lagging behind significantly in webauthn browser support for a long time. Were the situation different, our passkey wrapper would set uv=required.

In fact, this uv protection behaviour was created by this library and it was argued for a long time in the webauthn wg, until eventually consensus has reached that we should change the spec to guide implementations to do the same and protect and track uv states and apply that consistently ( https://github.com/w3c/webauthn/pull/1774 )

With this in mind, what you have described here is not a flaw in this library, but a security flaw in Apple's passkey implementation. This library is just exposing it - it's not the first time that our very strict adherence to secure and correct behaviour has exposed issues in major platforms either.

However, we are unable to report this issue on your behalf - I wish I could! Apple's webauthn team are very closed off, and we have no consistent way to report issues. The best bet will be either through https://security.apple.com/ or through feedback assistant. In the feedback assistant case especially since you are the one in possession of the hardware, you can complete the report (since feedback assistant wants a hardware details upload, and mine is an m1 macbook pro that is not affected.) Of course, I'm happy to advise as much as possible, or be included in emails too.

The only outstanding questions I have is what happens if you do a request with UV required to the macstudio? When you create a credential with preferred / required on the macstudio, what are the UV states? What keyboard are you using, does it have touchid at all?

I'm really sorry that this may not have been the answer you were looking for - on the other hand, well done finding a security flaw in Apple's passkey implementation!

--

If I was writing the report to Apple I think I'd draft text like:

User Verification bypass in iCloud passkeys.

When a credential is created on an iPhone and the relying party (RP) requests user verification (UV) preferred, the iPhone is able to satisfy this with TouchID/FaceID. This establishes to the RP and user that the credential in question is capable of self contained multifactor authentication (MFCDA, per nist 800-63b 5.1.9). When this credential is then synchronised to a Mac Studio with [List type of keyboard here, if it includes a touch id reader or not], then UV is silently discarded. In addition the user's machine password [IS or IS NOT] prompted for. The UV bit is then not set in attested credential data (ACD Webauthn L3 6.5.1 https://www.w3.org/TR/webauthn-3/#sctn-attested-credential-data ).

As a result, this creates a situation where a passkey that previously required local device authentication and UV, is able to bypass that requirement and use the credential without UV being set. This would constitute a UV bypass, and removes the guarantees of the authenticator from being MFCDA into a single factor cryptographic device authenticator.

The macstudio should prompt for and provide a source of UV, and then indicate this in the ACD flags to ensure that all passkeys in an iCloud account are consistent in their enforcement of UV and remains a MFCDA.

Syfaro commented 1 year ago

First off, thank you so much for the incredibly comprehensive response! I'm learning a lot here. I now see why this felt like really weird behavior, because it is! I'm developing some hobbyist software and browser compatibility isn't important, so I can set user verification to required to avoid this whole issue for now.

Here's the result of all of those situations. I'm using a Mac Studio without any kind of Touch ID capable keyboard.

Created on iPhone with AuthenticatorSelectionCriteria.user_verification set to Required:

Created on Mac Studio with the default values (should be user verification preferred):

Created on the Mac Studio with user verification set to required:

In case it's relevant, all of these tests were done in Safari and I'm running macOS 13.2.1, my iPhone is on iOS 16.4, and my iPad which exhibits the same behavior as the iPhone is on 16.3.1.

Another data point, created on Mac Studio in Chrome with default preferred or required:

I'll go ahead and close this issue as it does appear to be caused by something else, but let me know if there's anything else I can do!

Firstyear commented 1 year ago

First off, thank you so much for the incredibly comprehensive response! I'm learning a lot here. I now see why this felt like really weird behavior, because it is! I'm developing some hobbyist software and browser compatibility isn't important, so I can set user verification to required to avoid this whole issue for now.

Sounds like a plan! I'm glad that I was able to help.

Here's the result of all of those situations. I'm using a Mac Studio without any kind of Touch ID capable keyboard.

That's possibly part of the issue is my guess.

Created on iPhone with AuthenticatorSelectionCriteria.user_verification set to Required:

  • iPhone prompts for Face ID.
  • The Passkey from finish_passkey_registration has registration_policy set to Preferred, user_verified is true.

    • Does this represent something weird happening? If userVerification is set to required, should the registration policy also be required? Is there something I can do to debug this further?

Yeah, this does seem weird. i'm going to assume you set this in the call to generate_challenge_register_options()? Or did you alter this in the request that was given to navigator.credentials.get? Inside the library the policy is passed down directly from register_credential_internal into Credential::new, so it should be an exact reflection of what was request from the generate_challenge_register_options.

Created on Mac Studio with the default values (should be user verification preferred):

  • Mac Studio does no user verification prompt, just a Continue button.
  • The Passkey has registration_policy set to Preferred, user_verified is false.
  • No user verification is prompted and can successfully authorize with the Continue button.

Well at least it's consistently wrong 🤣

Created on the Mac Studio with user verification set to required:

  • Mac Studio prompts for password.
  • Same as with the iPhone, registration policy is set to preferred and user verified is true.
  • Attempting to use the passkey for future authorizations results in the same failure as the passkey created on the iPhone.

It does sound like there is an odd policy pass through issue here though. I'll have a look today. Are you doing this with your own site or our example?

But also good to know that if you set required, the UV can be asserted via the password prompt. The question is why apple doesn't do this with UV=preferred since it weakens the security of the passkey as a whole.

  • Mac Studio asks for user verification for both registration and authentication via Apple Watch or password.
  • Passkey has registration_policy always set to preferred, user verified is always true.
  • Authentication works as expected.

Yep, chrome doesn't use the normal icloud passkeys which is why it probably "does the right thing".

I'll go ahead and close this issue as it does appear to be caused by something else, but let me know if there's anything else I can do!

As mentioned, it would be great to report this as a security issue to apple :)

Firstyear commented 1 year ago

Followup - Just tested and I can't reproduce the policy pass through quirk you are seeing, I'm seeing the uv policy pass through correctly to the credential. So I would like to know more about how you are setting or modifying this value.

Syfaro commented 1 year ago

i'm going to assume you set this in the call to generate_challenge_register_options()? Or did you alter this in the request that was given to navigator.credentials.get? Inside the library the policy is passed down directly from register_credential_internal into Credential::new, so it should be an exact reflection of what was request from the generate_challenge_register_options.

Ah, I think this was a case of me using it wrong then. It looks like there's no way to set it to be required through the public API of the webauthn-rs crate, generate_challenge_register_options requires having access to the WebauthnCore which isn't public and using the core crate directly for everything is a little too scary! I had been updating the authenticator selection criteria of the creation challenge response which I now see is only changing the part that goes to the client, and the registration state of the passkey registration isn't public so I can't modify it the same way.

As mentioned, it would be great to report this as a security issue to apple :)

I'll have to give that a try.

Firstyear commented 1 year ago

Ahh yep, that would do it! Thanks for explaining :)

Good luck with webauthn-rs in your project, if you have any other questions or feedback please let us know!

Firstyear commented 1 year ago

@Syfaro Hi there, thanks to a conversation with another project contributor we were able to reproduce this on a macbook pro. I'll go ahead and report it to apple as well :)

Syfaro commented 1 year ago

Oh interesting, was it exhibiting the behavior when in clamshell mode?

I'd be curious if you can reproduce another issue I was running into, when using autofill it seems to ignore required user verification.

Firstyear commented 1 year ago

Do you mean conditional mediation for your autofill?

Syfaro commented 1 year ago

Yup, I do mean conditional mediation.

Firstyear commented 1 year ago

Okay, can I get some more details? When you say required user verification do you mean that you have set uv=required in the library code? Or is it still set to preferred? In addition this is still with the macstudio and the touchid-less keyboard?

Syfaro commented 1 year ago

I'm setting user verification required in the library code which I can see is being passed to the client, and yes this is still on the Mac Studio with no Touch ID capable keyboard. When I select the passkey it immediately submits without performing user verification. It works as expected on a MacBook with Touch ID available.

Firstyear commented 1 year ago

I just tried with UV required with the macbook pro in clamshell mode and no touchID, and in both cases with conditional UI I was prompted for password to register/authenticate. So either it's something different in your setup conditions, or it is something macstudio unique? What site are you using for the conditional UI test?

Syfaro commented 1 year ago

I've tried on my own site and the tester here: https://webauthn.firstyear.id.au/condui_test

I get a couple of errors on the tester site (AttestationNotVerifiable when trying to register, InvalidUserUniqueId when trying to authenticate), but you can see I'm not prompted for anything when clicking the passkey from the autofill entry.

2023-02-21 19 20 35

Firstyear commented 1 year ago

Yeah, those errors aren't a problem in this context - the passkey is still created and them attempts to be used. I think that's an older version of the tester though, I'll update it shortly.

Saying this, you're absolutely correct, there certainly does appear to be some kind of issue present as you are identifying. I'm just not sure how to reproduce it myself at this point.

In general, this is why we have hidden conditional ui prompts behind flags in the library - every browser has scuffed their implementation in some way, the worst being one browser that outright crashes the whole process when you access a conditional ui prompt. At this point I'm still not convinced it's a good user experience yet given all the complexity to checks notes auto complete a username (especially when autofill/complete already exists ....).

kahnclusions commented 1 month ago

Sorry to keep commenting on old issues! 😅 Has the behaviour of Safari changed since this discussion? I'm testing on my MacBook Pro (M1, but without TouchID setup), using the tide server in tutorial, and despite having "userVerification": "required" Safari is not presenting me with a password input when doing the authentication... on the original registration yes, but on authentication no. Any ideas?

Firefox and Chrome both do the right thing: password input is forced every time. Safari doesn't 😢