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

Enable WebAuthn plugin in wp-admin #153

Merged
merged 10 commits into from
May 18, 2023
Merged

Enable WebAuthn plugin in wp-admin #153

merged 10 commits into from
May 18, 2023

Conversation

iandunn
Copy link
Member

@iandunn iandunn commented May 5, 2023

Fixes #114
Closes #134
Closes #146
Related #142 -- This uses the same technique for bootstrapping the test suite.

This is an alternate approach to #134 / #146.

To Test

  1. Switch to this branch in your environment. Using a w.org sandbox is best, since local environments can't match all the complexities and quirks of production.
  2. wp plugin install two-factor-provider-webauthn. Make sure it's at least version 2.1.0.
  3. Add this to an SVN-ignored mu-plugin to network-activate the plugin without affecting production:
    // temporary for testing #153
    add_filter( 'site_option_active_sitewide_plugins', function( $net_plugins ) {
    	$net_plugins['two-factor-provider-webauthn/index.php'] = time();
    
    	return $net_plugins;
    } );

Things to note:

  • This would add wp_2fa_webauthn_credentials and wp_2fa_webauthn_users tables to the database. AFAICT the reason for that is to avoid shared memcache servers leaking credentials. If that's rare in practice, then it doesn't seem necessary to me. It could make it harder to migrate keys when we eventually use whatever two-factor implements.
    • Technically, just testing it on a sandbox adds the tables to prod. If we don't go with this solution, then I'll delete them.
  • Our custom UI will need to make requests to the webauthn_preregister and webauthn_register AJAX endpoints. Or we may want to write an additional REST API endpoint to grab some raw data like CredentialRow->counter, rather than parsing it out of the AJAX response.
  • I tried changing the user_verification_requirement setting to discouraged, so that users won't have to enter a PIN before authenticating, but was still prompted. I think the flow would be much smoother without it, and the added security doesn't outweigh that in this scenario IMO. We're using WebAuthn as a 2nd factor, and Google, GitHub, and most others I've seen discourage the PIN, so it seems safe to me.
    • Hopefully this is something we'll have time to dig into after the beta launch. On the webauthn.io demo, I need to set both User Verification and Discoverable Credential to discouraged before I can authenticate without a PIN. I don't see a way to discourage discoverable credentials here without forking.
  • Currently there're only 2 localizations. That probably isn't important for the wp-admin UI, since we're building a custom one anyway, but there are two strings that show up during login. We may want to highlight this in the beta announcement to get more translations.
  • Webauthn on Android sjinks/wp-two-factor-provider-webauthn#221 might also be a blocker, but we could probably fix post-MVP
  • There's a small issue PHP 8 issue, but it's shouldn't affect us yet. This has been resolved.

@iandunn iandunn added this to the MVP milestone May 5, 2023
@iandunn iandunn self-assigned this May 5, 2023
@iandunn iandunn mentioned this pull request May 5, 2023
17 tasks
@iandunn
Copy link
Member Author

iandunn commented May 9, 2023

I ran into sjinks/wp-two-factor-provider-webauthn#456 when testing on my sandbox. We may be able to work around that by having folks register their keys on login.wordpress.org, but currently regular users don't have access to wp-admin on that site (or any other, really).

@dd32 do you have any thoughts on whether or not we should grant folks access until we build the custom UI? I'm guessing we'd do it on-the-fly rather than actually adding roles in the db for everyone.

@dd32
Copy link
Member

dd32 commented May 9, 2023

I ran into sjinks/wp-two-factor-provider-webauthn#456 when testing on my sandbox

This works for me using a Yubikey, but not using Mac TouchID. I guess that's the difference between a PassKey and a Security Key? I might see if I can make TouchID work today based on your notes.

dd32 do you have any thoughts on whether or not we should grant folks access until we build the custom UI?

I would Errr on the side of not providing any access for any user to wp-admin, unless they have access that allows that already (Such as via a Make site).

@dd32
Copy link
Member

dd32 commented May 9, 2023

This works for me using a Yubikey, but not using Mac TouchID.

