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

WIP: Support device public key and passkeys #356

Merged
merged 13 commits into from Jun 21, 2023

Conversation

aseigler
Copy link
Collaborator

@aseigler aseigler commented Dec 13, 2022

Resolves #334 (and #350).

@aseigler aseigler added the feature New feature label Dec 13, 2022
@aseigler aseigler self-assigned this Dec 13, 2022
@aseigler aseigler marked this pull request as draft December 13, 2022 13:21
@codecov-commenter
Copy link

codecov-commenter commented Dec 14, 2022

Codecov Report

Merging #356 (457412d) into master (5e72564) will decrease coverage by 2.11%.
The diff coverage is 42.91%.

📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more

@@            Coverage Diff             @@
##           master     #356      +/-   ##
==========================================
- Coverage   76.80%   74.70%   -2.11%     
==========================================
  Files          89       92       +3     
  Lines        2531     2728     +197     
  Branches      427      456      +29     
==========================================
+ Hits         1944     2038      +94     
- Misses        466      577     +111     
+ Partials      121      113       -8     
Impacted Files Coverage Δ
...ls/Objects/AuthenticationExtensionsClientInputs.cs 14.28% <0.00%> (-5.72%) ⬇️
...s/AuthenticationExtensionsDevicePublicKeyInputs.cs 0.00% <0.00%> (ø)
Src/Fido2/AuthenticatorResponse.cs 57.14% <ø> (ø)
Src/Fido2/DevelopmentInMemoryStore.cs 0.00% <0.00%> (ø)
Src/Fido2/Objects/AttestedCredentialData.cs 82.14% <0.00%> (-3.32%) ⬇️
Src/Fido2/AuthenticatorAssertionResponse.cs 43.04% <23.07%> (-7.81%) ⬇️
...ido2/Objects/DevicePublicKeyAuthenticatorOutput.cs 41.37% <41.37%> (ø)
...s/Objects/AuthenticationExtensionsClientOutputs.cs 85.71% <50.00%> (-14.29%) ⬇️
Src/Fido2/AuthenticatorAttestationResponse.cs 76.00% <61.53%> (-10.96%) ⬇️
...2.Models/Objects/AttestationVerificationSuccess.cs 90.00% <85.71%> (-10.00%) ⬇️
... and 12 more

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

@iamcarbon
Copy link
Sponsor Contributor

iamcarbon commented Jan 18, 2023

Hi @aseigler -- anything functionality left here?

@iamcarbon iamcarbon mentioned this pull request Jan 18, 2023
@aseigler
Copy link
Collaborator Author

Hi @aseigler -- anything functionality left here?

Not sure, need to do another pass on the DPK things to make sure I've got all the logic straight. The passkey area I think is good to go.

@aseigler
Copy link
Collaborator Author

Still need to make RP configurable BE/BS options and plumb those into registration and login flows before passkey is done.

DPK looks like it has object names that have been updated.

Reminder to create CredentialRecord object, move existing StoredCredential members there and hand around that credential record instead of parts of it in and out of login flow.

Reminder to remove the rest of the references to token binding.

Demo/Startup.cs Outdated
@@ -52,6 +52,8 @@ public void ConfigureServices(IServiceCollection services)
options.Origins = Configuration.GetSection("fido2:origins").Get<HashSet<string>>();
options.TimestampDriftTolerance = Configuration.GetValue<int>("fido2:timestampDriftTolerance");
options.MDSCacheDirPath = Configuration["fido2:MDSCacheDirPath"];
options.AllowBackupEligibleCredential = Configuration.GetValue<bool>("fido2:allowBackupEligibleCredential");
Copy link

@rudiv rudiv Feb 19, 2023

Choose a reason for hiding this comment

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

I would argue that these be allowed by default to support a happier upgrade path. Some RPs may not care about these statuses at all, and so their existing configuration would break?

I know this comment is against the Demo project, but the Configuration default would also be false if this wasn't decided / set at startup.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I would argue that these be allowed by default to support a happier upgrade path. Some RPs may not care about these statuses at all, and so their existing configuration would break?

I know this comment is against the Demo project, but the Configuration default would also be false if this wasn't decided / set at startup.

I hear ya. This is a enterprise vs consumer setting in my eyes, and I personally am enterprise focused. When this PR is in a release it will be part of a major update, and we are expecting that will contain some breaking changes. Regardless of which way it goes by default, there will be notes to help guide implementers.

Copy link

Choose a reason for hiding this comment

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

I agree, I think my main concern is that this may block typical use cases.

For an example (even thinking enterprise), you may wish for your users to have a passkey that's backed up and set this setting. However, they would then be unable to add a physical key (Yubikey et al). I know this is the whole point of "Passkeys" to not need it, but I feel that it's more of a per-key decision than one that should be set globally for the RP?

Copy link
Collaborator

Choose a reason for hiding this comment

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

@aseigler I agree that we should default to true. Given that the average developer might not know what this option do (and should be True for them), while I think we can expect enterprise developers have more knowledge and know to turn it off because of their own research/policy for deploying fido2.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

