Skip to content

Commit af27faf

Browse files
committed
Move core passkey implementation to Microsoft.AspNetCore.Identity
1 parent 5d3b864 commit af27faf

File tree

57 files changed

+450
-787
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+450
-787
lines changed

src/Identity/Extensions.Core/src/DefaultPasskeyHandler.cs renamed to src/Identity/Core/src/DefaultPasskeyHandler.cs

Lines changed: 10 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System;
5-
using System.Buffers.Binary;
6-
using System.Collections.Generic;
74
using System.Diagnostics;
85
using System.Linq;
96
using System.Security.Cryptography;
107
using System.Text;
118
using System.Text.Json;
12-
using System.Threading.Tasks;
13-
using Microsoft.Extensions.Logging;
149
using Microsoft.Extensions.Options;
1510

1611
namespace Microsoft.AspNetCore.Identity;
@@ -22,7 +17,7 @@ public sealed partial class DefaultPasskeyHandler<TUser> : IPasskeyHandler<TUser
2217
where TUser : class
2318
{
2419
private readonly IPasskeyOriginValidator _originValidator;
25-
private readonly IPasskeyAttestationStatementVerifier? _attestationStatementVerifier;
20+
private readonly IPasskeyAttestationStatementVerifier _attestationStatementVerifier;
2621
private readonly PasskeyOptions _passkeyOptions;
2722

2823
/// <summary>
@@ -34,7 +29,7 @@ public sealed partial class DefaultPasskeyHandler<TUser> : IPasskeyHandler<TUser
3429
public DefaultPasskeyHandler(
3530
IOptions<IdentityOptions> options,
3631
IPasskeyOriginValidator originValidator,
37-
IPasskeyAttestationStatementVerifier? attestationStatementVerifier = null)
32+
IPasskeyAttestationStatementVerifier attestationStatementVerifier)
3833
{
3934
_originValidator = originValidator;
4035
_attestationStatementVerifier = attestationStatementVerifier;
@@ -151,7 +146,7 @@ private async Task<PasskeyAttestationResult> PerformAttestationCoreAsync(
151146
}
152147

153148
// 12. Let clientDataHash be the result of computing a hash over response.clientDataJSON using SHA-256.
154-
var clientDataHash = ComputeSHA256Hash(response.ClientDataJSON);
149+
var clientDataHash = SHA256.HashData(response.ClientDataJSON.AsSpan());
155150

156151
// 13. Perform CBOR decoding on the attestationObject field of the AuthenticatorAttestationResponse structure and obtain the
157152
// the authenticator data authenticatorData.
@@ -166,7 +161,7 @@ private async Task<PasskeyAttestationResult> PerformAttestationCoreAsync(
166161
}
167162

168163
// 14. Verify that the rpIdHash in authenticatorData is the SHA-256 hash of the RP ID expected by the Relying Party.
169-
var rpIdHash = ComputeSHA256Hash(Encoding.UTF8.GetBytes(originalOptions.Rp.Id ?? string.Empty));
164+
var rpIdHash = SHA256.HashData(Encoding.UTF8.GetBytes(originalOptions.Rp.Id ?? string.Empty));
170165
if (!authenticatorData.RpIdHash.Span.SequenceEqual(rpIdHash.AsSpan()))
171166
{
172167
throw PasskeyException.InvalidRelyingPartyIDHash();
@@ -230,14 +225,11 @@ private async Task<PasskeyAttestationResult> PerformAttestationCoreAsync(
230225

231226
// 21-24. Determine the attestation statement format by performing a USASCII case-sensitive match on fmt against the set of supported WebAuthn
232227
// Attestation Statement Format Identifier values...
233-
if (_attestationStatementVerifier is not null)
228+
// Handles all validation related to the attestation statement (21-24).
229+
var isAttestationStatementValid = await _attestationStatementVerifier.VerifyAsync(attestationObjectMemory, clientDataHash).ConfigureAwait(false);
230+
if (!isAttestationStatementValid)
234231
{
235-
// Handles all validation related to the attestation statement (21-24).
236-
var isAttestationStatementValid = await _attestationStatementVerifier.VerifyAsync(attestationObjectMemory, clientDataHash).ConfigureAwait(false);
237-
if (!isAttestationStatementValid)
238-
{
239-
throw PasskeyException.InvalidAttestationStatement();
240-
}
232+
throw PasskeyException.InvalidAttestationStatement();
241233
}
242234

243235
// 25. Verify that the credentialId is <= 1023 bytes.
@@ -410,7 +402,7 @@ private async Task<PasskeyAssertionResult<TUser>> PerformAssertionCoreAsync(
410402
}
411403

412404
// 15. Verify that the rpIdHash in authData is the SHA-256 hash of the RP ID expected by the Relying Party.
413-
var rpIdHash = ComputeSHA256Hash(Encoding.UTF8.GetBytes(originalOptions.RpId ?? string.Empty));
405+
var rpIdHash = SHA256.HashData(Encoding.UTF8.GetBytes(originalOptions.RpId ?? string.Empty));
414406
if (!authenticatorData.RpIdHash.Span.SequenceEqual(rpIdHash.AsSpan()))
415407
{
416408
throw PasskeyException.InvalidRelyingPartyIDHash();
@@ -459,7 +451,7 @@ private async Task<PasskeyAssertionResult<TUser>> PerformAssertionCoreAsync(
459451
}
460452

461453
// 20. Let clientDataHash be the result of computing a hash over the cData using SHA-256.
462-
var clientDataHash = ComputeSHA256Hash(response.ClientDataJSON);
454+
var clientDataHash = SHA256.HashData(response.ClientDataJSON.AsSpan());
463455

464456
// 21. Using credentialRecord.publicKey, verify that sig is a valid signature over the binary concatenation of authData and hash.
465457
byte[] data = [.. response.AuthenticatorData.AsSpan(), .. clientDataHash];
@@ -502,24 +494,4 @@ private async Task<PasskeyAssertionResult<TUser>> PerformAssertionCoreAsync(
502494
// 25. If all the above steps are successful, continue the authentication ceremony as appropriate.
503495
return PasskeyAssertionResult.Success(storedPasskey, user);
504496
}
505-
506-
private static byte[] ComputeSHA256Hash(byte[] data)
507-
{
508-
#if NETCOREAPP
509-
return SHA256.HashData(data);
510-
#else
511-
using var sha256 = SHA256.Create();
512-
return sha256.ComputeHash(data);
513-
#endif
514-
}
515-
516-
private static byte[] ComputeSHA256Hash(BufferSource data)
517-
{
518-
#if NETCOREAPP
519-
return SHA256.HashData(data.AsSpan());
520-
#else
521-
using var sha256 = SHA256.Create();
522-
return sha256.ComputeHash(data.ToArray());
523-
#endif
524-
}
525497
}

src/Identity/Extensions.Core/src/DefaultPasskeyOriginValidator.cs renamed to src/Identity/Core/src/DefaultPasskeyOriginValidator.cs

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,27 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System;
5-
using System.Collections.Generic;
6-
using System.Linq;
7-
using System.Runtime.CompilerServices;
8-
using System.Text;
9-
using System.Threading.Tasks;
4+
using Microsoft.AspNetCore.Http;
105
using Microsoft.Extensions.Options;
116

127
namespace Microsoft.AspNetCore.Identity;
138

14-
/// <summary>
15-
/// The default passkey origin validator.
16-
/// </summary>
17-
public sealed class DefaultPasskeyOriginValidator : IPasskeyOriginValidator
9+
internal class DefaultPasskeyOriginValidator : IPasskeyOriginValidator
1810
{
19-
private readonly IPasskeyRequestContextProvider _requestContextProvider;
11+
private readonly IHttpContextAccessor _httpContextAccessor;
2012
private readonly PasskeyOptions _options;
2113

22-
/// <summary>
23-
/// Constructs a new <see cref="DefaultPasskeyOriginValidator"/>.
24-
/// </summary>
2514
public DefaultPasskeyOriginValidator(
26-
IPasskeyRequestContextProvider requestContextProvider,
15+
IHttpContextAccessor httpContextAccessor,
2716
IOptions<IdentityOptions> options)
2817
{
29-
_requestContextProvider = requestContextProvider;
18+
ArgumentNullException.ThrowIfNull(httpContextAccessor);
19+
ArgumentNullException.ThrowIfNull(options);
20+
21+
_httpContextAccessor = httpContextAccessor;
3022
_options = options.Value.Passkey;
3123
}
3224

33-
/// <inheritdoc/>
3425
public bool IsValidOrigin(PasskeyOriginInfo originInfo)
3526
{
3627
if (string.IsNullOrEmpty(originInfo.Origin))
@@ -59,12 +50,10 @@ public bool IsValidOrigin(PasskeyOriginInfo originInfo)
5950
}
6051
}
6152

62-
if (_options.AllowCurrentOrigin)
53+
if (_options.AllowCurrentOrigin && _httpContextAccessor.HttpContext?.Request.Headers.Origin is [var origin])
6354
{
64-
var context = _requestContextProvider.Context;
65-
6655
// Uri.Equals correctly handles string comparands.
67-
if (originUri.Equals(context.Origin))
56+
if (originUri.Equals(origin))
6857
{
6958
return true;
7059
}

src/Identity/Core/src/EventIds.cs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ namespace Microsoft.AspNetCore.Identity;
77

88
internal static class EventIds
99
{
10-
public static EventId UserCannotSignInWithoutConfirmedEmail = new EventId(0, "UserCannotSignInWithoutConfirmedEmail");
11-
public static EventId SecurityStampValidationFailed = new EventId(0, "SecurityStampValidationFailed");
12-
public static EventId SecurityStampValidationFailedId4 = new EventId(4, "SecurityStampValidationFailed");
13-
public static EventId UserCannotSignInWithoutConfirmedPhoneNumber = new EventId(1, "UserCannotSignInWithoutConfirmedPhoneNumber");
14-
public static EventId InvalidPassword = new EventId(2, "InvalidPassword");
15-
public static EventId UserLockedOut = new EventId(3, "UserLockedOut");
16-
public static EventId UserCannotSignInWithoutConfirmedAccount = new EventId(4, "UserCannotSignInWithoutConfirmedAccount");
17-
public static EventId TwoFactorSecurityStampValidationFailed = new EventId(5, "TwoFactorSecurityStampValidationFailed");
18-
public static EventId NoPasskeyCreationOptions = new EventId(6, "NoPasskeyCreationOptions");
19-
public static EventId UserDoesNotMatchPasskeyCreationOptions = new EventId(7, "UserDoesNotMatchPasskeyCreationOptions");
10+
public static readonly EventId UserCannotSignInWithoutConfirmedEmail = new(0, "UserCannotSignInWithoutConfirmedEmail");
11+
public static readonly EventId SecurityStampValidationFailed = new(0, "SecurityStampValidationFailed");
12+
public static readonly EventId SecurityStampValidationFailedId4 = new(4, "SecurityStampValidationFailed");
13+
public static readonly EventId UserCannotSignInWithoutConfirmedPhoneNumber = new(1, "UserCannotSignInWithoutConfirmedPhoneNumber");
14+
public static readonly EventId InvalidPassword = new(2, "InvalidPassword");
15+
public static readonly EventId UserLockedOut = new(3, "UserLockedOut");
16+
public static readonly EventId UserCannotSignInWithoutConfirmedAccount = new(4, "UserCannotSignInWithoutConfirmedAccount");
17+
public static readonly EventId TwoFactorSecurityStampValidationFailed = new(5, "TwoFactorSecurityStampValidationFailed");
18+
public static readonly EventId NoPasskeyCreationOptions = new(6, "NoPasskeyCreationOptions");
19+
public static readonly EventId UserDoesNotMatchPasskeyCreationOptions = new(7, "UserDoesNotMatchPasskeyCreationOptions");
20+
public static readonly EventId PasskeyAttestationFailed = new(8, "PasskeyAttestationFailed");
21+
public static readonly EventId PasskeyAssertionFailed = new(9, "PasskeyAssertionFailed");
2022
}

src/Identity/Core/src/HttpPasskeyRequestContextProvider.cs

Lines changed: 0 additions & 25 deletions
This file was deleted.

src/Identity/Extensions.Core/src/IPasskeyAttestationStatementVerifier.cs renamed to src/Identity/Core/src/IPasskeyAttestationStatementVerifier.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System;
5-
using System.Collections.Generic;
6-
using System.Linq;
7-
using System.Text;
8-
using System.Threading.Tasks;
9-
104
namespace Microsoft.AspNetCore.Identity;
115

126
/// <summary>

src/Identity/Extensions.Core/src/IPasskeyHandler.cs renamed to src/Identity/Core/src/IPasskeyHandler.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System;
5-
using System.Collections.Generic;
6-
using System.Linq;
7-
using System.Text;
8-
using System.Threading.Tasks;
9-
104
namespace Microsoft.AspNetCore.Identity;
115

126
/// <summary>

src/Identity/Extensions.Core/src/IPasskeyOriginValidator.cs renamed to src/Identity/Core/src/IPasskeyOriginValidator.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System;
5-
using System.Collections.Generic;
6-
using System.Linq;
7-
using System.Text;
8-
using System.Threading.Tasks;
9-
104
namespace Microsoft.AspNetCore.Identity;
115

126
/// <summary>

src/Identity/Core/src/IdentityBuilderExtensions.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ public static IdentityBuilder AddDefaultTokenProviders(this IdentityBuilder buil
4141
private static void AddSignInManagerDeps(this IdentityBuilder builder)
4242
{
4343
builder.Services.AddHttpContextAccessor();
44-
builder.Services.AddScoped<IPasskeyRequestContextProvider, HttpPasskeyRequestContextProvider>();
44+
builder.Services.AddScoped<IPasskeyAttestationStatementVerifier, NoOpPasskeyAttestationStatementVerifier>();
45+
builder.Services.AddScoped<IPasskeyOriginValidator, DefaultPasskeyOriginValidator>();
46+
builder.Services.AddScoped(typeof(IPasskeyHandler<>).MakeGenericType(builder.UserType), typeof(DefaultPasskeyHandler<>).MakeGenericType(builder.UserType));
4547
builder.Services.AddScoped(typeof(ISecurityStampValidator), typeof(SecurityStampValidator<>).MakeGenericType(builder.UserType));
4648
builder.Services.AddScoped(typeof(ITwoFactorSecurityStampValidator), typeof(TwoFactorSecurityStampValidator<>).MakeGenericType(builder.UserType));
4749
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<SecurityStampValidatorOptions>, PostConfigureSecurityStampValidatorOptions>());

src/Identity/Core/src/IdentityServiceCollectionExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ public static class IdentityServiceCollectionExtensions
102102
services.TryAddScoped<ITwoFactorSecurityStampValidator, TwoFactorSecurityStampValidator<TUser>>();
103103
services.TryAddScoped<IUserClaimsPrincipalFactory<TUser>, UserClaimsPrincipalFactory<TUser, TRole>>();
104104
services.TryAddScoped<IUserConfirmation<TUser>, DefaultUserConfirmation<TUser>>();
105-
services.TryAddScoped<IPasskeyRequestContextProvider, HttpPasskeyRequestContextProvider>();
105+
services.TryAddScoped<IPasskeyAttestationStatementVerifier, NoOpPasskeyAttestationStatementVerifier>();
106106
services.TryAddScoped<IPasskeyOriginValidator, DefaultPasskeyOriginValidator>();
107107
services.TryAddScoped<IPasskeyHandler<TUser>, DefaultPasskeyHandler<TUser>>();
108108
services.TryAddScoped<UserManager<TUser>>();

0 commit comments

Comments
 (0)