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

Support PassKey Integration (Web Authentication API) #1870

Closed
Ajedi32 opened this issue Apr 20, 2018 · 93 comments · Fixed by #8825
Closed

Support PassKey Integration (Web Authentication API) #1870

Ajedi32 opened this issue Apr 20, 2018 · 93 comments · Fixed by #8825

Comments

@Ajedi32
Copy link

Ajedi32 commented Apr 20, 2018

Recently, the W3C finalized their Candidate Recommendation for the Web Authentication Standard, a new browser feature that allows sites to request user authentication through a standardized API.

Implementation is currently underway in Chrome, Firefox, and Edge.

Once this standard starts being implemented by sites, I'd like to be able to use KeePassXC as an Authenticator for it, so that I can store my Web Authentication private keys in the same place my existing legacy passwords are stored, and keep them synchronized across all my devices.

While it might still be too early to begin working on a concrete implementation (available documentation for the Web Authentication API is still a little sparse, outside of the standards document itself), I figured it can't hurt to start looking into this now so we're prepared when the time comes.

Would integration with the Web Authentication API be a feature worth supporting?

@droidmonkey
Copy link
Member

Looks very promising!

@varjolintu
Copy link
Member

I will definitely keep an eye on this!

@francoisferrand
Copy link
Contributor

Web Authentication is supported in the latest Google Chrome release (67)

@varjolintu
Copy link
Member

So far I haven't found a way to use a software as an Authenticator, and I assume it will not be possible unless you have a idiot-proof way to emulate a USB device.

@Ajedi32
Copy link
Author

Ajedi32 commented Jun 8, 2018

It's possible official support for third-party software authenticators could be coming in the future (though I've heard no news about anything like that yet); but even in the absence of that couldn't you use a browser extension to directly intercept calls to the Web Authentication API and implement it that way?

Until we have some real-world examples of sites using the Web Authentication API as a password replacement, it's hard to say what the best way to approach the problem is.

@Ajedi32
Copy link
Author

Ajedi32 commented Mar 4, 2019

FYI, the Web Authentication standard is now officially a W3C recommendation: https://www.w3.org/TR/2019/REC-webauthn-1-20190304/

It's possible to implement an Authenticator via a browser extension by intercepting calls to the Web Authentication API and returning an appropriate response to the web page. Krypton is a good example of how this can be done: https://github.com/kryptco/kr-u2f/blob/192af059419501bb702148ae5301f042c9859447/src/inject_webauthn_chromium.ts (Note the linked code is not FOSS so we can't copy it directly, but it does provide a general idea of how this can be done.)

WebAuthn can be tested at https://webauthn.org/ or https://webauthn.io/

@droidmonkey
Copy link
Member

Cool, thank you for keeping on top of this

@rugk
Copy link

rugk commented Mar 5, 2019

But is not WebAuthn intended to be used as a 2FA method? Not a replacement for passwords…

And if you use WebAuthn as a password replacement, you cannot really use any 2FA anymore? Or what should one do, if one then wants to use a hardware key as 2FA WebAuthn e.g.?

@droidmonkey
Copy link
Member

droidmonkey commented Mar 5, 2019

WebAuthn is replacing human generated passwords with challenge response keys. These CAN be stored on hardware tokens, but certainly not necessary or desired in most cases. I for one hate carrying a token around with me.

@rugk
Copy link

rugk commented Mar 5, 2019

. I for one hate carrying a token around with me.

But you notice this is a security advantage?

So let's think again about 2FA: You want:

  • something you know
  • something you have
  • something you are

(all and/or connected, 2 factors for two-factor-authenticatiion obviously)

So "traditionally" passwords are the first (you know them) and things like a TOTP app (on a different mobile phone) or hardware tokens are "what you have".
With KeePassXC, it would only be "what you know"…

Respectively it would all be on one device (being one factor in the end). If that is compromised, all your logins are compromised. Without it, you have currently have to compromise two devices (KeePass DB and on one phone or even a hardware key), which is obviously more secure…

@droidmonkey
Copy link
Member