They would actually be able to register a typical security key with this setting true or false, as those do not set BE flag. Currently only certain platform authenticators do. It's undefined how the platform is protecting those BE keys and where they are going to be restored to. That's why in a situation where the RP cares (enterprise), they might wish to restrict BE keys from being registered at all on the platform, or maybe allow some blessed authenticators but not others (would require additional work to support this). I guess there might be a reason an RP would only want to allow BE keys? That's not the result or intent of this implementation, at least currently.

Copy link

Choose a reason for hiding this comment

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

Understand the Enterprise point of view.

From a more consumer-focused point of view (ie. specifically the well marketed "Passkeys" aspect) we may want to accept only Passkeys at first, to ensure that users are not able to lose access very easily. Whilst it's not perfect and most authenticators don't give on where the keys are from / stored, knowing that they're backed up allows it to be reasonably assumed that the key is durable and isn't going to disappear like in the case of a physical key loss. For example, we explicitly advise the user to register with a backed up authenticator (specifically iCloud backed Passkeys or their Android phone).

I might be wrong in assuming that this is where this library is going to see most of its use in the short-mid term, but that's my 2c on why this default would be the wrong one.

Copy link
Collaborator Author

@aseigler aseigler Mar 6, 2023

Choose a reason for hiding this comment

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

From a more consumer-focused point of view (ie. specifically the well marketed "Passkeys" aspect) we may want to accept only Passkeys at first

First thing: https://shouldicapitalizepasskey.com

Sounds crazy to me, but if that's what people want to do with this it's easy enough to switch the logic for BE and BS so instead of true/false the policy can be allow/disallow/require for both registration and authentication flows with the defaults being the most "normal" allow but giving the RP admin the ability to ability for more strict control.

@iamcarbon
Copy link
Sponsor Contributor

@aseigler Anything else left here before we merge?

@aseigler
Copy link
Collaborator Author

aseigler commented Mar 4, 2023

@aseigler Anything else left here before we merge?

I think the only thing that hasn't been gotten to in the list was moving StoredCredential to CredentialRecord but that can be done separately, no need to hold this up for that.

@aseigler aseigler marked this pull request as ready for review March 4, 2023 03:35
@aseigler aseigler marked this pull request as draft March 6, 2023 13:22
Update BE/BS policy logic
Add more assertion tests, move more error strings to error messages
Get metadata into assertion flow for DPK
@aseigler aseigler marked this pull request as ready for review April 9, 2023 16:16
@Dnnzk
Copy link

Dnnzk commented May 6, 2023

Hey! Any news on this?

@abergs abergs mentioned this pull request Jun 8, 2023
Copy link
Collaborator

@abergs abergs left a comment

Choose a reason for hiding this comment

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

Great work on these two large features. I have some comments, but I'm happy with the changes.

_demoStorage.UpdateCounter(res.CredentialId, res.Counter);

if (res.DevicePublicKey is not null)
creds.DevicePublicKeys.Add(res.DevicePublicKey);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't understand what we are doing here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Basically if a new device public key was discovered in the assertion flow, add it to the list of device public keys associated with this credential.

Copy link
Collaborator

@abergs abergs Jun 26, 2023

Choose a reason for hiding this comment

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

Hmm... I think I understand @aseigler. Am I right to think that before saving the DPK the RP would perhaps check some policy or trigger a risk based ceremony before trusting that DPK and then saving/updating the storage with res.DevicePublicKey but in our demo we just add it directly to our in memory storage?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, basically same as registering a new credential against an existing user, but way more complicated.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Alright makes sense, I was just confused about how we used it in our demo. When I get the time I'll add a // TODO: Check policy if we allow new devices, if-so, store it to db

Id = authData.AttestedCredentialData.CredentialID,
PublicKey = authData.AttestedCredentialData.CredentialPublicKey.GetBytes(),
SignCount = authData.SignCount,
//Transports = result of response.getTransports();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why do we not return Transports anymore?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We didn't return transports before, in fact we didn't even collect it in the JavaScript at all, I added in this but it looks like it needs to be added to all of the other registration JavaScript files, and collect transports in AuthenticatorAttestationRawResponse.ResponseData, and then populate transports in the controller from the raw response. There are a few reasons to collect transports, to help with user facing dialogs, but also to compare what transports are being used against a policy, or comparing what transports should be available according to the authenticator metadata. Basically this is incomplete at the moment.

public byte[] UserId { get; set; }

public PublicKeyCredentialDescriptor Descriptor { get; set; }
Copy link
Collaborator

@abergs abergs Jun 21, 2023

Choose a reason for hiding this comment

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

Should we deprecate the descriptor or do we still have a use for it? (Now that we've added Id, PublicKey, Type to the class itself)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, I don't see a reason to store both Id and Descriptor.

/// <summary>
/// The Credential ID of the public key credential source.
/// </summary>
public byte[] Id { get; set; }
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we want to use the same JsonConverter as we do on PublicKey?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Already done in AuthenticatorAttestationRawResponse, no?

/// The value of the attestationObject attribute when the public key credential source was registered.
/// Storing this enables the Relying Party to reference the credential's attestation statement at a later time.
/// </summary>
public byte[] AttestationObject { get; set; }
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we want to use JsonConverter on this and AttestationClientDataJSON?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Already done in AuthenticatorAttestationRawResponse, no?

@abergs abergs merged commit 7b92f4a into passwordless-lib:master Jun 21, 2023
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Library lacks support for backup eligible passkeys and devicePublicKey extension
6 participants