keepassxreboot / keepassxc

KeePassXC is a cross-platform community-driven port of the Windows application “Keepass Password Safe”.
https://keepassxc.org/
Other
20.74k stars 1.44k forks source link

Add support for hmac-secret FIDO2 extension #3560

Open prusnak opened 4 years ago

prusnak commented 4 years ago

FIDO2 standard (the superset of the older U2F standard) includes hmac-secret extension introducing behaviour similar to older Yubikey HMAC-SHA1 Challenge-Response merged in https://github.com/keepassxreboot/keepassxc/pull/127 but much more universal as FIDO2 is an open standard.

We could try to expand the current Challenge-Response implementation in KeePassXC to cover this too (using libfido2) which will make KeePassXC compatible with any FIDO2 dongle.

This will satisfy https://github.com/keepassxreboot/keepassxc/issues/3450 and any other vendor-specific requests in the future.

flocke commented 4 years ago

I would love that, I have a Solo that supports HMAC-SHA1 and I would love to be able to use it to unlock KeePassXC. If you need a tester at some point just ping me here, I would be more than happy to help.

janis91 commented 4 years ago

Do you think about adding this in terms of a second factor or "passwordless"?

prusnak commented 4 years ago

With Trezor both use cases are possible, because the device contains touchscreen where you can enter your PIN (which is the second factor, the other one is having the device).

But I think starting with 2factor might be a good start.

qertoip commented 4 years ago

It would be really cool to have this.

Currently Trezor cannot be used with KeePassXC, which is unfortunate, as it is the most secure 2FA hardware key on the market, simply because it was originally designed for storing huge amounts of Bitcoin. In practice this comes down to super reliable backups and secure PIN with exponential escape and keypad scrambling.

droidmonkey commented 4 years ago

Just like OnlyKey, Trezor is more than welcome to use the new Yubikey API that allow us to specify a vendor hardware id. @prusnak please let me know if you need more information on this.

prusnak commented 4 years ago

@droidmonkey Yes, please give me more info on this - point me to the new Yubikey API. Is this using libfido2?

droidmonkey commented 4 years ago

No it's hmac-sha1. Take a look at Yubikey.cpp init function

prusnak commented 4 years ago

@droidmonkey I know about this, of course, but this is not relevant to this issue. The old hmac-sha1 method by Yubikey is obsoleted by FIDO2 hmac-secret extension and that's what new tokens will use. This issue is about implementing this via libfido2. Trezor will not use the obsolete and proprietary Yubikey API.

droidmonkey commented 4 years ago

Ah I see, I was confused by the conversation above a little. We do not have plans (yet) to implement libfido2.

banym commented 4 years ago

I am interessed in FIDO U2F / FIDO2 support for the Nitrokey FIDO2 and would add $150 as a bounty if it would be implemented.

I would love to use it as a second factor if possible. It would be nice be able to add multiple keys to one database for backup reasons.

banym commented 4 years ago

What do I need to do to make the bounty added to the topic of this like on the other issues with bounty?

riedel commented 4 years ago

Here is a implementation for LUKS using libfido: https://github.com/mjec/fido2-hmac-secret/blob/fd19bf40a94f177818bce5d2c50cc5eb57136478/src/authenticator.c#L315 that could be build upon

riedel commented 4 years ago

I actually managed to do a very dirty quick POC : d63e7371120b7ec203e356d2ceb8b70fdc9d982e . It seems only diligence work if someone wants to grab that bounty ;)

To get it to work you first need to create a resident hmac token with the hostname 'keepassxc.org' See https://developers.yubico.com/libfido2/Manuals/fido2-cred.html

I doubt that I fully understood how the whole thing is supposed to work though ;)

riedel commented 4 years ago

Just one more comment: I guess it makes sense to support multiple drivers by passing not only the slot, but also the driver to the challenge function and add an intermediate proxy class.

Currently only one device with multiple slots is supported by design (getSerial, getVendor), that IMHO could be fixed in any case.

Currently credential management is only supported by Yubikey, so on my Solo Key I also cannot search any slots but only define the assertion to hopefully match.

