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

libfido2-util: fix a regression in the pre-flight mechanism #32515

Merged
merged 1 commit into from May 2, 2024

Conversation

kszczek
Copy link
Contributor

@kszczek kszczek commented Apr 27, 2024

The recently merged PR #32295 introduced support for the credProtect extension, but in doing so, it broke the discoverability of credentials by setting the policy to FIDO_CRED_PROT_UV_REQUIRED. This policy would require us to pass the PIN to the token in the pre-flight request to be able to discover it, which defeats the purpose of pre-flight requests as they're supposed to be non-interactive.

Instead of completely removing credProtect support, we could just relax the policy to FIDO_CRED_PROT_UV_OPTIONAL_WITH_ID, but I decided against that for two reasons:

  1. This issue would still occur for an edge case described in the CTAP 2.1 specification.

    Note: Some authenticators for high-security environments may be configured to always set credProtect 3 for all created credentials regardless of what the platform requests...

  2. FIDO_CRED_PROT_UV_OPTIONAL_WITH_ID policy seems to be useful only for resident keys, because you always have to pass the credential ID anyway for non-resident keys, so this policy would always be fulfilled, as we use non-resident keys.

I stumbled upon this issue while trying to create a user's home directory with a FIDO2 token using homectl, systemd-homed and systemd-homework from the tip of the main branch.

# ./homectl create --fido2-device=auto test
Initializing FIDO2 credential on security token.
πŸ‘† (Hint: This might require confirmation of user presence on security token.)
πŸ” Please enter security token PIN: β€’β€’β€’β€’β€’β€’                  
Generating secret key on FIDO2 security token.
πŸ‘† In order to allow secret key generation, please confirm presence on security token.
Password suggestions: ERXuJyl6ocub7i Eg@KUKINAt3fsEx arinUd0JiBx@rUh MyzhohwiMzeLS ax0BAPOBPoKwaK oNarDes3JYq=EK
πŸ” Please enter new password for user test: β€’β€’β€’β€’β€’β€’β€’β€’                
πŸ” Please enter new password for user test (repeat): β€’β€’β€’β€’β€’β€’β€’β€’                
πŸ‘† Please confirm presence on security token.
Operation on home test failed: Failed to execute operation: Resource temporarily unavailable

Snippet from systemd-homed's journal (running with SYSTEMD_LOG_LEVEL=debug)

The credential is not in the token /dev/hidraw2, skipping.
Got notify message lacking both ERRNO= and SYSTEMD_LUKS_LOCK_FD= field, ignoring.
Worker reported error code EAGAIN.
Operation on test failed: Resource temporarily unavailable

@github-actions github-actions bot added util-lib please-review PR is ready for (re-)review by a maintainer labels Apr 27, 2024
Copy link

Important

An -rc1 tag has been created and a release is being prepared, so please note that PRs introducing new features and APIs will be held back until the new version has been released.

@bluca bluca added good-to-merge/waiting-for-ci πŸ‘ PR is good to merge, but CI hasn't passed at time of review. Please merge if you see CI has passed fido2 and removed please-review PR is ready for (re-)review by a maintainer labels Apr 27, 2024
@YHNdnzj
Copy link
Member

YHNdnzj commented Apr 27, 2024

cc @BryanJacobs

@BryanJacobs
Copy link
Contributor

If you want to discover tokens without requiring a PIN (knowing that to use that token later the PIN will be required), then using credProtect level 2 is appropriate.

Doing this would make possible (again) the problem from the other ticket though: if the token is created without a PIN then the disk will be encrypted using the "wrong" secret, and decryption will fail.

Is systemd-homed using the hmac-secret extension the same as systemd-cryptenroll? If so, it might be better to change the path to prompt the user for their PIN if the home directory was set up with a credential requiring a PIN...

@BryanJacobs
Copy link
Contributor

My understanding of the state of things if this PR is merged:

Seems generally okay, but I think the ideal solution would be to track whether a certain credential requires UV for usage, and if so, also require UV for discovery. That's one user interaction in the case the correct credential is present, and unlike the other proposed solutions, it works in every case (including alwaysUv authenticators).

@kszczek
Copy link
Contributor Author

kszczek commented Apr 27, 2024

Then I guess we could use the credProtect extension only when using UV, but not when using a PIN without UV. This wouldn't break the pre-flight, as it explicitly doesn't support UV:

/* FIDO2 devices may not support pre-flight requests with UV, at least not
* without user interaction [1]. As a result, let's just return true
* here and go ahead with trying the unlock directly.
* Reference:
* 1: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#sctn-getAssert-authnr-alg
* See section 7.4 */
if (has_uv && FLAGS_SET(flags, FIDO2ENROLL_UV)) {
log_debug("Pre-flight requests with UV are unsupported, device: %s", path);
return true;
}