Looks like you're right, the rpId needs to be set. This works for me, allowing me to set a Mac TouchID Passkey up via wordpress.org (or make.wordpress.org) and use it at login.wordpress.org.

Index: two-factor-provider-webauthn/inc/class-utils.php
===================================================================
--- two-factor-provider-webauthn/inc/class-utils.php	(revision 2909844)
+++ two-factor-provider-webauthn/inc/class-utils.php	(working copy)
@@ -31,7 +31,9 @@

 	public static function create_webauthn_server(): ServerInterface {
 		$builder = new ServerBuilder();
-		$builder->setRelyingParty( new RelyingParty( get_bloginfo( 'name' ), self::get_u2f_app_id() ) );
+		$relay   = new RelyingParty( get_bloginfo( 'name' ), self::get_u2f_app_id() );
+		$relay->setId( 'wordpress.org' );
+		$builder->setRelyingParty( $relay );
 		$builder->setCredentialStore( new WebAuthn_Credential_Store() );
 		$builder->enableExtensions( 'appid' );
 		return $builder->build();

The proper way to do that in the plugin is probably a new option for the PassKey domain, rather than assuming it's the top-level domain.

Or I guess the other way that might not require a setting at all is this:

$relay->setId( ltrim( COOKIE_DOMAIN, '.' ) ); // Convert 'example.org' OR '.example.org' to 'example.org'. 

Followed up in sjinks/wp-two-factor-provider-webauthn#457 with a similar approach.

@dd32 dd32 mentioned this pull request May 9, 2023
2 tasks
@iandunn
Copy link
Member Author

iandunn commented May 9, 2023

This works for me using a Yubikey, but not using Mac TouchID

Huh, it didn't work for me w/ a Yubikey 🤔 . It does now with sjinks/wp-two-factor-provider-webauthn#457, though 🤷🏻‍♂️

I don't have a fingerprint scanner, but will see if I can setup a different soft key to test with.

Great job on the PR 👍🏻

not providing any access for any user to wp-admin, unless they have access that allows that already

Ah, ok, and then we would just tell folks who don't have it to wait until the custom UI is built 👍🏻

@iandunn iandunn mentioned this pull request May 9, 2023
13 tasks
@dd32
Copy link
Member

dd32 commented May 10, 2023

Ah, ok, and then we would just tell folks who don't have it to wait until the custom UI is built 👍🏻

That's my best understanding.

There's some things that end-users could probably safely access, such as the user-dashboard (/wp-admin/user/profile.php) but we'd need to strip literally everything else out of the page, to avoid edge-cases around what they can't currently access.

@iandunn
Copy link
Member Author

iandunn commented May 10, 2023

That sounds good to me 👍🏻

@iandunn iandunn force-pushed the enable-webauthn-plugin branch 2 times, most recently from 9ced987 to bd0fe5b Compare May 10, 2023 22:42
@iandunn iandunn marked this pull request as ready for review May 11, 2023 00:40
@iandunn iandunn requested review from dd32 and pkevan May 11, 2023 00:40
Copy link
Member

@dd32 dd32 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to work well for me, with the before mentioned change: https://github.com/WordPress/wporg-two-factor/pull/153/files/1736cab7c6c180291baf6deffb1fa54224d4df6a#r1195957886

I've created #165 for the WebAuthN + android issues.

wporg-two-factor.php Outdated Show resolved Hide resolved
@pkevan
Copy link
Contributor

pkevan commented May 17, 2023

AFAICT the reason for that is to avoid shared memcache servers leaking credentials.

I would assume this applies whereby a memcache server is shared across multiple unrelated sites. If that's not the case for WP.org it might be unnecessary, but it might be helpful to avoid having an additional layer where credentials are stored to reduce complexity etc. Worth double checking with secops to get their thoughts before deciding though.

@dd32
Copy link
Member

dd32 commented May 17, 2023

I'm seeing duplicate queries on load, probably due to the way we're running init() early:

SELECT * FROM wp_2fa_webauthn_credentials WHERE user_handle = '...' | WildWolf\WordPress\TwoFactorWebAuthn\WebAuthn_Credential_Store::get_user_keys | 0.0003
-- | -- | --
SELECT * FROM wp_2fa_webauthn_credentials WHERE user_handle = '...' | WildWolf\WordPress\TwoFactorWebAuthn\WebAuthn_Credential_Store::get_user_keys | 0.0003

Annoying that the plugin believes object caches are insecure..

edit: This is unrelated to our changes, this is caused by set_current_user running multiple times it appears. Just annoying plugin choices.

@pkevan
Copy link
Contributor

pkevan commented May 17, 2023

Seems to work for both yubikey (5nfc) and touch id (14" 2021 M1) on my sandbox - both got registered, and both were invalid when trying to add a second time.

The only strange thing is the revalidation prompt - since it's time based, you don't get asked again after doing it once. So in my testing, I removed all webauthn keys, and then re-added, but wasn't prompted to reauth, which seems like we should?

Also, when I was asked to re-auth, it didn't choose the webauthn key which was already registered (prior to switching to this branch from some earlier testing), but the TOTP key. Not sure if it's because that was already setup prior to adding a touch id, but probably worth investigating further.

@dd32
Copy link
Member

dd32 commented May 18, 2023

and then re-added, but wasn't prompted to reauth, which seems like we should?

Adding a key is an act of authenticating in a manner, so revalidating immediately after adding it seems.. duplicative? But yeah, it's time-based, I'm not sure this is something that needs fixing?

Also, when I was asked to re-auth, it didn't choose the webauthn key which was already registered (prior to switching to this branch from some earlier testing), but the TOTP key.

That's because for re-auth it prefers the method used to login with. A change upstream to Two-Factor (or on wporg) to prefer the most-recently-configured method probably makes sense though.

A good example of the preference thing would be if maybe TOTP, Email, and WebAuthN were enabled and you login with Email because you don't have your devices with you, it re-prompts with Email again rather than the primary method of TOTP.

@pkevan
Copy link
Contributor

pkevan commented May 18, 2023

Adding a key is an act of authenticating in a manner, so revalidating immediately after adding it seems.. duplicative?

If that's the case and method we follow then we should avoid triggering the re-auth once done - my example was after adding one key I needed to reauth (since it hadn't been done), but not after adding-removing-adding others. Maybe the timer needs resetting upon log-in?

@dd32
Copy link
Member

dd32 commented May 18, 2023

Adding a key is an act of authenticating in a manner, so revalidating immediately after adding it seems.. duplicative?

If that's the case and method we follow then we should avoid triggering the re-auth once done - my example was after adding one key I needed to reauth (since it hadn't been done), but not after adding-removing-adding others. Maybe the timer needs resetting upon log-in?