Security is all about choice. I did not say this is replacing 2FA or that it is stronger than 2FA. The vast majority of the world has no idea what 2FA is, nor do they care. I have lowered my personal risk on those accounts that I have deemed are "high value" by using true 2FA. For accounts that are not high value, I would very much like to have the convenience of using WebAuth without a hardware token.

You can make your own choices. That's the beautiful thing about KeePassXC!

@nagromc
Copy link

nagromc commented Mar 6, 2019

. I for one hate carrying a token around with me.

But you notice this is a security advantage?

So let's think again about 2FA: You want:

* something you know

* something you have

* something you are

(all and/or connected, 2 factors for two-factor-authenticatiion obviously)

So "traditionally" passwords are the first (you know them) and things like a TOTP app (on a different mobile phone) or hardware tokens are "what you have".
With KeePassXC, it would only be "what you know"…

Respectively it would all be on one device (being one factor in the end). If that is compromised, all your logins are compromised. Without it, you have currently have to compromise two devices (KeePass DB and on one phone or even a hardware key), which is obviously more secure…

It is understandable and I agree with you. But as @droidmonkey said, it's a trade-off between security vs. convenience. And it is perfectly assumed in the FAQ:

We believe that storing both together [passwords and TOTP secrets] can still be more secure than not using 2FA at all, but to maximize the security gain from using 2FA, you should always store TOTP secrets in a separate database, secured with a different password, possibly even on a different computer.

You have the choice to store your 2FA (TOTP or FIDO2/CTAP) secrets in the same database.

@phoerious
Copy link
Member

phoerious commented Mar 6, 2019