So then instead of completely removing credProtect support, we could just do something like this:

int extensions = FIDO_EXT_HMAC_SECRET;
if (FLAGS_SET(lock_with, FIDO2ENROLL_UV)) {
        /* Attempt to use the "cred protect" extension, requiring user verification (UV) for this
         * credential. If the authenticator doesn't support the extension, it will be ignored. */
        extensions |= FIDO_EXT_CRED_PROTECT;

        r = sym_fido_cred_set_prot(c, FIDO_CRED_PROT_UV_REQUIRED);
        if (r != FIDO_OK)
                log_warning("Failed to set protection level on FIDO2 credential, ignoring: %s", sym_fido_strerr(r));
}

What do you think?

@BryanJacobs
Copy link
Contributor

Then I guess we could use the credProtect extension only when using UV, but not when using a PIN without UV. This wouldn't break the pre-flight, as it explicitly doesn't support UV:

/* FIDO2 devices may not support pre-flight requests with UV, at least not
* without user interaction [1]. As a result, let's just return true
* here and go ahead with trying the unlock directly.
* Reference:
* 1: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#sctn-getAssert-authnr-alg
* See section 7.4 */
if (has_uv && FLAGS_SET(flags, FIDO2ENROLL_UV)) {
log_debug("Pre-flight requests with UV are unsupported, device: %s", path);
return true;
}

So then instead of completely removing credProtect support, we could just do something like this:

int extensions = FIDO_EXT_HMAC_SECRET;
if (FLAGS_SET(lock_with, FIDO2ENROLL_UV)) {
        /* Attempt to use the "cred protect" extension, requiring user verification (UV) for this
         * credential. If the authenticator doesn't support the extension, it will be ignored. */
        extensions |= FIDO_EXT_CRED_PROTECT;

        r = sym_fido_cred_set_prot(c, FIDO_CRED_PROT_UV_REQUIRED);
        if (r != FIDO_OK)
                log_warning("Failed to set protection level on FIDO2 credential, ignoring: %s", sym_fido_strerr(r));
}

What do you think?

I think it might be worth testing how systemd-homed behaves when attempting to use it with a token that has alwaysUv set. I think it might be broken in that scenario, because the preflight would cause the token to reply with PIN_REQUIRED, breaking discovery.

What you've proposed above is better in my opinion than entirely removing the use of credProtect, but I think there's still an underlying issue - the current code assumes that credentials requiring UV to use can always be discovered without user interaction. That assumption isn't true, by design. First, some authenticators have alwaysUv=true. Second, some authenticators have makeCredUvNotRequired=false.

I still think it's better to check if any of the credentials to be used will require PIN/UV, and if they will, prompt for the PIN (or touch, or whatever) - and thus authenticator selection - prior to trying "discovery".

But that's more work, and at least it's not broken for everybody if you only use credProtect for non-PIN UV, so still an improvement.

@kszczek
Copy link
Contributor Author

kszczek commented Apr 27, 2024

I think it might be worth testing how systemd-homed behaves when attempting to use it with a token that has alwaysUv set. I think it might be broken in that scenario, because the preflight would cause the token to reply with PIN_REQUIRED, breaking discovery.

I unfortunately don't own any FIDO2 tokens with proper UV, so I can't test this properly, but I guess the best solution for this problem would be to parse the make credential response and if uv is enabled but not requested by user, then throw an error? This would inform the user that credentials without UV are not supported by their token and they either have to disable alwaysUv or enroll with --fido2-with-user-verification=true explicitly.

What you've proposed above is better in my opinion than entirely removing the use of credProtect, but I think there's still an underlying issue - the current code assumes that credentials requiring UV to use can always be discovered without user interaction. That assumption isn't true, by design. First, some authenticators have alwaysUv=true. Second, some authenticators have makeCredUvNotRequired=false.

The pre-flight exits early, returning true if the credential was created with UV without getting an assertion, so if you have multiple tokens with UV support, systemd-homed/systemd-cryptsetup will try each one, prompting for interaction.

I still think it's better to check if any of the credentials to be used will require PIN/UV, and if they will, prompt for the PIN (or touch, or whatever) - and thus authenticator selection - prior to trying "discovery".

I guess that's the problem that pre-flights solved. If the user has multiple FIDO2 tokens plugged in, they don't have to enter the PIN for each one of them to find the right one.

@BryanJacobs
Copy link
Contributor

I unfortunately don't own any FIDO2 tokens with proper UV, so I can't test this properly, but I guess the best solution for this problem would be to parse the make credential response and if uv is enabled but not requested by user, then throw an error? This would inform the user that credentials without UV are not supported by their token and they either have to disable alwaysUv or enroll with --fido2-with-user-verification=true explicitly.

No, alwaysUv is a mutable flag. fido2-token -S -u <device> enables it, and it can be disabled again later. It could be disabled when enrolling and then enabled when trying to use the credential.