I haven't quite got if you need the resident key. I am happy now with the challenge that I got something mysteriously working.

As this is a security product, I would ask someone with much better C++ / QT skills take on the task though :)

riedel commented 4 years ago

Actually on my solo key now the credman extension is working (FIDO_2_1_PRE) . So listing the rk (slots) would be possible. However this might require a PIN, which would probably break the UX a bit.

My1 commented 4 years ago

With Trezor both use cases are possible, because the device contains touchscreen where you can enter your PIN (which is the second factor, the other one is having the device).

But I think starting with 2factor might be a good start.

even normally when you enter the PIN on the computer instead of the fido device you have 2 factors, although it's not perfect security-wise

To get it to work you first need to create a resident hmac token with the hostname 'keepassxc.org' See https://developers.yubico.com/libfido2/Manuals/fido2-cred.html

resident credentials only? if yes then sad.

couldnt keepass just keep the credentialID in the db file but not encrypted so it can be used to poll a "normal" credential?

Actually on my solo key now the credman extension is working (FIDO_2_1_PRE) . So listing the rk (slots) would be possible. However this might require a PIN, which would probably break the UX a bit.

you can iirc poll RKs for a specific RP without having the credential management. like I have had a few devices which dont have credential management but when getting asked to log in they just listed all RKs nicely. (it did require PIN though as browsers as far as I am aware always force PIN for RK stuff)

riedel commented 4 years ago

resident credentials only? if yes then sad.

couldnt keepass just keep the credentialID in the db file but not encrypted so it can be used to poll a "normal" credential?

don't rks make sense in the case of HMAC? Why is is this sad? You do not need a credentialID if its not resident don't you? I can try if it generates a HMAC also from the builtin key. It might however be a security risk, because some other "service" might just do the same and then your secret is exposed. IMHO this breaks security a bit.

you can iirc poll RKs for a specific RP without having the credential management. like I have had a >few devices which dont have credential management but when getting asked to log in they just >listed all RKs nicely. (it did require PIN though as browsers as far as I am aware always force PIN for >RK stuff)

Yes you can simply do try and error with assertions, so that is polling. So you suggest to simply have also 2 "slots" and poll them like for yubikey. Or poll the maximum (in my case) of 50 ?

My1 commented 4 years ago

don't rks make sense in the case of HMAC? Why is is this sad? You do not need a credentialID if its not resident don't you? I can try if it generates a HMAC also from the builtin key. It might however be a security risk, because some other "service" might just do the same and then your secret is exposed. IMHO this breaks security a bit.

isnt hmac same as with normal credential based on the keys it generates instead of the master secret? would be pretty bonkers if not.