Nothing says you could not generate a WebAuthn response from more than one factor (I haven't read the spec, but it's quite flexible and generic from what I've heard).
As used today, 2FA is largely a mitigation of weak passwords and not of break ins into your machine. If your PC is taken over, then taking over your phone as well is usually rather easy (or vice versa). You only get the full advantage of 2FA if at least one of your tokens never comes in contact with the other (like e.g. a YubiKey or a dedicated hardware token generator that is hard to impossible to penetrate with malware).

@rugk
Copy link

rugk commented Mar 6, 2019

If your PC is taken over, then taking over your phone as well is usually rather easy (or vice versa).

I would totally challenge that assumption…


But yes, it can be totally useful as an optional feature. I totally think users can trade-off things there. (We have the same thing with TOTP tokens in KeePassXC.)

What I am just not sure about is, whether the WebAuthn spec actually intends this implementation? Does it take the thing into account (for security implications or so) or is the spec rather intended for 2FA?

@Victor239
Copy link

It's up to the website to support 2FA, I don't see any reason why WebAuthn can't be used as one factor. Similarly to how Google implements 2FA login it would just be WebAuthn on one page then TOTP or something else on the next.

@varjolintu
Copy link
Member

I already have a test branch that can intercept WebAuthn create/get on the test page. For now it doesn't do anything yet, but I'll try to improve things when I have some time. But it's certainly possible to do.

@droidmonkey droidmonkey removed this from the Future milestone Mar 24, 2019
@darkdragon-001
Copy link

What's the state of this?

@varjolintu
Copy link
Member

@darkdragon-001 Haven't had time to do it any further. Not at simple as it sounds.

@nm17
Copy link

nm17 commented Apr 24, 2022

Any news on this?

@varjolintu
Copy link
Member

@nm17 Yes. Sort of. I've tested some possibilities with this via the browser extension, but so far there are some security issues how to transfer certain messages from/to content scripts. At this point I haven't found a way to securely transfer some variables related to authentication without the possibility of other scripts capturing them as well.

@rugk
Copy link

rugk commented Apr 24, 2022

Would it make sense to elaborate on that i.e./respectively document that progress somewhere (so other scan help/pick it up), or ask some question on Stackoverflow or similar about the problem you are facing?

@varjolintu
Copy link
Member

varjolintu commented Apr 26, 2022

@rugk Sure, I can provide some details. If anyone have better ideas for the implementation, please let me know.

To get the Webauthn to work, browser extension has to add a separate script file to web_accesible_resources in manifest.json, and inject that script to every page. Yes. Without that there's no way you can override navigator.credentials with your own implementation (see details: https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API).
Injecting a script to every web page already gives me some shivers, but that's not all. If I want to send a message to the actual content script (and to KeePassXC from there) I'll have to use window.postMessage() and window.addEventListener('message', ...) for the response, just because the script is injected. Transferring messages directly to the background script seems impossible, and using window for messages means every other script/extension can also listen to them. Much secure.

@Ajedi32
Copy link
Author

Ajedi32 commented Apr 26, 2022

It sounds like what you're looking for is browser.runtime.sendMessage. The KeePassXC Browser extension already has a wrapper for that defined in its existing content script that already gets injected into every page, so you can probably just reuse that.

Also, it's probably worth noting that other scripts on the same page as the content script are necessarily same-origin with the domain you're authenticating against, so they can, by design, already get access to things like passwords (by reading the value attribute of a password input element), or perform authentication using public key credentials (by calling navigator.credentials.get themselves). There's very little that can be done to prevent that, nor is it really the responsibility of WebAuthn to do so. It's only really scripts on other origins you need to worry about, and that can be solved using the sendMessage API I mentioned above.

Obviously you don't want scripts on the page being able to read the private key, regardless of their origin. The solution to that I believe is to not send the private key to content scripts in the first place. Instead you basically want to run those private parts of the script in the extension's background page/worker and communicate with the content script via message passing (e.g. using the sendMessage API I just described).

@varjolintu
Copy link
Member

@Ajedi32 Thanks for your comment. I had problems passing messages using browser.runtime.sendMessage because browser namespace is not defined or available in the injected script. Otherwise it would be an ideal solution.

@Ajedi32
Copy link
Author

Ajedi32 commented Apr 26, 2022

Okay, I see the problem you're having.

Problem summary

Content scripts can't interfere with the runtime environment of other scripts on the page, which you need to do in this case since you need to override the WebAuthn API. So instead you're declaring the WebAuthn override script as a web-accessible resource and using a content script to manually inject that into the page.

The problem is that this injected script doesn't have access to browser.runtime.sendMessage; since only content scripts can access private extension APIs. You could theoretically fix that by declaring the page as externally connectable to allow it to use postMessage, except that since WebAuthn could potentially be used on any site you would have to do that for all pages, and declaring all pages as externally connectable is explicitly disallowed for some reason.

That leaves window.postMessage as the next most obvious way of communicating between the injected script and the extension, as explained here. The only problem with that is that other scripts could potentially be listening to those events, and the normal way of preventing that using the targetOrigin parameter is, again, explicitly not allowed in extensions for some reason.

Possible solutions

Solution 1: Use postMessage anyway

The good news is, after giving it some thought I'm not certain those limitations on targetOrigin are actually a deal breaker. Obviously leaking the contents of WebAuthn API calls to third-party origins would be bad, but I'm not sure that's actually possible as long as you're only calling postMessage on your own window. Indeed, the MDN docs warn not to use a target origin of * when you use postMessage to send data to other windows, but do not give that same warning for sending data to your own window. So assuming I'm not overlooking anything, using postMessage might still be viable.

One issue is that you wouldn't be able to send responses back from the extension with postMessage, since browsers apparently set the origin of such requests to null, making them impossible to verify. That's solvable though by sending a communication channel from the host page using the Channel Messaging API and communicating back via that method rather than via postMessage (or alternately, by authenticating the postMessage response using a shared secret sent in the request message).

Another other issue is that, as you pointed out, this method doesn't protect against other scripts on the same page intercepting messages to the WebAuth API. As I said though, I don't think that's actually a security issue, since those scripts should all be same-origin with the host page (unless I'm overlooking something) and so could already intercept communications with the WebAuthn API by doing exactly what we're doing (overriding the API with their own functions).

Solution 2: Custom Events

Another alternative solution is hinted at in the MDN docs about using postMessage in extensions, where it suggests using "custom events to communicate with content scripts", "with randomly generated event names, if needed, to prevent snooping from the guest page".

See the Events article on MDN for details on how that would work. Basically you'd create a CustomEvent with a randomly-generated name, attach event listeners for that event to some common DOM object that both the content script and injected WebAuthn script have access to, then send messages back and forth by triggering that event on the DOM object. I'm not sure if this is actually more secure than postMessage, but it does seem less likely to cause accidental interference between the extension and host page at least.

@pabs3
Copy link

