diff --git a/src/WebAuthn.Net.Storage.MySql/WebAuthn.Net.Storage.MySql.csproj b/src/WebAuthn.Net.Storage.MySql/WebAuthn.Net.Storage.MySql.csproj
index bb4ab5a3..fafb23a3 100644
--- a/src/WebAuthn.Net.Storage.MySql/WebAuthn.Net.Storage.MySql.csproj
+++ b/src/WebAuthn.Net.Storage.MySql/WebAuthn.Net.Storage.MySql.csproj
@@ -32,6 +32,6 @@
-
+
diff --git a/src/WebAuthn.Net/Services/AuthenticationCeremony/Implementation/DefaultAuthenticationCeremonyService.cs b/src/WebAuthn.Net/Services/AuthenticationCeremony/Implementation/DefaultAuthenticationCeremonyService.cs
index dd971f11..8479d6b7 100644
--- a/src/WebAuthn.Net/Services/AuthenticationCeremony/Implementation/DefaultAuthenticationCeremonyService.cs
+++ b/src/WebAuthn.Net/Services/AuthenticationCeremony/Implementation/DefaultAuthenticationCeremonyService.cs
@@ -234,7 +234,7 @@ public DefaultAuthenticationCeremonyService(
protected IAuthenticationCeremonyCounters Counters { get; }
///
- public async Task BeginCeremonyAsync(
+ public virtual async Task BeginCeremonyAsync(
HttpContext httpContext,
BeginAuthenticationCeremonyRequest request,
CancellationToken cancellationToken)
@@ -294,7 +294,7 @@ public async Task BeginCeremonyAsync(
}
///
- public async Task> CompleteCeremonyAsync(
+ public virtual async Task> CompleteCeremonyAsync(
HttpContext httpContext,
CompleteAuthenticationCeremonyRequest request,
CancellationToken cancellationToken)
diff --git a/src/WebAuthn.Net/Services/RegistrationCeremony/Implementation/DefaultRegistrationCeremonyService.cs b/src/WebAuthn.Net/Services/RegistrationCeremony/Implementation/DefaultRegistrationCeremonyService.cs
index d94de201..32f164a6 100644
--- a/src/WebAuthn.Net/Services/RegistrationCeremony/Implementation/DefaultRegistrationCeremonyService.cs
+++ b/src/WebAuthn.Net/Services/RegistrationCeremony/Implementation/DefaultRegistrationCeremonyService.cs
@@ -18,8 +18,11 @@
using WebAuthn.Net.Models.Protocol.RegistrationCeremony.CreateCredential;
using WebAuthn.Net.Models.Protocol.RegistrationCeremony.CreateOptions;
using WebAuthn.Net.Services.Common.AttestationObjectDecoder;
+using WebAuthn.Net.Services.Common.AttestationObjectDecoder.Models;
using WebAuthn.Net.Services.Common.AttestationStatementDecoder.Abstractions;
+using WebAuthn.Net.Services.Common.AttestationStatementDecoder.Models;
using WebAuthn.Net.Services.Common.AttestationStatementVerifier.Abstractions;
+using WebAuthn.Net.Services.Common.AttestationStatementVerifier.Models.AttestationStatementVerifier;
using WebAuthn.Net.Services.Common.AttestationStatementVerifier.Models.Enums;
using WebAuthn.Net.Services.Common.AttestationTrustPathValidator;
using WebAuthn.Net.Services.Common.AuthenticatorDataDecoder;
@@ -263,12 +266,12 @@ public virtual async Task BeginCeremonyAsync(
var expiresAt = GetExpiresAtUtc(createdAt, timeout);
var options = CreatePublicKeyCredentialCreationOptions(request, timeout, rpId, challenge, credentialsToExclude);
var outputOptions = PublicKeyCredentialCreationOptionsEncoder.Encode(options);
- var registrationCeremony = new RegistrationCeremonyParameters(
+ var registrationCeremonyParameters = new RegistrationCeremonyParameters(
options,
expectedRpParameters,
createdAt,
expiresAt);
- var ceremonyId = await CeremonyStorage.SaveAsync(context, registrationCeremony, cancellationToken);
+ var ceremonyId = await CeremonyStorage.SaveAsync(context, registrationCeremonyParameters, cancellationToken);
await context.CommitAsync(cancellationToken);
var result = new BeginRegistrationCeremonyResult(outputOptions, ceremonyId);
Counters.IncrementBeginCeremonyEnd(true);
@@ -276,7 +279,10 @@ public virtual async Task BeginCeremonyAsync(
}
///
- public async Task> CompleteCeremonyAsync(HttpContext httpContext, CompleteRegistrationCeremonyRequest request, CancellationToken cancellationToken)
+ public virtual async Task> CompleteCeremonyAsync(
+ HttpContext httpContext,
+ CompleteRegistrationCeremonyRequest request,
+ CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(httpContext);
ArgumentNullException.ThrowIfNull(request);
@@ -285,11 +291,11 @@ public async Task> CompleteCeremonyAs
using (Logger.BeginCompleteRegistrationCeremonyScope(request.RegistrationCeremonyId))
await using (var context = await ContextFactory.CreateAsync(httpContext, cancellationToken))
{
- var registrationCeremonyOptions = await CeremonyStorage.FindAsync(
+ var registrationCeremonyParameters = await CeremonyStorage.FindAsync(
context,
request.RegistrationCeremonyId,
cancellationToken);
- if (registrationCeremonyOptions is null)
+ if (registrationCeremonyParameters is null)
{
Logger.RegistrationCeremonyNotFound();
Counters.IncrementCompleteCeremonyEnd(false);
@@ -298,7 +304,7 @@ public async Task> CompleteCeremonyAs
// https://www.w3.org/TR/2023/WD-webauthn-3-20230927/#sctn-registering-a-new-credential
// 1. Let 'options' be a new 'PublicKeyCredentialCreationOptions' structure configured to the Relying Party's needs for the ceremony.
- var options = registrationCeremonyOptions.Options;
+ var options = registrationCeremonyParameters.Options;
// 2. Call navigator.credentials.create() and pass 'options' as the 'publicKey' option.
// Let 'credential' be the result of the successfully resolved promise.
@@ -360,7 +366,7 @@ public async Task> CompleteCeremonyAs
}
// 9. Verify that the value of 'C.origin' is an origin expected by the Relying Party. See §13.4.9 Validating the origin of a credential for guidance.
- var allowedOrigin = registrationCeremonyOptions.ExpectedRp.Origins.FirstOrDefault(x => string.Equals(x, C.Origin, StringComparison.Ordinal));
+ var allowedOrigin = registrationCeremonyParameters.ExpectedRp.Origins.FirstOrDefault(x => string.Equals(x, C.Origin, StringComparison.Ordinal));
if (allowedOrigin is null)
{
Logger.InvalidOrigin(C.Origin);
@@ -373,7 +379,7 @@ public async Task> CompleteCeremonyAs
{
// 1. Verify that the Relying Party expects that this credential would have been created within an iframe that is not same-origin with its ancestors.
// 2. Verify that the value of C.topOrigin matches the origin of a page that the Relying Party expects to be sub-framed within. See §13.4.9 Validating the origin of a credential for guidance.
- if (!registrationCeremonyOptions.ExpectedRp.AllowIframe)
+ if (!registrationCeremonyParameters.ExpectedRp.AllowIframe)
{
if (!string.Equals(allowedOrigin, C.TopOrigin, StringComparison.Ordinal))
{
@@ -384,7 +390,7 @@ public async Task> CompleteCeremonyAs
}
else
{
- if (!registrationCeremonyOptions.ExpectedRp.TopOrigins.Any(x => string.Equals(x, C.TopOrigin, StringComparison.Ordinal)))
+ if (!registrationCeremonyParameters.ExpectedRp.TopOrigins.Any(x => string.Equals(x, C.TopOrigin, StringComparison.Ordinal)))
{
Logger.InvalidTopOrigin(C.TopOrigin);
Counters.IncrementCompleteCeremonyEnd(false);
@@ -446,7 +452,7 @@ public async Task> CompleteCeremonyAs
// 13. Verify that the 'rpIdHash' in 'authData' is the SHA-256 hash of the 'RP ID' expected by the Relying Party.
var authDataRpIdHash = authData.RpIdHash;
- var expectedRpIdHash = SHA256.HashData(Encoding.UTF8.GetBytes(registrationCeremonyOptions.ExpectedRp.RpId));
+ var expectedRpIdHash = SHA256.HashData(Encoding.UTF8.GetBytes(registrationCeremonyParameters.ExpectedRp.RpId));
if (!authDataRpIdHash.AsSpan().SequenceEqual(expectedRpIdHash.AsSpan()))
{
Logger.RpIdHashMismatch();
@@ -590,11 +596,16 @@ public async Task> CompleteCeremonyAs
currentBe,
currentBs,
response);
- var userCredentialRecord = new UserCredentialRecord(
- options.User.Id,
- registrationCeremonyOptions.ExpectedRp.RpId,
- request.Description,
- credentialRecord);
+ var userCredentialRecord = await CreateUserCredentialRecordAsync(
+ context,
+ registrationCeremonyParameters,
+ request,
+ credentialRecord,
+ attestationObjectResult.Ok,
+ authData,
+ attStmt,
+ attStmtVerification,
+ cancellationToken);
var credentialIdNotRegisteredForAnyUser = await CredentialStorage.SaveIfNotRegisteredForOtherUserAsync(
context,
userCredentialRecord,
@@ -749,7 +760,7 @@ protected virtual PublicKeyCredentialCreationOptions CreatePublicKeyCredentialCr
}
///
- /// Creates a , which is the final artifact of the registration ceremony.
+ /// Creates a that stores the properties of the registered public key.
///
/// PublicKeyCredential. The response received from the authenticator during the registration ceremony.
/// Authenticator Data (which has attestedCredentialData).
@@ -838,6 +849,55 @@ protected virtual CredentialRecord CreateCredentialRecord(
response.ClientDataJson);
return credentialRecord;
}
+
+ ///
+ /// Creates a , which is the final artifact of the registration ceremony.
+ ///
+ /// The context in which the WebAuthn operation is performed.
+ /// Registration ceremony parameters.
+ /// Request containing parameters for completing the registration ceremony.
+ /// that stores the properties of the registered public key.
+ /// Decoded attestation object.
+ /// Decoded value of authenticator data (authData).
+ /// Decoded value of attestation statement (attStmt).
+ /// Verified value of the attestation statement (attStmt).
+ /// Cancellation token for an asynchronous operation.
+ /// Instance of .
+ /// Any of the parameters is
+ ///
+ /// This method is mostly made to allow the override of any properties of the resulting before it is saved to the database.
+ /// For example, you can set the description of the registering public key depending on the type of attestation.
+ /// The asynchronous signature of this method is made for flexibility.
+ /// The saving of the itself is performed in the next step.
+ /// Please don't save it to the database in this method.
+ ///
+ protected virtual Task CreateUserCredentialRecordAsync(
+ TContext context,
+ RegistrationCeremonyParameters registrationCeremonyParameters,
+ CompleteRegistrationCeremonyRequest request,
+ CredentialRecord credentialRecord,
+ AttestationObject attestationObject,
+ AttestedAuthenticatorData authData,
+ AbstractAttestationStatement attStmt,
+ VerifiedAttestationStatement verifiedAttestationStatement,
+ CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ ArgumentNullException.ThrowIfNull(context);
+ ArgumentNullException.ThrowIfNull(registrationCeremonyParameters);
+ ArgumentNullException.ThrowIfNull(request);
+ ArgumentNullException.ThrowIfNull(credentialRecord);
+ ArgumentNullException.ThrowIfNull(attestationObject);
+ ArgumentNullException.ThrowIfNull(authData);
+ ArgumentNullException.ThrowIfNull(attStmt);
+ ArgumentNullException.ThrowIfNull(verifiedAttestationStatement);
+ var result = new UserCredentialRecord(
+ registrationCeremonyParameters.Options.User.Id,
+ registrationCeremonyParameters.ExpectedRp.RpId,
+ request.Description,
+ credentialRecord);
+ return Task.FromResult(result);
+ }
}
///
diff --git a/src/WebAuthn.Net/Storage/RegistrationCeremony/IRegistrationCeremonyStorage.cs b/src/WebAuthn.Net/Storage/RegistrationCeremony/IRegistrationCeremonyStorage.cs
index 3963cdef..3dadb445 100644
--- a/src/WebAuthn.Net/Storage/RegistrationCeremony/IRegistrationCeremonyStorage.cs
+++ b/src/WebAuthn.Net/Storage/RegistrationCeremony/IRegistrationCeremonyStorage.cs
@@ -16,12 +16,12 @@ public interface IRegistrationCeremonyStorage
/// Saves the parameters of the specified registration ceremony and returns the unique identifier of the saved record.
///
/// The context in which the WebAuthn operation is performed.
- /// Registration ceremony parameters.
+ /// Registration ceremony parameters.
/// Cancellation token for an asynchronous operation.
///
Task SaveAsync(
TContext context,
- RegistrationCeremonyParameters registrationCeremony,
+ RegistrationCeremonyParameters registrationCeremonyParameters,
CancellationToken cancellationToken);
///
diff --git a/src/WebAuthn.Net/Storage/RegistrationCeremony/Implementation/DefaultCookieRegistrationCeremonyStorage.cs b/src/WebAuthn.Net/Storage/RegistrationCeremony/Implementation/DefaultCookieRegistrationCeremonyStorage.cs
index 77fdf82b..aad46ded 100644
--- a/src/WebAuthn.Net/Storage/RegistrationCeremony/Implementation/DefaultCookieRegistrationCeremonyStorage.cs
+++ b/src/WebAuthn.Net/Storage/RegistrationCeremony/Implementation/DefaultCookieRegistrationCeremonyStorage.cs
@@ -69,14 +69,14 @@ public DefaultCookieRegistrationCeremonyStorage(
///
public virtual Task SaveAsync(
TContext context,
- RegistrationCeremonyParameters registrationCeremony,
+ RegistrationCeremonyParameters registrationCeremonyParameters,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(context);
cancellationToken.ThrowIfCancellationRequested();
var options = Options.CurrentValue;
var id = Guid.NewGuid().ToString("N").ToLowerInvariant();
- var container = new RegistrationCeremonyParametersCookieContainer(id, registrationCeremony);
+ var container = new RegistrationCeremonyParametersCookieContainer(id, registrationCeremonyParameters);
var jsonBytesResult = SafeJsonSerializer.SerializeToUtf8Bytes(container, options.SerializerOptions);
if (jsonBytesResult.HasError)
{
diff --git a/tests/WebAuthn.Net.Tests.Unit/DSL/Fakes/Storage/FakeRegistrationCeremonyStorage.cs b/tests/WebAuthn.Net.Tests.Unit/DSL/Fakes/Storage/FakeRegistrationCeremonyStorage.cs
index 3b3fbf2d..53cd77e4 100644
--- a/tests/WebAuthn.Net.Tests.Unit/DSL/Fakes/Storage/FakeRegistrationCeremonyStorage.cs
+++ b/tests/WebAuthn.Net.Tests.Unit/DSL/Fakes/Storage/FakeRegistrationCeremonyStorage.cs
@@ -15,14 +15,14 @@ public class FakeRegistrationCeremonyStorage : IRegistrationCeremonyStorage SaveAsync(
FakeWebAuthnContext context,
- RegistrationCeremonyParameters registrationCeremony,
+ RegistrationCeremonyParameters registrationCeremonyParameters,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var id = Guid.NewGuid().ToString("N");
lock (_locker)
{
- _registrationCeremonies[id] = registrationCeremony;
+ _registrationCeremonies[id] = registrationCeremonyParameters;
}
return Task.FromResult(id);