also big problem is that 1) 90% of all FIDO2 devices I know of have no credential management so grilling RKs is kinda impossible without resetting the whole device which is just crazy 2) the credential management extension is still not fully released (the Fido2.1 pointer on options isnt called FIDO2_1_PRE without reason.

also considering the fact that there is a "salt" as the CTAP2 spec calls it needed, which obviously would be random on database creation, it would be the same as with file based secrets that there certainly is a small chance you can just hit the same, but probably negligible

Yes you can simply do try and error with assertions, so that is polling. So you suggest to simply have also 2 "slots" and poll them like for yubikey. Or poll the maximum (in my case) of 50 ?

image

no random try and error involved, the user just enters PIN and gets a list and selects the one needed.

riedel commented 4 years ago

isnt hmac same as with normal credential based on the keys it generates instead of the master secret? would be pretty bonkers if not.

This is (ab-)using the HMAC as symmetric key so things are a bit more difficult I guess. "Normal" credential work in an asymmetric way so there is no risk in replaying.

I don't have much time to experiment at the moment. I tested the interface with the commandline libfido2 tools, so if you can also easily try the workflow there without programming.

Yes you can simply do try and error with assertions, so that is polling. So you suggest to simply have also 2 "slots" and poll them like for yubikey. Or poll the maximum (in my case) of 50 ?

image

no random try and error involved, the user just enters PIN and gets a list and selects the one needed.

Can you explain the screenshot and may make a reference to the FIDO2 CTAP spec? A command line version would also help!

My1 commented 4 years ago

sure. in chrome and Windows 10 implementations (which are the most widespread webauthn implementations I have seen so far) when multiple RKs exist for any RP (relying party, generally meaning domain of a website in webauthn contexts) after entering the pin is spawns a selection of the Resident Keys for that RP.

linking to CTAP2.0 for obvious reasons here: https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetAssertion

step 9 and 10 say:

If more than one credential was located in step 1 and allowList is present and not empty, select any applicable credential and proceed to step 12. Otherwise, order the credentials by the time when they were created in reverse order. The first credential is the most recent credential that was created.

If authenticator does not have a display:

Remember the authenticatorGetAssertion parameters.

Create a credential counter(credentialCounter) and set it 1. This counter signifies how many credentials are sent to the platform by the authenticator.

Start a timer. This is used during authenticatorGetNextAssertion command. This step is optional if transport is done over NFC.

Update the response to include the first credential’s publicKeyCredentialUserEntity information and numberOfCredentials. User identifiable information (name, DisplayName, icon) inside publicKeyCredentialUserEntity MUST not be returned if user verification is not done by the authenticator.

basically you can use this and authenticatorGetNextAssertion to pull all the assertions for all RKs concerining that RP and go, in web contexts you have to select one to actually use and send, a local software could theoretically try them all but obviously that is not efficient.

obviously no matter whether an RK is used or not, if you keep the credentialID around, you dont have to do any ambiguity steps

prusnak commented 4 years ago

@My1 You seem to be very knowledgeable in FIDO subject. Are you also a programmer? Maybe you can pick up what @riedel has done and develop it further. I'd gladly provide assistance with the testing if needed.

My1 commented 4 years ago

I am a big fan of fido stuff and I am more using it in context of webauthn and just gained experience by also testing quite a few fido devices both U2F and Fido2, I have played around with U2F for about 4 years already.

probably cant help with programming though as I have borderline no experience in desktop programming, I mostly do webstuff in PHP, CSS and HTML, although I could give a look over the code and hack though a bit

the part about how this stuff works just now was not something I knew prior to the last response, I just gave the spec a quick read at the places where I would think they would have to be, lol.

My1 commented 4 years ago

the biggest piece of fun though will be multi-OS support as W10 >= 1903 does Fido stuff from the OS side, but all others do not do this so keepass needs to run all the fido requests by itself on all other OSes and needs to either a) pipe through windows b) run as admin (not desirable usually) on later w10

prusnak commented 4 years ago

the biggest piece of fun though will be multi-OS support

https://github.com/Yubico/libfido2 should provide sufficient abstraction so applications such as keepassxc do not have to deal with this

My1 commented 4 years ago

the biggest piece of fun though will be multi-OS support

https://github.com/Yubico/libfido2 should provide sufficient abstraction so applications such as keepassxc do not have to deal with this

except you do have to do for the specific point I noted. as libfido2's readme states:

On Windows 1903 and newer versions, access to FIDO devices has been restricted to applications using the operating system’s native API. Use of libfido2 is still possible in privileged applications.

prusnak commented 4 years ago

I am pretty confident libfido2 will become a wrapper for the operating system’s native API on Windows (same as libhidapi which just wraps OS native API for HID on Windows). https://github.com/Yubico/libfido2/issues/153

My1 commented 4 years ago

that would be nice indeed

My1 commented 4 years ago

okay I think the issue sounds like it isnt likely to be done any soon, considering the thing the reply links to

bugzy commented 4 years ago

@riedel Have you taken another look at this? I went through your proof of concept code and tried to extract the changes into their own files classes and functions (See here). I was hoping to be able to get something working by doing a search and replace of sorts, but with the latest release of 2.6.0, I am not sure that my initial plan is going to work anymore. Did you have any thoughts on rebasing your POC to the latest master?

Full disclaimer: I am a dabbler, not a programmer. I just figured that I could clean up after @riedel and get things working in a clean way without overwriting the Yubikey stuff.

riedel commented 4 years ago