pabs3 commented Aug 20, 2023

I have a slightly but hopefully not too offtopic question; currently WebAuthn cannot be used when JavaScript is disabled, have the KeePassXC folks thought about how to support WebAuthn in that scenario, or with websites that don't want to ship JavaScript?

w3c/webauthn#1255

@varjolintu
Copy link
Member

I have a slightly but hopefully not too offtopic question; currently WebAuthn cannot be used when JavaScript is disabled, have the KeePassXC folks thought about how to support WebAuthn in that scenario, or with websites that don't want to ship JavaScript?

w3c/webauthn#1255

I don't think that's possible, unless browsers itself add a feature where it's possible to pass WebAuthn calls directly outside the browser (e.g. using native messaging). So far our only solution for this is to use JavaScript for capturing the necessary API calls.

@varjolintu
Copy link
Member

varjolintu commented Aug 21, 2023

And with the latest changes Google's Passkey support also works with KeePassXC. And of course when trying the feature with Firefox, Google complains the browser is not compatible ;) Didn't try it yet with a different user agent though.

EDIT: Microsoft's site also works.

@FlyveHest
Copy link

Google now defaults to using passkeys when signing in.

https://blog.google/technology/safety-security/passkeys-default-google-accounts/

@user858753257
Copy link

When you publish the Passkeys support . Will be Linux also supported ?

@varjolintu
Copy link
Member

varjolintu commented Oct 25, 2023

When you publish the Passkeys support . Will be Linux also supported ?

Linux is of course supported, with all browsers using the browser extension.

@user858753257
Copy link

When you publish the Passkeys support . Will be Linux also supported ?

Linux is of course supported, with all browsers using the browser extension.

Perfect . Until now that's a missing point for me on Linux systems 🙏

When I read it right the kernel itself doesent have support

@Midou36O

This comment was marked as resolved.

@varjolintu
Copy link
Member

varjolintu commented Nov 1, 2023

Anyone who missed the messages in the PR, you can download the latest snapshot build (2023-10-26 or later) from https://snapshot.keepassxc.org/ and test the Passkey feature.

(Use a test database and/or have some decent backups, just in case)

@opotonniee
Copy link

Congrats for this new feature, very useful!

A few comments after testing the nov-5 snapshot:

  • After upgrading from 2.7.6, the execution failed with several error popups complaining about missing Qt5 DLLs. I uninstalled and reinstalled and then it worked fine.
  • Although it is supposed to be opaque, it is confusing that you call KPEX_PASSKEY_GENERATED_USER_ID the credential Id. The User ID is actually the user handle
  • It is a pity it does not support passkey autofill, is this in the plans?
  • Why does Add basic support for WebAuthn (Passkeys) #8825 state that it does not support resident keys? Do you mean it always creates resident key, whatever the request attributes?

@varjolintu
Copy link
Member

varjolintu commented Nov 6, 2023

Although it is supposed to be opaque, it is confusing that you call KPEX_PASSKEY_GENERATED_USER_ID the credential Id. The User ID is actually the user handle

Not sure what you mean here. Could you clarify this a bit?

It is a pity it does not support passkey autofill, is this in the plans?

Passkey should be supported as well. See #9987 for some small related changes.

Why does Add basic support for WebAuthn (Passkeys) #8825 state that it does not support resident keys? Do you mean it always creates resident key, whatever the request attributes?

We are always storing username, user ID etc. but not handling situations where requireResidentKey is set.

@opotonniee
Copy link

Although it is supposed to be opaque, it is confusing that you call KPEX_PASSKEY_GENERATED_USER_ID the credential Id. The User ID is actually the user handle

Not sure what you mean here. Could you clarify this a bit?

I mean the advanced attribute named KPEX_PASSKEY_GENERATED_USER_ID is actually storing the credential id, not the user id. This attribute name is misleading.

It is a pity it does not support passkey autofill, is this in the plans?

Passkey should be supported as well. See #9987 for some small related changes.

This PR is about importing passkeys, I don't see how it addresses autofill, which is about prompting the user to select an already registered passkeys when an authentication request does not contain the user name.

Why does Add basic support for WebAuthn (Passkeys) #8825 state that it does not support resident keys? Do you mean it always creates resident key, whatever the request attributes?

