Skip to content

Add passkey tests verifying WebAuthn conformance #62441

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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Identity/Core/src/DefaultPasskeyHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ await VerifyClientDataAsync(
// NOTE: We simply fail the ceremony in this case.
if (authenticatorData.SignCount <= storedPasskey.SignCount)
{
throw PasskeyException.SignCountLessThanStoredSignCount();
throw PasskeyException.SignCountLessThanOrEqualToStoredSignCount();
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Identity/Core/src/PasskeyExceptionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public static PasskeyException ExpectedBackupIneligibleCredential()
public static PasskeyException InvalidAssertionSignature()
=> new("The assertion signature was invalid.");

public static PasskeyException SignCountLessThanStoredSignCount()
public static PasskeyException SignCountLessThanOrEqualToStoredSignCount()
=> new("The authenticator's signature counter is unexpectedly less than or equal to the stored signature counter.");

public static PasskeyException InvalidAttestationObject(Exception ex)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ public VersionTwoDbContext(DbContextOptions options)
}
}

public class VersionThreeDbContext : IdentityDbContext<IdentityUser, IdentityRole, string>
{
public VersionThreeDbContext(DbContextOptions options)
: base(options)
{
}
}

public class EmptyDbContext : IdentityDbContext<IdentityUser, IdentityRole, string>
{
public EmptyDbContext(DbContextOptions options)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.Builder;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test;

public class VersionThreeSchemaTest : IClassFixture<ScratchDatabaseFixture>
{
private readonly ApplicationBuilder _builder;

public VersionThreeSchemaTest(ScratchDatabaseFixture fixture)
{
var services = new ServiceCollection();

services
.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build())
.AddDbContext<VersionThreeDbContext>(o =>
o.UseSqlite(fixture.Connection)
.ConfigureWarnings(b => b.Log(CoreEventId.ManyServiceProvidersCreatedWarning)))
.AddIdentity<IdentityUser, IdentityRole>(o =>
{
// MaxKeyLength does not need to be set in version 3
o.Stores.SchemaVersion = IdentitySchemaVersions.Version3;
})
.AddEntityFrameworkStores<VersionThreeDbContext>();

services.AddLogging();

_builder = new ApplicationBuilder(services.BuildServiceProvider());
var scope = _builder.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope();
var db = scope.ServiceProvider.GetRequiredService<VersionThreeDbContext>();
db.Database.EnsureCreated();
}

[Fact]
public void EnsureDefaultSchema()
{
using var scope = _builder.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope();
var db = scope.ServiceProvider.GetRequiredService<VersionThreeDbContext>();
VerifyVersion3Schema(db);
}

internal static void VerifyVersion3Schema(DbContext dbContext)
{
using var sqlConn = (SqliteConnection)dbContext.Database.GetDbConnection();
sqlConn.Open();
Assert.True(DbUtil.VerifyColumns(sqlConn, "AspNetUsers", "Id", "UserName", "Email", "PasswordHash", "SecurityStamp",
"EmailConfirmed", "PhoneNumber", "PhoneNumberConfirmed", "TwoFactorEnabled", "LockoutEnabled",
"LockoutEnd", "AccessFailedCount", "ConcurrencyStamp", "NormalizedUserName", "NormalizedEmail"));
Assert.True(DbUtil.VerifyColumns(sqlConn, "AspNetRoles", "Id", "Name", "NormalizedName", "ConcurrencyStamp"));
Assert.True(DbUtil.VerifyColumns(sqlConn, "AspNetUserRoles", "UserId", "RoleId"));
Assert.True(DbUtil.VerifyColumns(sqlConn, "AspNetUserClaims", "Id", "UserId", "ClaimType", "ClaimValue"));
Assert.True(DbUtil.VerifyColumns(sqlConn, "AspNetUserLogins", "UserId", "ProviderKey", "LoginProvider", "ProviderDisplayName"));
Assert.True(DbUtil.VerifyColumns(sqlConn, "AspNetUserTokens", "UserId", "LoginProvider", "Name", "Value"));
Assert.True(DbUtil.VerifyColumns(sqlConn, "AspNetUserPasskeys", "UserId", "CredentialId", "PublicKey", "Name", "CreatedAt",
"SignCount", "Transports", "IsUserVerified", "IsBackupEligible", "IsBackedUp", "AttestationObject",
"ClientDataJson"));

Assert.True(DbUtil.VerifyMaxLength(dbContext, "AspNetUsers", 256, "UserName", "Email", "NormalizedUserName", "NormalizedEmail", "PhoneNumber"));
Assert.True(DbUtil.VerifyMaxLength(dbContext, "AspNetRoles", 256, "Name", "NormalizedName"));
Assert.True(DbUtil.VerifyMaxLength(dbContext, "AspNetUserLogins", 128, "LoginProvider", "ProviderKey"));
Assert.True(DbUtil.VerifyMaxLength(dbContext, "AspNetUserTokens", 128, "LoginProvider", "Name"));

DbUtil.VerifyIndex(sqlConn, "AspNetRoles", "RoleNameIndex", isUnique: true);
DbUtil.VerifyIndex(sqlConn, "AspNetUsers", "UserNameIndex", isUnique: true);
DbUtil.VerifyIndex(sqlConn, "AspNetUsers", "EmailIndex");
}
}
1 change: 1 addition & 0 deletions src/Identity/Extensions.Core/src/UserManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2188,6 +2188,7 @@ public virtual Task<IList<UserPasskeyInfo>> GetPasskeysAsync(TUser user)
{
ThrowIfDisposed();
var passkeyStore = GetUserPasskeyStore();
ArgumentNullThrowHelper.ThrowIfNull(user);
ArgumentNullThrowHelper.ThrowIfNull(credentialId);

return passkeyStore.FindPasskeyAsync(user, credentialId, CancellationToken);
Expand Down
Loading
Loading