no I did not try. You are at the point why I gave up :) Particularly because I felt that I probably get it wrong without investing more thought and understanding larger parts of the code base. The code first felt extensible, but then I realized too many gotchas/dependencies/undocumented expected behaviour to make it easily portable by adding just a driver.

prusnak commented 4 years ago

but with the latest release of 2.6.0, I am not sure that my initial plan is going to work anymore

@bugzy What has changed in 2.6.0 that makes the original plan not applicable?

bugzy commented 4 years ago

@prusnak There was a "Complete refactor of hardware key integration (YubiKey and OnlyKey) [#4584, #4843]" so that it is more robust, and after that #5125 to fix issues from that refactor. Essentially, that refactor broke the mappings that @riedel provided with his POC, and invalidates any further attempts to work based on the POC without significant knowledge of KeepassXC HW Key code.

(sorry for the late response; work, what else can I say ;-) )

riedel commented 4 years ago

BTW: If anyone really refactors this they should use object oriented paradigms like inheritance instead of things like https://github.com/keepassxreboot/keepassxc/blob/7a00c5a86fcab6e544deebc8186e7e59769407ef/src/keys/drivers/YubiKey.cpp#L43 , this really would make it easier to add more implementations. Just my 2cents... . The problems fixed with last commits show how complicate it is to make this robust.

droidmonkey commented 4 years ago

This is more about the yubikey library for detecting keys being terrible.

qertoip commented 3 years ago

I really hope someone will pick up and deliver on this.

Can discuss offering a bounty for FIDO2 support tested on Trezor T.

riedel commented 3 years ago

The biggest issue why I stopped working is that Microsoft blocks direct access to FIDO2 devices inject their Windows Hello stuff into the webauthn implementation. So either one would need to implement two seperate drivers for Windows (totally underdocumented) and for the rest of the universe , or libfido2 gets Windows support: https://github.com/Yubico/libfido2/issues/34 . I have contacted Microsoft because I am really annoyed with the situation. If someone gets libfido2 to run via the Windows API I am willing to merge my POC into the latest structure. Maybe someone else also knows another crossplattform webauthn/fido2 library, that covers W10 support >1903 .

prusnak commented 3 years ago

I think we should focus on libfido2 support and let Microsoft deal with the mess they created (hopefully by sending PR to libfido2 support for their native API fixing https://github.com/Yubico/libfido2/issues/34).

mielouk commented 3 years ago

If it runs on Linux I'm just fine with it :wink:

rpanak commented 3 years ago

I'm late to the discussion, but I'd still like to go back to a previous point in the discussion. As @My1 tried to point out (I think), the hmac-secret extension implementation has some drawbacks:

I'm trying to think of a reason why not to go the normal FIDO2 way used e.g. for websites (which is IIRC basically a challenge-response protocol with asymmetric crypto). It seems to me that hmac-secret extension is basically a way to deterministically generate a shared secret. You could, however, achieve this using the challenge-response protocol by keeping the challenge fixed and stored unencrypted with the database file. You could argue this is not a proper key material, but this could probably be solved by applying a KDF to do key stretching on the authenticator response.

To be honest, this makes me wonder why does the hmac-secret extension exist at all. So I guess I must be missing something? Any explanation is welcome. :)

My1 commented 3 years ago

@rpanak well there are multiple things: 1) OSes that take away FIDO control (notably w10 1903+) make implementation more difficult, worst case impossible 2) RKs are limited and most fido devices dont support individual removal (as that only came in 2.1), making them tricky to use over time when you need to rekey and stuff, and the span of resident keys is VERY wide, like the Onlykey supports only 12 (not tested yet) FIDO2-capable yubikeys of all kinds have 25, solo and Nitro have 50 and eWBM/Trustkey run on 150 based on my testing) 3) HMAC as you said is also iirc optional, as far as I know much more widely implemented in FIDO devices because it was already on 2.0, although outside of windows Azure AD Logins (which is kinda specific) not that widely used as far as I know, so I have no idea how the future of this feature will be. 4) FIDO2 was kinda made for web stuff so the question is what restrictions the rpID has and what to use there (and especially whether and how far an OS may enforce that stuff, see point 1)