We are always storing username, user ID etc. but not handling situations where requireResidentKey is set.

ok, clear.

@varjolintu
Copy link
Member

I mean the advanced attribute named KPEX_PASSKEY_GENERATED_USER_ID is actually storing the credential id, not the user id. This attribute name is misleading.

Ah, you are meaning this one? https://w3c.github.io/webauthn/#ref-for-dom-credential-id

This PR is about importing passkeys, I don't see how it addresses autofill, which is about prompting the user to select an already registered passkeys when an authentication request does not contain the user name.

It includes a change where Relying Party is checked with the normal URL, exposing passkeys to the normal autofill. It's also filling the username defined in Passkey instead of the entry's username. The current snapshot version does not do this, except if the Passkey is stored to a new entry. If an authentication request doesn't contain any allowed credentials, all entries with user handle are returned to the autofill.

@taelfrinn
Copy link

taelfrinn commented Nov 7, 2023

We are always storing username, user ID etc. but not handling situations where requireResidentKey is set.

my understanding of non-resident keys is that they are not stored. they are generated on demand from information stored in the id which is why non-resident keys tend to have huge ids. (the credentialid contains some seed data, which can be combined with the RP and username to resynthesize the key. it might also have some kind of mac to prove it hasnt been tampered with). So the website has to present a list of "allowCredentials" in order for the fido device to re-create keys from.

While resident keys rely on local storage, and the RP does not provide any kind of keyids or userid to the user, and lets the client choose from any stored keys for the RP. Given that this implementation stores keys, full support for resident keys AKA passkeys should be possible. Full resident keys are what constitutes passkeys, while non-resident keys are just a 2fa method - their design being typically used after password login completes, as MFA to it.

Also, by extension, it should also be possible to generate real "non-resident" keys in which nothing is stored locally. Of course, given that the keepassvault does not have the extreme space limitations of hardware tokens, this is probably not worth any effort.

-=-=-

Separately, one prerequisite here that is not mentioned is that the master vault secret should be at least as secure as the keys it contains. (i.e. not chosen by the user, at least having an entropy of 128 bits) Given that passkeys will replace passwords, a weak and easily guessed user-chosen master vault passphrase should probably not be an easy option for users, and instead we should push/insist on/or default to a "correct horse battery staple"-like spec, such as bip39, for master vaults. (which have the property that a user cannot just pick them manually, but must synth them from entropy)

This is important here because passkeys generally dont need 2fa. Unlike a traditional keepass vault which can be weak user-chosen passwords protected by a weak user-chosen vault password, then "backed" by SMS or OTP 2fa, a database of passkeys will usually mean direct and full access.

External USB keys deal with this by being a separate tamper resistant device. A software alternative has to have at least a crypto-strong on-disk vault, imo, due to being exposed to all other software the user may run.

IMO, a software version of webauthn, whether resident passkeys or non-resident 2FA methods, should at a minimum require 128 bits of entropy for the vault.

@phoerious
Copy link
Member

phoerious commented Nov 8, 2023

Separately, one prerequisite here that is not mentioned is that the master vault secret should be at least as secure as the keys it contains. (i.e. not chosen by the user, at least having an entropy of 128 bits) Given that passkeys will replace passwords, a weak and easily guessed user-chosen master vault passphrase should probably not be an easy option for users, and instead we should push/insist on/or default to a "correct horse battery staple"-like spec,

That's incorrect. Your master password does not need to be as secure as the PassKey and enforcing such a thing will alleviate any benefit you get from it. KeePassXC transforms the Key with Argon2 for exactly that reason. This doesn't make up for very weak passwords, but you also don't need a super-random 128-bit string for reasonable security. Your password database is also offline on your machine, so an attacker needs access to the device. In any case, even a weak password is more secure than simply storing it in your browser without additional protection.

@ccoenen
Copy link

ccoenen commented Nov 8, 2023

I fully agree with @phoerious. If you do PassKey with a Security Token like a YubiKey, you also don't input a 128+ bit sequence via that one button. (and even if you unlocked a PassKey store on your phone with a fingerprint, there is (much) less than 128 bits of information in your fingerprint!)