Ah! Gotcha, Two-Factor bumps the time when a provider is enabled for a user (via the two-factor API, when they previously didn't have 2FA enabled), but it has no knowledge of the plugin adding a new key.
It sounds like in this case the provider was enabled for you, but wasn't active as it had no keys, and once the keys were added, it became active but still didn't go through the enable-provider flow.. OR you simply ran into the short 15min revalidation time (IMHO it needs to be increased, at least for w.org).

So I don't think this is at all related to this PR itself.

There's a few options here I think:

  1. The WebAuthN provider needs to be updated to call a Two-Factor method to bump the session 2fa timestamp (Probably, it needs a PR to understand the reAuth flow anyway)
  2. The wporg-two-factor enable-provider code needs to not be a filter, but actually call the Two-Factor enable-provider methods (Maybe)
  3. wporg-two-factor needs to bump the session 2fa timestamp to work around 1 or 2 (Probably)

@pkevan
Copy link
Contributor

pkevan commented May 18, 2023

So I don't think this is at all related to this PR itself.

Agreed - i'll make a new issue for it rather than it getting stuck in this PR 😄. Part of me wonders whether it just needs re-testing once live, as it could have been my profile and having partially set it up for an earlier test of this.

@pkevan
Copy link
Contributor

pkevan commented May 18, 2023

It sounds like in this case the provider was enabled for you, but wasn't active as it had no keys

There was one active key and I added another.

iandunn and others added 2 commits May 18, 2023 12:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Turn on WebAuthn in wp-admin
3 participants