just abusing classic FIDO is a fun idea tho likely not easy. 1) points 1 and 4 above 2) at least when I work with webauthn and browsers, I never actually get back the key used on subsequent logins (makes sense as the RP is assumed to know it already), but basically only 3 things: a) the credentialID (aka keyhandle for people used to u2f) which you have to provide anyway so that's not going to help b) clientDataJson (basically an array of the client options back c) AuthenticatorData (some basic data about the authentication, so whether UV was used or not, the counter, the hash of the rpID, and extension data (like the HMAC-secret thing)) d) a signature for all of the above 3) if you wanna go passwordless and use the FIDO2-PIN there's another challenge, the fact whether you passed the PIN check or not is only conveyed via a bitflag in the Autenticator data (the UV part in 2c), so this could also be an easy bypass as you cannot properly enforce it

basically there is nothing in there which stays the same but you dont actually provide in advance, so you cannot pass it into the encryption but only act as a data valve, and I have a very strong opinion about using that in an offline encrypted storage solution


TLDR: FIDO2-HMAC would likely be possible even if it has some pitfalls and definitely in the category of better than nothing, but trying classic FIDO2 on the keepass safes would basically just be a farce similar as would be using TOTP and easily bypassed anyway especially on an open source option like keepass

Regarding why this exists, I think likely that Microsoft wanted it because they have some encryption things with the user profiles using passwords (which is why there's also a warning that forcibly resetting the PW will ax some things, including a Keepass DB protected by the Windows Account) and if there suddenly is no password but still the need for security this gets very awkward very fast so there needs to be a way to get some kind of static key, and that makes HMAC a likely candidate.

rpanak commented 3 years ago

@My1

at least when I work with webauthn and browsers, I never actually get back the key used on subsequent logins (makes sense as the RP is assumed to know it already), but basically only 3 things: a) the credentialID (aka keyhandle for people used to u2f) which you have to provide anyway so that's not going to help b) clientDataJson (basically an array of the client options back c) AuthenticatorData (some basic data about the authentication, so whether UV was used or not, the counter, the hash of the rpID, and extension data (like the HMAC-secret thing)) d) a signature for all of the above

Well, my idea was that the signature you mentioned is a function of the authenticator's master key plus some input data provided by the client (browser/keepass). If you keep the inputs constant, the signature should be constant. I see however, that the signed data contains also the signature counter as well as a bit field with some bits reserved for future use, thus you can't count on it being constant with constant input parameters (rather you can count on the signature being different each time because of the counter).

but trying classic FIDO2 on the keepass safes would basically just be a farce similar as would be using TOTP and easily bypassed anyway especially on an open source option like keepass

The user and his password can be viewed as a (deterministic) source of a secret, that is processed (KDF), used to decrypt the database and then forgotten. TOTP is fundamentaly different as it's output changes and thus it can't be used as a source of a secret. Unfortunately, as stated before, the signature in a FIDO2 assertion is not constant either (no matter the input data).

It's really unfortunate the FIDO2-HMAC keys have to be stored as RKs and can't be derived from the platform-supplied input (in a way similar to how Yubikey does this for normal FIDO2 credentials).

Anyway, thanks for the information.

tanriol commented 3 years ago

As @My1 tried to point out (I think), the hmac-secret extension implementation has some drawbacks: [...]

  • it implies use of resident keys, and because of limited authenticator storage, this doesn't scale as well as "normal credentials". Not sure though what the limits for the number of resident keys are for Yubikeys, but AFAIK normal keys are not stored but rather derived.

I think it does not require resident keys. At least it seems possible to generate a non-resident credential with hmac-secret and generate an assertion using it with command-line tools.

rpanak commented 3 years ago