@vvirtues
Copy link

vvirtues commented Nov 10, 2023

Just to note, Bitwarden has passkey support slowly rolling out. This should be very much doable.

@MrConorAE
Copy link

Hey there, just started using KeePass from 1Password and this looks super interesting. Is there any timeline on getting this to stable, or is it going to be in snapshots only for now?

@droidmonkey
Copy link
Member

No hard timeline right now, but I would think shortly after the new year.

@andycandy-de
Copy link

I am also waiting for this great feature 🤣
Thank you for your great work ♥️

@gwillen
Copy link

gwillen commented Dec 15, 2023

I'm trying to test this out in the snapshot. What I've found so far:

  • On Linux, it works great with webauthn.io (in both Firefox and Chrome), but Google refuses to generate a passkey ("can't be created on this device".) I don't know whether that's expected, or whether other password manager extensions are able to work around it.
  • On macOS, I'm unable to run the snapshot on my machine, because it seems to have a minimum version of macOS 13. I'm not sure whether that's intended, or an artifact of the snapshot build process being different from the release build process.

EDIT: However, your build process is unusually pleasant and smooth for a mac app, so I just built from source, and everything seems to work great! The UX is a little unintuitive when logging in with a passkey on google.com, but that seems unsurprising at this early stage.

@varjolintu
Copy link
Member

I'm trying to test this out in the snapshot. What I've found so far:

* On Linux, it works great with webauthn.io (in both Firefox and Chrome), but Google refuses to generate a passkey ("can't be created on this device".) I don't know whether that's expected, or whether other password manager extensions are able to work around it.

* On macOS, I'm unable to run the snapshot on my machine, because it seems to have a minimum version of macOS 13. I'm not sure whether that's intended, or an artifact of the snapshot build process being different from the release build process.

EDIT: However, your build process is unusually pleasant and smooth for a mac app, so I just built from source, and everything seems to work great! The UX is a little unintuitive when logging in with a passkey on google.com, but that seems unsurprising at this early stage.

With Google, do you mean with Firefox? I was unable to create a Passkey for Google with Firefox. It's probably a user-agent related restriction. Any Chromium-based browser works just fine.

About the UX, maybe we shouldn't ask a separate confirmation for Passkey entries because those are confirmed anyway in a separate dialog.

@kwin
Copy link

kwin commented Dec 15, 2023

Firefox has a bug with Google passkeys: https://bugzilla.mozilla.org/show_bug.cgi?id=1865625.

@gwillen
Copy link

gwillen commented Dec 15, 2023

It looks like that Firefox bug is specifically for macOS -- on my macOS machine, I only tried Edge (and it worked). My failures were both on Linux, where it failed in both Chrome and Firefox. (My vague understanding is that this is expected on Linux.)

For the UX issues: creating a Passkey was fine as I recall, but when logging in with a Passkey I get the following sequence of events (some of which are probably Google's fault):

  • (Assuming the database is already unlocked:)
  • I get a "Browser Access Request" and I select a passkey and hit "Allow Selected".
  • This has no visible effect on the page; the email field is still blank. (Clicking the keepassxc icon in the field then populates it. This interaction seems like it shouldn't be necessary given the previous one.)
  • I click "next", and google gives me a password field.
    • This ... seems like google's fault? I don't know why it's not automatically asking for passkeys, since the account is set up for passkeys. But it could also be that it's looking for something in the environment that it's not finding?
    • There is a keepassx icon in the password box, but it's a red herring; I don't have a password saved for this account so it doesn't do anything.
  • I click "Try another way" -> "Use your passkey".
  • Then I get the authentication prompt and everything works from there.

(This is all when doing a first-time login on a new device, simulated using incognito mode. If I log out and go to log back in, everything is seamless -- it gives a list of logged-out accounts, I click the one I want, it gives a dialog mumbling about using a passkey, I click next, the authentication happens.)

@foss-
Copy link

foss- commented Dec 15, 2023

Firefox has a bug with Google passkeys: https://bugzilla.mozilla.org/show_bug.cgi?id=1865625.

Note that this is fixed already and fix is available in v122 (currently nightly branch). So you could install Firefox nighty to test on macOS.

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

Successfully merging a pull request may close this issue.