From dc04ec39629f4304fbd0dd97d50e6bdf0a24458f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85berg?= Date: Tue, 29 Oct 2024 14:47:12 +0100 Subject: [PATCH 1/4] RequestNewCredentialParams --- .../Server/Controllers/UserController.cs | 15 ++++---- Demo/Controller.cs | 2 +- Demo/TestController.cs | 11 +++++- Src/Fido2.Models/CredentialCreateOptions.cs | 3 ++ Src/Fido2/Fido2.cs | 32 ++-------------- Src/Fido2/IFido2.cs | 12 +----- Src/Fido2/RequestNewCredentialParams.cs | 37 +++++++++++++++++++ 7 files changed, 63 insertions(+), 49 deletions(-) create mode 100644 Src/Fido2/RequestNewCredentialParams.cs diff --git a/BlazorWasmDemo/Server/Controllers/UserController.cs b/BlazorWasmDemo/Server/Controllers/UserController.cs index 1ec1719e..27931826 100644 --- a/BlazorWasmDemo/Server/Controllers/UserController.cs +++ b/BlazorWasmDemo/Server/Controllers/UserController.cs @@ -103,12 +103,13 @@ public CredentialCreateOptions GetCredentialOptions( } // 4. Create options - var options = _fido2.RequestNewCredential( - user, - existingKeys, - authenticatorSelection, - attestationType ?? AttestationConveyancePreference.None, - new AuthenticationExtensionsClientInputs + var options = _fido2.RequestNewCredential(new RequestNewCredentialParams + { + User = user, + ExcludeCredentials = existingKeys, + AuthenticatorSelection = authenticatorSelection, + AttestationPreference = attestationType ?? AttestationConveyancePreference.None, + Extensions = new AuthenticationExtensionsClientInputs { Extensions = true, UserVerificationMethod = true, @@ -118,7 +119,7 @@ public CredentialCreateOptions GetCredentialOptions( Attestation = attestationType?.ToString() ?? AttestationConveyancePreference.None.ToString() }, } - ); + }); // 5. Temporarily store options, session/in-memory cache/redis/db _pendingCredentials[key] = options; diff --git a/Demo/Controller.cs b/Demo/Controller.cs index b0a0273b..76e6e661 100644 --- a/Demo/Controller.cs +++ b/Demo/Controller.cs @@ -71,7 +71,7 @@ public JsonResult MakeCredentialOptions([FromForm] string username, CredProps = true }; - var options = _fido2.RequestNewCredential(user, existingKeys, authenticatorSelection, attType.ToEnum(), exts); + var options = _fido2.RequestNewCredential(new RequestNewCredentialParams { User = user, ExcludeCredentials = existingKeys, AuthenticatorSelection = authenticatorSelection, AttestationPreference = attType.ToEnum(), Extensions = exts }); // 4. Temporarily store options, session/in-memory cache/redis/db HttpContext.Session.SetString("fido2.attestationOptions", options.ToJson()); diff --git a/Demo/TestController.cs b/Demo/TestController.cs index 2ccf4fdc..740d8e9e 100644 --- a/Demo/TestController.cs +++ b/Demo/TestController.cs @@ -66,8 +66,15 @@ public OkObjectResult MakeCredentialOptionsTest([FromBody] TEST_MakeCredentialPa exts.Example = opts.Extensions.Example; // 3. Create options - var options = _fido2.RequestNewCredential(user, existingKeys, opts.AuthenticatorSelection, opts.Attestation, exts); - + var options = _fido2.RequestNewCredential(new RequestNewCredentialParams + { + User = user, + ExcludeCredentials = existingKeys, + AuthenticatorSelection = opts.AuthenticatorSelection, + AttestationPreference = opts.Attestation, + Extensions = exts + }); + // 4. Temporarily store options, session/in-memory cache/redis/db HttpContext.Session.SetString("fido2.attestationOptions", options.ToJson()); diff --git a/Src/Fido2.Models/CredentialCreateOptions.cs b/Src/Fido2.Models/CredentialCreateOptions.cs index 0bd5db4d..ddad5db4 100644 --- a/Src/Fido2.Models/CredentialCreateOptions.cs +++ b/Src/Fido2.Models/CredentialCreateOptions.cs @@ -285,6 +285,9 @@ public bool RequireResidentKey }; } +/// +/// +/// public class Fido2User { /// diff --git a/Src/Fido2/Fido2.cs b/Src/Fido2/Fido2.cs index 665252a8..52e02514 100644 --- a/Src/Fido2/Fido2.cs +++ b/Src/Fido2/Fido2.cs @@ -26,37 +26,13 @@ public Fido2( /// /// Returns CredentialCreateOptions including a challenge to be sent to the browser/authenticator to create new credentials. /// - /// - /// Recommended. This member is intended for use by Relying Parties that wish to limit the creation of multiple credentials for the same account on a single authenticator. The client is requested to return an error if the new credential would be created on an authenticator that also contains one of the credentials enumerated in this parameter. - /// - /// - public CredentialCreateOptions RequestNewCredential( - Fido2User user, - IReadOnlyList excludeCredentials, - AuthenticationExtensionsClientInputs? extensions = null) - { - return RequestNewCredential(user, excludeCredentials, AuthenticatorSelection.Default, AttestationConveyancePreference.None, extensions); - } - - /// - /// Returns CredentialCreateOptions including a challenge to be sent to the browser/authenticator to create new credentials. - /// - /// - /// Recommended. This member is intended for use by Relying Parties that wish to limit the creation of multiple credentials for the same account on a single authenticator. The client is requested to return an error if the new credential would be created on an authenticator that also contains one of the credentials enumerated in this parameter. - /// - /// This member is intended for use by Relying Parties that wish to express their preference for attestation conveyance. The default is none. - /// + /// The input arguments for generating CredentialCreateOptions /// - public CredentialCreateOptions RequestNewCredential( - Fido2User user, - IReadOnlyList excludeCredentials, - AuthenticatorSelection authenticatorSelection, - AttestationConveyancePreference attestationPreference, - AuthenticationExtensionsClientInputs? extensions = null) + public CredentialCreateOptions RequestNewCredential(RequestNewCredentialParams requestNewCredentialParams) { - byte[] challenge = RandomNumberGenerator.GetBytes(_config.ChallengeSize); + var challenge = RandomNumberGenerator.GetBytes(_config.ChallengeSize); + return CredentialCreateOptions.Create(_config, challenge, requestNewCredentialParams.User, requestNewCredentialParams.AuthenticatorSelection, requestNewCredentialParams.AttestationPreference, requestNewCredentialParams.ExcludeCredentials, requestNewCredentialParams.Extensions); - return CredentialCreateOptions.Create(_config, challenge, user, authenticatorSelection, attestationPreference, excludeCredentials, extensions); } /// diff --git a/Src/Fido2/IFido2.cs b/Src/Fido2/IFido2.cs index e22035c4..846c36e6 100644 --- a/Src/Fido2/IFido2.cs +++ b/Src/Fido2/IFido2.cs @@ -19,15 +19,5 @@ Task MakeAssertionAsync(MakeAssertionParams makeAssertion Task MakeNewCredentialAsync(MakeNewCredentialParams makeNewCredentialParams, CancellationToken cancellationToken = default); - CredentialCreateOptions RequestNewCredential( - Fido2User user, - IReadOnlyList excludeCredentials, - AuthenticationExtensionsClientInputs? extensions = null); - - CredentialCreateOptions RequestNewCredential( - Fido2User user, - IReadOnlyList excludeCredentials, - AuthenticatorSelection authenticatorSelection, - AttestationConveyancePreference attestationPreference, - AuthenticationExtensionsClientInputs? extensions = null); + CredentialCreateOptions RequestNewCredential(RequestNewCredentialParams requestNewCredentialParams); } diff --git a/Src/Fido2/RequestNewCredentialParams.cs b/Src/Fido2/RequestNewCredentialParams.cs new file mode 100644 index 00000000..43c7d946 --- /dev/null +++ b/Src/Fido2/RequestNewCredentialParams.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using Fido2NetLib.Objects; + +namespace Fido2NetLib; + +/// +/// The input arguments for generating CredentialCreateOptions +/// +public sealed class RequestNewCredentialParams +{ + /// + /// This member contains names and an identifier for the user account performing the registration. Its value’s name, displayName and id members are REQUIRED. id can be returned as the userHandle in some future authentication ceremonies, and is used to overwrite existing discoverable credentials that have the same rp.id and user.id on the same authenticator. name and displayName MAY be used by the authenticator and client in future authentication ceremonies to help the user select a credential, but are not returned to the Relying Party as a result of future authentication ceremonies + /// + public required Fido2User User { get; init; } + + /// + /// The Relying Party SHOULD use this OPTIONAL member to list any existing credentials mapped to this user account (as identified by user.id). This ensures that the new credential is not created on an authenticator that already contains a credential mapped to this user account. If it would be, the client is requested to instead guide the user to use a different authenticator, or return an error if that fails. + /// + public IReadOnlyList ExcludeCredentials { get; init; } = + Array.Empty(); + + /// + /// The Relying Party MAY use this OPTIONAL member to specify capabilities and settings that the authenticator MUST or SHOULD satisfy to participate in the create() operation. See § 5.4.4 Authenticator Selection Criteria (dictionary AuthenticatorSelectionCriteria). + /// + public AuthenticatorSelection AuthenticatorSelection { get; init; } = AuthenticatorSelection.Default; + + /// + /// The Relying Party MAY use this OPTIONAL member to specify a preference regarding attestation conveyance. Its value SHOULD be a member of AttestationConveyancePreference. Client platforms MUST ignore unknown values, treating an unknown value as if the member does not exist. + /// + public AttestationConveyancePreference AttestationPreference { get; init; } = AttestationConveyancePreference.None; + + /// + /// The Relying Party MAY use this OPTIONAL member to provide client extension inputs requesting additional processing by the client and authenticator. For example, the Relying Party may request that the client returns additional information about the credential that was created. + /// + public AuthenticationExtensionsClientInputs? Extensions { get; init; } +} From bf65a9ab6945f24d49636127a9f90630d5a0aec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85berg?= Date: Tue, 29 Oct 2024 15:54:59 +0100 Subject: [PATCH 2/4] Create wrapper objects for GetAssertionOptinos --- .../Server/Controllers/UserController.cs | 10 +++--- Demo/Controller.cs | 11 +++--- Demo/TestController.cs | 11 +++--- Src/Fido2/Fido2.cs | 11 ++++-- Src/Fido2/GetAssertionOptionsParams.cs | 35 +++++++++++++++++++ Src/Fido2/IFido2.cs | 5 +-- 6 files changed, 62 insertions(+), 21 deletions(-) create mode 100644 Src/Fido2/GetAssertionOptionsParams.cs diff --git a/BlazorWasmDemo/Server/Controllers/UserController.cs b/BlazorWasmDemo/Server/Controllers/UserController.cs index 27931826..15b15b05 100644 --- a/BlazorWasmDemo/Server/Controllers/UserController.cs +++ b/BlazorWasmDemo/Server/Controllers/UserController.cs @@ -219,10 +219,12 @@ public AssertionOptions MakeAssertionOptions([FromRoute] string? username, [From }; // 2. Create options (usernameless users will be prompted by their device to select a credential from their own list) - var options = _fido2.GetAssertionOptions( - existingKeys, - userVerification ?? UserVerificationRequirement.Discouraged, - exts); + var options = _fido2.GetAssertionOptions(new GetAssertionOptionsParams + { + AllowedCredentials = existingKeys, + UserVerification = userVerification ?? UserVerificationRequirement.Discouraged, + Extensions = exts + }); // 4. Temporarily store options, session/in-memory cache/redis/db _pendingAssertions[new string(options.Challenge.Select(b => (char)b).ToArray())] = options; diff --git a/Demo/Controller.cs b/Demo/Controller.cs index 76e6e661..8ce16280 100644 --- a/Demo/Controller.cs +++ b/Demo/Controller.cs @@ -166,11 +166,12 @@ public ActionResult AssertionOptionsPost([FromForm] string username, [FromForm] // 3. Create options var uv = string.IsNullOrEmpty(userVerification) ? UserVerificationRequirement.Discouraged : userVerification.ToEnum(); - var options = _fido2.GetAssertionOptions( - existingCredentials, - uv, - exts - ); + var options = _fido2.GetAssertionOptions(new GetAssertionOptionsParams() + { + AllowedCredentials = existingCredentials, + UserVerification = uv, + Extensions = exts + }); // 4. Temporarily store options, session/in-memory cache/redis/db HttpContext.Session.SetString("fido2.assertionOptions", options.ToJson()); diff --git a/Demo/TestController.cs b/Demo/TestController.cs index 740d8e9e..72c4efff 100644 --- a/Demo/TestController.cs +++ b/Demo/TestController.cs @@ -151,11 +151,12 @@ public IActionResult AssertionOptionsTest([FromBody] TEST_AssertionClientParams exts.Example = assertionClientParams.Extensions.Example; // 3. Create options - var options = _fido2.GetAssertionOptions( - existingCredentials, - uv, - exts - ); + var options = _fido2.GetAssertionOptions(new GetAssertionOptionsParams + { + AllowedCredentials = existingCredentials, + UserVerification = uv, + Extensions = exts + }); // 4. Temporarily store options, session/in-memory cache/redis/db HttpContext.Session.SetString("fido2.assertionOptions", options.ToJson()); diff --git a/Src/Fido2/Fido2.cs b/Src/Fido2/Fido2.cs index 52e02514..5618be1e 100644 --- a/Src/Fido2/Fido2.cs +++ b/Src/Fido2/Fido2.cs @@ -53,10 +53,15 @@ public async Task MakeNewCredentialAsync(MakeNewC /// /// Returns AssertionOptions including a challenge to the browser/authenticator to assert existing credentials and authenticate a user. /// - /// - /// - /// + /// The input arguments for generating AssertionOptions /// + public AssertionOptions GetAssertionOptions(GetAssertionOptionsParams getAssertionOptionsParams) + { + byte[] challenge = RandomNumberGenerator.GetBytes(_config.ChallengeSize); + + return AssertionOptions.Create(_config, challenge, getAssertionOptionsParams.AllowedCredentials, getAssertionOptionsParams.UserVerification, getAssertionOptionsParams.Extensions); + } + public AssertionOptions GetAssertionOptions( IReadOnlyList allowedCredentials, UserVerificationRequirement? userVerification, diff --git a/Src/Fido2/GetAssertionOptionsParams.cs b/Src/Fido2/GetAssertionOptionsParams.cs new file mode 100644 index 00000000..234a8527 --- /dev/null +++ b/Src/Fido2/GetAssertionOptionsParams.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using Fido2NetLib.Objects; + +namespace Fido2NetLib; + +/// +/// The input arguments for generating AssertionOptions +/// +public sealed class GetAssertionOptionsParams +{ + /// + /// This OPTIONAL member is used by the client to find authenticators eligible for this authentication ceremony. It can be used in two ways: + /// + /// * If the user account to authenticate is already identified (e.g., if the user has entered a username), then the Relying Party SHOULD use this member to list credential descriptors for credential records in the user account. This SHOULD usually include all credential records in the user account. + /// The items SHOULD specify transports whenever possible. This helps the client optimize the user experience for any given situation. Also note that the Relying Party does not need to filter the list when requesting user verification — the client will automatically ignore non-eligible credentials if userVerification is set to required. + /// See also the § 14.6.3 Privacy leak via credential IDs privacy consideration. + /// * If the user account to authenticate is not already identified, then the Relying Party MAY leave this member empty or unspecified. In this case, only discoverable credentials will be utilized in this authentication ceremony, and the user account MAY be identified by the userHandle of the resulting AuthenticatorAssertionResponse. If the available authenticators contain more than one discoverable credential scoped to the Relying Party, the credentials are displayed by the client platform or authenticator for the user to select from (see step 7 of § 6.3.3 The authenticatorGetAssertion Operation). + /// + /// If not empty, the client MUST return an error if none of the listed credentials can be used. + /// + /// The list is ordered in descending order of preference: the first item in the list is the most preferred credential, and the last is the least preferred. + /// + public IReadOnlyList AllowedCredentials { get; init; } = Array.Empty(); + + /// + /// This OPTIONAL member specifies the Relying Party's requirements regarding user verification for the get() operation. The value SHOULD be a member of UserVerificationRequirement but client platforms MUST ignore unknown values, treating an unknown value as if the member does not exist. Eligible authenticators are filtered to only those capable of satisfying this requirement. + /// + public UserVerificationRequirement? UserVerification { get; init; } + + /// + /// The Relying Party MAY use this OPTIONAL member to provide client extension inputs requesting additional processing by the client and authenticator. + /// + public AuthenticationExtensionsClientInputs? Extensions { get; init; } +} diff --git a/Src/Fido2/IFido2.cs b/Src/Fido2/IFido2.cs index 846c36e6..8d18ce4c 100644 --- a/Src/Fido2/IFido2.cs +++ b/Src/Fido2/IFido2.cs @@ -8,10 +8,7 @@ namespace Fido2NetLib; public interface IFido2 { - AssertionOptions GetAssertionOptions( - IReadOnlyList allowedCredentials, - UserVerificationRequirement? userVerification, - AuthenticationExtensionsClientInputs? extensions = null); + AssertionOptions GetAssertionOptions(GetAssertionOptionsParams getAssertionOptionsParams); Task MakeAssertionAsync(MakeAssertionParams makeAssertionParams, CancellationToken cancellationToken = default); From 3546e5c0354705e1d338cbfe93e1a96a538ebd6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85berg?= Date: Tue, 29 Oct 2024 20:11:11 +0100 Subject: [PATCH 3/4] format --- Demo/TestController.cs | 6 +++--- Src/Fido2/GetAssertionOptionsParams.cs | 4 ++-- Src/Fido2/RequestNewCredentialParams.cs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Demo/TestController.cs b/Demo/TestController.cs index 72c4efff..317ce7aa 100644 --- a/Demo/TestController.cs +++ b/Demo/TestController.cs @@ -73,9 +73,9 @@ public OkObjectResult MakeCredentialOptionsTest([FromBody] TEST_MakeCredentialPa AuthenticatorSelection = opts.AuthenticatorSelection, AttestationPreference = opts.Attestation, Extensions = exts - }); - - // 4. Temporarily store options, session/in-memory cache/redis/db + }); + + // 4. Temporarily store options, session/in-memory cache/redis/db HttpContext.Session.SetString("fido2.attestationOptions", options.ToJson()); // 5. return options to client diff --git a/Src/Fido2/GetAssertionOptionsParams.cs b/Src/Fido2/GetAssertionOptionsParams.cs index 234a8527..67ab69fe 100644 --- a/Src/Fido2/GetAssertionOptionsParams.cs +++ b/Src/Fido2/GetAssertionOptionsParams.cs @@ -22,12 +22,12 @@ public sealed class GetAssertionOptionsParams /// The list is ordered in descending order of preference: the first item in the list is the most preferred credential, and the last is the least preferred. /// public IReadOnlyList AllowedCredentials { get; init; } = Array.Empty(); - + /// /// This OPTIONAL member specifies the Relying Party's requirements regarding user verification for the get() operation. The value SHOULD be a member of UserVerificationRequirement but client platforms MUST ignore unknown values, treating an unknown value as if the member does not exist. Eligible authenticators are filtered to only those capable of satisfying this requirement. /// public UserVerificationRequirement? UserVerification { get; init; } - + /// /// The Relying Party MAY use this OPTIONAL member to provide client extension inputs requesting additional processing by the client and authenticator. /// diff --git a/Src/Fido2/RequestNewCredentialParams.cs b/Src/Fido2/RequestNewCredentialParams.cs index 43c7d946..13877c75 100644 --- a/Src/Fido2/RequestNewCredentialParams.cs +++ b/Src/Fido2/RequestNewCredentialParams.cs @@ -24,12 +24,12 @@ public sealed class RequestNewCredentialParams /// The Relying Party MAY use this OPTIONAL member to specify capabilities and settings that the authenticator MUST or SHOULD satisfy to participate in the create() operation. See § 5.4.4 Authenticator Selection Criteria (dictionary AuthenticatorSelectionCriteria). /// public AuthenticatorSelection AuthenticatorSelection { get; init; } = AuthenticatorSelection.Default; - + /// /// The Relying Party MAY use this OPTIONAL member to specify a preference regarding attestation conveyance. Its value SHOULD be a member of AttestationConveyancePreference. Client platforms MUST ignore unknown values, treating an unknown value as if the member does not exist. /// public AttestationConveyancePreference AttestationPreference { get; init; } = AttestationConveyancePreference.None; - + /// /// The Relying Party MAY use this OPTIONAL member to provide client extension inputs requesting additional processing by the client and authenticator. For example, the Relying Party may request that the client returns additional information about the credential that was created. /// From a2e7efef5c9a3174cbd62ed8d94d4d0cd6b47f6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85berg?= Date: Tue, 29 Oct 2024 20:25:17 +0100 Subject: [PATCH 4/4] remove empty comment --- Src/Fido2.Models/CredentialCreateOptions.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Src/Fido2.Models/CredentialCreateOptions.cs b/Src/Fido2.Models/CredentialCreateOptions.cs index ddad5db4..0bd5db4d 100644 --- a/Src/Fido2.Models/CredentialCreateOptions.cs +++ b/Src/Fido2.Models/CredentialCreateOptions.cs @@ -285,9 +285,6 @@ public bool RequireResidentKey }; } -/// -/// -/// public class Fido2User { ///