@tanriol The CTAP spec (https://fidoalliance.org/specs/fido-v2.1-rd-20201208/fido-client-to-authenticator-protocol-v2.1-rd-20201208.html#sctn-hmac-secret-extension) mentions:

The authenticator generates two random 32-byte values (called CredRandomWithUV and CredRandomWithoutUV) and associates them with the credential.

I understood this as storing those random values in the authenticator.

At least it seems possible to generate a non-resident credential with hmac-secret and generate an assertion using it with command-line tools.

Can you provide more info about how you achieved this? What FIDO2 hw have you used, what tools, etc.?

My1 commented 3 years ago

@rpanak

The user and his password can be viewed as a (deterministic) source of a secret, that is processed (KDF), used to decrypt the database and then forgotten. TOTP is fundamentaly different as it's output changes and thus it can't be used as a source of a secret. Unfortunately, as stated before, the signature in a FIDO2 assertion is not constant either (no matter the input data).

that a user and password is different than a TOTP and the former 2 can be used as a part of a secret is obvious (I didnt even mention User and password, just that both TOTP and classic FIDO2 are unsuitable for encryption secrets (like we are trying to accomplish here) by their dynamic nature and are therefore only valid as a data valve.

@tanriol this is actually interesting and likely needs some research.

prusnak commented 3 years ago

I understood this as storing those random values in the authenticator.

@rpanak These random values are generated by the authenticator, but they don't need to be stored there. In another word, the whole credential (including the random values) can be stored in KeepassXC and sent to a authenticator device when needed. So, no, you don't need a device with RK support to use HMAC extension.

rpanak commented 3 years ago

@prusnak I thought CredRandom is supposed to be the secret key (see https://fidoalliance.org/specs/fido-v2.1-rd-20201208/img/hmac-secret-extension.png). If you store it outside of the authenticator, I don't think you need the authenticator anymore.

tanriol commented 3 years ago

I'm not really knowledgeable in this, so I cannot be sure I'm not doing something stupid :-)

At least it seems possible to generate a non-resident credential with hmac-secret and generate an assertion using it with command-line tools.

Can you provide more info about how you achieved this? What FIDO2 hw have you used, what tools, etc.?

Yubikey 5, libfido2 command-line utils, based on examples in manpages. Credential created with

echo credential challenge | openssl sha256 -binary | base64 > cred_param
echo relying party >> cred_param
echo user name >> cred_param
dd if=/dev/urandom bs=1 count=32 2>/dev/null | base64 >> cred_param
fido2-cred -M -h -i cred_param /dev/hidraw2 | fido2-cred -V -h | head -n 1 > cred_id

and used with

echo credential challenge | openssl sha256 -binary | base64 > assert_param
echo relying party >> assert_param
cat cred_id >> assert_param
echo salt | openssl sha256 -binary | base64 >> assert_param
# returns: client_hash, relying_party, authenticator_data, assertion_signature, hmac_secret
fido2-assert -G /dev/hidraw2 -h -i assert_param | tail -n 1

According to ykman fido list and fido2-token -I /dev/hidraw2 -c, there are no resident credentials. Probably the required secrets are stored somewhere in the credentialID?

rugk commented 3 years ago

Regarding the current campaign for SoloKey v2 there has been some comment by the creators on how that should be implemented:

We still think the way to do this is to use the hmac-secret extension that is part of the actual standard (it is in essence HMAC-SHA256 challenge-response). In that sense, the answer is, yes, at launch (and Solo V1 has it too, just like every fully certified FIDO2 dongle). If the question is whether we will implement Yubico's proprietary app (in essence, HMAC-SHA1 challenge-response), the answer is no, we do not plan to do so. This sentiment is shared by other vendors such as Trezor - if OSS such as KeePassXC and LUKS moves on to an actual standard instead of one company's private sauce, everyone wins.

prusnak commented 3 years ago

This sentiment is shared by other vendors such as Trezor - if OSS such as KeePassXC and LUKS moves on to an actual standard instead of one company's private sauce, everyone wins.

Can confirm - I am the guy from Trezor that is being mentioned :)

rugk commented 3 years ago

For Linux there seems to be a convenient variant:

JFYI new systemd has FIDO2 functionality via hmac-secret, so one does not need to used fido2luks anymore: http://0pointer.net/blog/unlocking-luks2-volumes-with-tpm2-fido2-pkcs11-security-hardware-on-systemd-248.html

https://github.com/solokeys/kickstarter2021/discussions/23#discussioncomment-315104