-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Groundwork to support flexible multifactor database authentication and FIDO2 #10311
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
base: feature/fido2
Are you sure you want to change the base?
Conversation
|
If this general feature works okay I'll implement support for FIDO2 in KeePassDX next, so we'll have compatible implementations everywhere relevant. |
| return false; | ||
| } | ||
|
|
||
| auto authenticationFactorInfo = m_db->authenticationFactorInfo(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is one of the two changes to the existing database reading code: if we read information about authentication factors from the header, either add a MultiAuthenticationHeaderKey to the CompositeKey or replace the CompositeKey with one entirely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reader shouldn't be concerned with this, the Database class should hold the composite keys that would also include multifactor keys. These get presented to the reader for decryption operations.
| QVariantMap data = readVariantMap(&variantBuffer); | ||
| db->setPublicCustomData(data); | ||
|
|
||
| auto it = data.constFind(AUTHENTICATION_FACTORS_HEADER_KEY); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the second change to the existing database reading code: read, validate, and store authentication factor info from one member of the outer header.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be moved into Database as it should be a normal CustomData element that the database is concerned with. the KDBX Reader has no concern for this information.
|
Oh this is fun! Will comb through this soon. |
|
I am willing to provide my design skills for some of the UI parts, although I'm definitely not a king at programming! |
| explicit FactorKeyDerivation() = default; | ||
| ~FactorKeyDerivation() override = default; | ||
|
|
||
| virtual bool derive(QByteArray& data, const QByteArray& key, const QByteArray& salt); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a purely virtual function (= 0)
|
I've added a commit to attempt to address build failures. I think your test coverage agent is running a different compiler (likely gcc) than I am (clang). It appears there are some minor differences in how the QT libraries work between the two. If it fails again please forgive me - on my local machine, |
|
I believe the MacOS build failure is test flake, rather than a problem with this PR. I don't have permission to rerun it, but other than that the automated checks now pass. |
I just stumbled over that myself: #10320 |
There is another piece of flake in |
|
@droidmonkey I think I've addressed all the feedback. I also checked that botan uses PKCS7 padding. PKCS7, when you need N bytes of padding, puts the number N N times. So a 15-byte message gets Are you waiting for something else from me here? If this direction is okay I can proceed to implement the saving and FIDO2 parts of the change. |
|
I'm good, will review as I can |
|
My "make format" seems to be doing something different from the CI formatting run. At any rate, this is still ready for review whenever. |
|
Yah cmake formatting was recently updated and we have a PR to align to the new version |
|
hi, is there any status update? |
This code is waiting for review by a project maintainer. |
This comment has been minimized.
This comment has been minimized.
|
I know people have been asking about this, because it has been open for a tad under a calendar year. But the status is still the same: waiting for a project maintainer to complete review of the code. Once that is done, or there's some kind of commitment that will happen, I'll raise a pull request implementing the FIDO2 support built on this. Do I regret not releasing and maintaining a fork with working FIDO2 changes, so people could use it? Perhaps a bit. But I'm trying to do things the correct way here, and forks usually create problems IMO. |
Just checking you're not waiting on anything more from me here? I'm not aware of anything further I'm supposed to do with/to this PR to make it ready. Let me know if you think something is missing. |
|
You are good, thank you. |
|
I have decided to merge your PR to a dedicated branch for Fido2 instead of develop. This will allow you to continue developing this feature over time without us having to release it directly with 2.8.0. |
This adds the ability to parse and validate (but not use) authentication factor information contained within the KDBX outer header. Use authentication factor headers if present for opening database Saving the database will still discard the header information, but read-only access works.
52d4606 to
1888b86
Compare
|
I am starting my journey to clean this up and conform it to our standards. Looks good so far. However, I am definitely not a fan of using XML for the AuthenticationFactor information. The parse code will be 100x easier with JSON. So I am going to shift to json. |
55d3643 to
90f3aae
Compare
90f3aae to
362836f
Compare
|
@BryanJacobs after reviewing and thinking about your spec, I have a couple critical findings:
These comments only apply if the first comment is not addressed:
I can see the benefit of having fully contained groups of credentials, but I dont think we can get past the multi KDF problem (without breaking forward secrecy) and complex key registration & unlock GUIs. |
|
I'm almost done cleaning up my local version which has the whole feature working end-to-end. Now that it's decided the feature isn't going to be integrated, I will post that as a fork.
The reason I used XML is because a KDBX reader must integrate an XML parser already, as XML is part of the spec. JSON is not. I think TLV or XML would make sense, but requiring a JSON parser in order to unlock the database (on top of the TLV and XML parsers) isn't worth the benefit of saving... something like 100 lines of parsing code.
I strongly disagree with this feedback. Pros of what you are suggesting:
These are the only advantages. Cons of what you are suggesting:
No, by design. The spec explicitly prohibits having a unique identifier for the key unless Enterprise Attestation is in use (we can't practically speaking use Enterprise Attestation). You can find whether a particular key is the right one by looking if it has a certain User ID on it, but to do that:
You're looking for ways to try to fit this into the way Yubikey challenge-response works right now. That is not how FIDO2 works, and you shouldn't do that. Instead of doing it this way I have put in the VerificationIn, VerificationOut, and Name elements in the header. The user Names the FIDO2 authenticator whatever they want, and we use VerificationIn to see if it provided the correct keying material to conclusively find if the user provided the right authenticator. The current design executes the Argon KDF just one time per full authentication attempt, not one time per credential. It executes the decryption operation in the header (AES256+HMAC) once per FIDO2 factor in each group. That decryption operation is going to be extremely fast. When a group contains FIDO2 factors, the user chooses one of them to use, even if the computer has ten keys connected to it. All the keys signal for a touch, the user touches one of them, and done. They don't need to touch each key in sequence.
I don't think this is you constructively replying to my proposal here, I think this is you rejecting the core approach. If you had intended to reject the approach, it would have been nice had you done that over a year ago when I proposed it. Please remember that it's NOT SAFE to only allow a single FIDO2 credential. If you do that, the user losing that FIDO2 credential will lock them out of their database. If you do not allow the user to have multiple credentials within a FactorGroup you CANNOT support FIDO2. I really can't say this strongly enough: do not go in the direction of only allowing a single FIDO2 key as part of the master key. Two different FIDO2 authenticators cannot produce the same keying material. Users' single authenticators will get reset, or lost, or have the credential deleted, and there is no way to back them up. People will lose access to their passwords. Everything else you've said in this response is reasonable feedback, debateable, there-are-advantages-either-way stuff. Having a single FIDO2 factor in a FactorGroup (ie, "only one of each factor"), is not. That's a bad idea, don't do it.
I am not sure I understand what you mean by "perfect FORWARD secrecy" in this context. My understanding of PFS is that it's a system where if a key is disclosed to an attacker in the future, they can't use it to decrypt an encrypted object they captured in the past. I'm going to assume you just mean "secrecy" instead of "perfect forward secrecy" here - please correct me if that's not the case. The reason for having the Comprehensive option in the header is so that the user can choose between the significantly increased convenience of not having to list the factors used to unlock the database and the SLIGHTLY increased security of not disclosing what types of factors are used for that unlocking. Kerckhoffs's principle says that concealing the types of keys used to unlock the database does not weaken its security, but I have left the decision to the users. Unless you do something fancy like putting decoy FIDO2 credentials into the header when FIDO2 is not used for unlocking, the disclosure that FIDO2 is one of the unlocking methods is inevitable with how FIDO2 works, due to the required data storage.
I'm sorry to hear that you're not going to accept this pull request. Rather than implement FIDO2 as a single feature, I will clean up and publish my local version that has these features working. I've been using it myself for many months at this point. Give me two weeks to do that. I don't think the complexity or amount of code it adds is prohibitive, but you're free to either:
I'm sorry this review process didn't work well, but I can't support a direction that leads to having users need to choose between being able to maintain access to their database (by having redundant, backed up credentials) and using FIDO2. Since you've said you want to go in the direction of using a single FIDO2 key alone, I'm not going to patch up this diff to do that. |
This defeats the purpose of using a KDF at all. As unlikely as it is, i could very much use the verification hash function to brute force the key material without the cost of the KDF factoring in. Im sorry if you got an impression of single fido2, I absolutely do not have this intention at all. I understand the unique problem that fido2 presents compared to yubikey methods.
Leave this to a kdbx upgrade. We are basically circumventing the kdbx standard with this pathway. I prefer to keep that minimal. |
A FIDO2 credential's hmac-secret extension uses HMAC-SHA256, with an effective security strength of 128 bits. The purpose of the KeePass KDF is to strengthen user passwords, so when someone uses "dog" as their password it can't be immediately guessed. That's not a problem with the authenticator-generated (not user-generated) secret. Yes, you can brute force one of the authenticator groups directly when it has VerificationIn present. That's why my spec makes the verification optional and requires that it be absent when a password factor is in the group. Good luck brute forcing a 256-bit-long, 128-effective-security-bits key. |
|
Sorry, amendment to the last: what you're actually attacking with the brute-force attack against VerificationIn is the 256 bit OUTPUT of the hmac, not the input. The effective NIST security strength is 256 bits. As a result, it's literally as strong as the database master key. This is not a viable attack vector. You cannot safely use VerificationIn for a group containing a password factor, because it allows non-KDF'ed guesses against the password (NB: I imagined eventually adding a KDFed-password factor type but did not write one to avoid complaints about "but the KDF runs twice!"). The use of verification in the header is safe for both Yubikey chal-resp and FIDO2 factors. As it's equally strong to the database master key (post-KDF), it does not weaken the overall security of the system. |
You added this in an edit to your comment so I didn't initially see/respond to it. This makes me think I misread something in your earlier comment. HOW do you wish to support multiple FIDO2 factors interchangeably?
Again, I do have this working, but I'm willing to leave it back-burner or whatever. My objective is to get FIDO2 support functional in a security- and user-satisfactory way. What I tried to do here was not to circumvent the standard but to evolve it. I'm willing to just ... not do that ... if there's a good path to FIDO2 support. Are you suggesting just dropping non-FIDO2 factors from KeePassXC's allowed list of things in the header? Or what changes? I'm not 100% clear. The FIDO2 Credential IDs have to go somewhere. |
|
Yes, I was trying to say (unsuccessfully) to just not support "comprehensive" factor groups. Only support the use of FIDO2 as an additional factor to password and/or key file, and allow multiple fido2 factors to be registered. It simplifies everything. I can agree to Yubikey OR FIDO2. You have to select which one you want to use with your database. |
|
Also, regarding XML vs. JSON, it's not about less code, it is about readability. The KDBX XML has absolutely no bearing on our use of JSON in custom data fields. We are trying to use JSON everywhere else since it is so much easier to read and deal with. |
|
Sorry if this is a bit off topic, but is it part of the intended use cases (and/or is it possible) to have a DB encrypted to a short password AND (fido2 token OR a really strong fallback password), or do you mean that all alternatives to the fido2 token have to be hardware tokens (either fido2 or yubikey)? |
|
The original proposal for the work here was a flexible list of alternative login credential groups that could include a second, very strong, password. I'm still not sold on enabling that level of flexibility since it introduces a lot of complications to the user interface and to the handling of the unlock and saving sequence. |
|
an progress in this? @BryanJacobs is this branch can be used in production? |
|
I'm very interested in this feature! Being able to unlock with either a hardware FIDO token OR a long backup passphrase (like LUKS) would be perfect. I could use the hardware key day-to-day but still have a secure backup passphrase in a safe place that I don't need to remember. If the key gets lost or breaks, I can still get into my database. Just wanted to comment to show there's interest in this. Thanks for the work on this feature and for maintaining KeePassXC in general. Great software! |
|
I need this feature because I sync the same database to several devices, one of them is a tablet where typing is a hassle, another one allows only to use a pass phrase. |
|
What keepass app are you using on the tablet? Passphrase only? I dont understand. Most mobile device apps for keepass databases support biometric quick unlock. |
One is KeepassXC on a 2-in-1 running Linux. When I'm working in the field, I don't take the detachable keyboard and use it as a tablet. The fingerprint reader is only available on expensive versions of the device. I got mine second hand. I have the option to use a micro SD card. |
Closes #11582
Screenshots
No user-visible changes.
Testing strategy
Unit tests for parsing of the header included, and a Kdbx test opening a database secured using header-described password authentication.
Type of change
Relates to #9506 .
Description
This PR allows KeePassXC to read files containing an XML blob in their header describing multifactor authentication for the database. This blob can describe a replacement for the existing composite key, or append to it. One or more "groups" can be used, and each group is one-of-N.
Either way, the multifactor methods' output goes through the EXISTING key derivation function - it's just input to the KDF.
The target use case here is to support using one-of-N FIDO authenticators: for that use case, the header would describe a single group with N-many Factors in it.
I have implemented end-to-end support for FIDO2 in this format in KeePassPy at libkeepass/pykeepass#373 , including human-readable documentation of the file format. But this repository is generally cleaner than that one so I figured I'd raise an intermediate PR rather than a big-bang here.
What is Done
What is Not Yet Done
Please let me know if this is going in an acceptable direction. If so, I'll keep going, but I'd rather not have a PR with 3000-plus lines in it.
Note that this PR contains two cleanly separated commits and it may be easier to look at the first one first.