What you've proposed above is better in my opinion than entirely removing the use of credProtect, but I think there's still an underlying issue - the current code assumes that credentials requiring UV to use can always be discovered without user interaction. That assumption isn't true, by design. First, some authenticators have alwaysUv=true. Second, some authenticators have makeCredUvNotRequired=false.

The pre-flight exits early, returning true if the credential was created with UV without getting an assertion, so if you have multiple tokens with UV support, systemd-homed/systemd-cryptsetup will try each one, prompting for interaction.

That's not great.

I still think it's better to check if any of the credentials to be used will require PIN/UV, and if they will, prompt for the PIN (or touch, or whatever) - and thus authenticator selection - prior to trying "discovery".

I guess that's the problem that pre-flights solved. If the user has multiple FIDO2 tokens plugged in, they don't have to enter the PIN for each one of them to find the right one.

You can check all the ones that definitely don't have a PIN at once :-P. But yes, sorry, there is no way to get that convenience reliably that I see.

@YHNdnzj YHNdnzj added please-review PR is ready for (re-)review by a maintainer and removed good-to-merge/waiting-for-ci πŸ‘ PR is good to merge, but CI hasn't passed at time of review. Please merge if you see CI has passed labels Apr 27, 2024
@BryanJacobs
Copy link
Contributor

Here's a crazy idea - what about using the CTAP2.1 authenticatorSelection command when available to find the right authenticator?

As users get rid of older non-2.1 tokens, the hassle would go away.

@kszczek
Copy link
Contributor Author

kszczek commented Apr 27, 2024

No, alwaysUv is a mutable flag. fido2-token -S -u enables it, and it can be disabled again later. It could be disabled when enrolling and then enabled when trying to use the credential.

I don't think we should be modifying the alwaysUv flag when enrolling. If user sets alwaysUv to a truthy value, they do it for a reason, and we shouldn't try to be smarter than the user, just notify them that what they tried to do is illegal.

Here's a crazy idea - what about using the CTAP2.1 authenticatorSelection command when available to find the right authenticator?

Would this work for non-resident keys that we use? Sorry, but I'm still learning about FIDO2 and CTAP.

if (has_rk) {
r = sym_fido_cred_set_rk(c, FIDO_OPT_FALSE);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to turn off FIDO2 resident key option of credential: %s", sym_fido_strerr(r));
}

@BryanJacobs
Copy link
Contributor

I don't think we should be modifying the alwaysUv flag when enrolling. If user sets alwaysUv to a truthy value, they do it for a reason, and we shouldn't try to be smarter than the user, just notify them that what they tried to is illegal.

Not what I was suggesting. I'm pointing out that when creating the home directory it can be not-set, and when trying to unlock the home directory it can be set. The user could have turned it on in the intervening time.

If at any point systemd relies on a user-verification-less discovery mechanism, then the user will be locked out.

Also, authenticators can support turning on alwaysUv and not-support disabling it: it's valid to have the transition be one-way unless a reset is performed.

Here's a crazy idea - what about using the CTAP2.1 authenticatorSelection command when available to find the right authenticator?

Would this work for non-resident keys that we use? Sorry, but I'm still learning about FIDO2 and CTAP.

if (has_rk) {
r = sym_fido_cred_set_rk(c, FIDO_OPT_FALSE);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to turn off FIDO2 resident key option of credential: %s", sym_fido_strerr(r));
}

authenticatorSelection doesn't have anything to do with the credentials. It makes all the tokens flash/whatever, and the user explicitly chooses the one they want to use.

The recently merged PR systemd#32295 introduced support for the credProtect
extension, but in doing so, it broke the discoverability of credentials
by setting the policy to FIDO_CRED_PROT_UV_REQUIRED for UV-less,
PIN-protected credentials. This policy would require us to pass the PIN
to the token in the pre-flight request to be able to discover it,
which defeats the purpose of pre-flight requests as they're supposed
to be non-interactive.

This commit restricts the usage of credProtect to UV credentials only.
@kszczek
Copy link
Contributor Author

kszczek commented Apr 27, 2024

Good points, but I think we should create separate RFEs for those issues and resolve the issue at hand in this PR.

/please-review

@BryanJacobs
Copy link
Contributor

Good points, but I think we should create separate RFEs for those issues and resolve the issue at hand in this PR.

/please-review

Agreed!

@bluca bluca added this to the v256 milestone Apr 27, 2024
@poettering poettering merged commit 70246e3 into systemd:main May 2, 2024
44 of 49 checks passed
@github-actions github-actions bot removed the please-review PR is ready for (re-)review by a maintainer label May 2, 2024
@kszczek kszczek deleted the fido2-preflight-regression branch May 19, 2024 20:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

None yet

5 participants