Skip to content

Commit 277cc09

Browse files
committed
Cleanups + new attesattion/assertion tests
1 parent 0ad1944 commit 277cc09

File tree

3 files changed

+499
-35
lines changed

3 files changed

+499
-35
lines changed

src/Identity/test/Identity.Test/DefaultPasskeyHandlerTest.Assertion.cs

Lines changed: 128 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,7 @@ public async Task Assertion_Fails_WhenCredentialIdIsNotBase64UrlEncoded()
6969
test.CredentialJson.TransformAsJsonObject(credentialJson =>
7070
{
7171
var base64UrlCredentialId = (string)credentialJson["id"]!;
72-
var rawCredentialId = Base64Url.DecodeFromChars(base64UrlCredentialId);
73-
var base64CredentialId = Convert.ToBase64String(rawCredentialId) + "==";
74-
credentialJson["id"] = base64CredentialId;
72+
credentialJson["id"] = GetInvalidBase64UrlValue(base64UrlCredentialId);
7573
});
7674

7775
var result = await test.RunAsync();
@@ -164,6 +162,58 @@ public async Task Assertion_Fails_WhenCredentialResponseIsNotAnObject(string jso
164162
Assert.StartsWith("The assertion credential JSON had an invalid format", result.Failure.Message);
165163
}
166164

165+
[Fact]
166+
public async Task Assertion_Fails_WhenOriginalOptionsChallengeIsMissing()
167+
{
168+
var test = new AssertionTest();
169+
test.OriginalOptionsJson.TransformAsJsonObject(originalOptionsJson =>
170+
{
171+
Assert.True(originalOptionsJson.Remove("challenge"));
172+
});
173+
174+
var result = await test.RunAsync();
175+
176+
Assert.False(result.Succeeded);
177+
178+
Assert.StartsWith("The original passkey request options had an invalid format", result.Failure.Message);
179+
Assert.Contains("was missing required properties including: 'challenge'", result.Failure.Message);
180+
}
181+
182+
[Fact]
183+
public async Task Assertion_Fails_WhenOriginalOptionsChallengeIsNotBase64UrlEncoded()
184+
{
185+
var test = new AssertionTest();
186+
test.OriginalOptionsJson.TransformAsJsonObject(originalOptionsJson =>
187+
{
188+
var base64UrlChallenge = (string)originalOptionsJson["challenge"]!;
189+
originalOptionsJson["challenge"] = GetInvalidBase64UrlValue(base64UrlChallenge);
190+
});
191+
192+
var result = await test.RunAsync();
193+
194+
Assert.False(result.Succeeded);
195+
Assert.StartsWith("The original passkey request options had an invalid format", result.Failure.Message);
196+
Assert.Contains("base64url string", result.Failure.Message);
197+
}
198+
199+
[Theory]
200+
[InlineData("42")]
201+
[InlineData("null")]
202+
[InlineData("{}")]
203+
public async Task Assertion_Fails_WhenOriginalOptionsChallengeIsNotString(string jsonValue)
204+
{
205+
var test = new AssertionTest();
206+
test.OriginalOptionsJson.TransformAsJsonObject(originalOptionsJson =>
207+
{
208+
originalOptionsJson["challenge"] = JsonNode.Parse(jsonValue);
209+
});
210+
211+
var result = await test.RunAsync();
212+
213+
Assert.False(result.Succeeded);
214+
Assert.StartsWith("The original passkey request options had an invalid format", result.Failure.Message);
215+
}
216+
167217
[Fact]
168218
public async Task Assertion_Fails_WhenClientDataJsonIsMissing()
169219
{
@@ -256,9 +306,7 @@ public async Task Assertion_Fails_WhenAuthenticatorDataIsNotBase64UrlEncoded()
256306
test.CredentialJson.TransformAsJsonObject(credentialJson =>
257307
{
258308
var base64UrlAuthenticatorData = (string)credentialJson["response"]!["authenticatorData"]!;
259-
var rawAuthenticatorData = Base64Url.DecodeFromChars(base64UrlAuthenticatorData);
260-
var base64AuthenticatorData = Convert.ToBase64String(rawAuthenticatorData) + "==";
261-
credentialJson["response"]!["authenticatorData"] = base64AuthenticatorData;
309+
credentialJson["response"]!["authenticatorData"] = GetInvalidBase64UrlValue(base64UrlAuthenticatorData);
262310
});
263311

264312
var result = await test.RunAsync();
@@ -325,9 +373,7 @@ public async Task Assertion_Fails_WhenResponseSignatureIsNotBase64UrlEncoded()
325373
test.CredentialJson.TransformAsJsonObject(credentialJson =>
326374
{
327375
var base64UrlSignature = (string)credentialJson["response"]!["signature"]!;
328-
var rawSignature = Base64Url.DecodeFromChars(base64UrlSignature);
329-
var base64Signature = Convert.ToBase64String(rawSignature) + "==";
330-
credentialJson["response"]!["signature"] = base64Signature;
376+
credentialJson["response"]!["signature"] = GetInvalidBase64UrlValue(base64UrlSignature);
331377
});
332378

333379
var result = await test.RunAsync();
@@ -401,6 +447,25 @@ public async Task Assertion_Fails_WhenResponseUserHandleIsNull()
401447
Assert.StartsWith("The authenticator response was missing a user handle", result.Failure.Message);
402448
}
403449

450+
[Fact]
451+
public async Task Assertion_Fails_WhenResponseUserHandleDoesNotMatchUserId()
452+
{
453+
var test = new AssertionTest
454+
{
455+
IsUserIdentified = true,
456+
};
457+
test.CredentialJson.TransformAsJsonObject(credentialJson =>
458+
{
459+
var newUserId = test.User.Id[..^1];
460+
credentialJson["response"]!["userHandle"] = Base64Url.EncodeToString(Encoding.UTF8.GetBytes(newUserId));
461+
});
462+
463+
var result = await test.RunAsync();
464+
465+
Assert.False(result.Succeeded);
466+
Assert.StartsWith("The provided user handle", result.Failure.Message);
467+
}
468+
404469
[Fact]
405470
public async Task Assertion_Fails_WhenClientDataJsonTypeIsMissing()
406471
{
@@ -509,9 +574,7 @@ public async Task Assertion_Fails_WhenClientDataJsonChallengeIsNotBase64UrlEncod
509574
test.ClientDataJson.TransformAsJsonObject(clientDataJson =>
510575
{
511576
var base64UrlChallenge = (string)clientDataJson["challenge"]!;
512-
var rawChallenge = Base64Url.DecodeFromChars(base64UrlChallenge);
513-
var base64Challenge = Convert.ToBase64String(rawChallenge) + "==";
514-
clientDataJson["challenge"] = base64Challenge;
577+
clientDataJson["challenge"] = GetInvalidBase64UrlValue(base64UrlChallenge);
515578
});
516579

517580
var result = await test.RunAsync();
@@ -803,7 +866,7 @@ public async Task Assertion_Succeeds_WhenSignCountIsZero()
803866
var test = new AssertionTest();
804867
test.AuthenticatorDataArgs.Transform(args => args with
805868
{
806-
SignCount = 0, // Normally 1
869+
SignCount = 0, // Usually 1 by default
807870
});
808871

809872
var result = await test.RunAsync();
@@ -1057,6 +1120,53 @@ public async Task Assertion_Fails_WhenAuthenticatorDataIsBackupEligibleButStored
10571120
result.Failure.Message);
10581121
}
10591122

1123+
[Fact]
1124+
public async Task Assertion_Fails_WhenProvidedCredentialIsNotInAllowedCredentials()
1125+
{
1126+
var test = new AssertionTest();
1127+
var allowedCredentialId = test.CredentialId.ToArray();
1128+
allowedCredentialId[0]++;
1129+
test.AddAllowedCredential(allowedCredentialId);
1130+
1131+
var result = await test.RunAsync();
1132+
1133+
Assert.False(result.Succeeded);
1134+
Assert.StartsWith(
1135+
"The provided credential ID was not in the list of allowed credentials",
1136+
result.Failure.Message);
1137+
}
1138+
1139+
[Fact]
1140+
public async Task Assertion_Succeeds_WhenProvidedCredentialIsInAllowedCredentials()
1141+
{
1142+
var test = new AssertionTest();
1143+
var otherAllowedCredentialId = test.CredentialId.ToArray();
1144+
otherAllowedCredentialId[0]++;
1145+
test.AddAllowedCredential(test.CredentialId);
1146+
test.AddAllowedCredential(otherAllowedCredentialId);
1147+
1148+
var result = await test.RunAsync();
1149+
1150+
Assert.True(result.Succeeded);
1151+
}
1152+
1153+
[Theory]
1154+
[InlineData(false)]
1155+
[InlineData(true)]
1156+
public async Task Assertion_Fails_WhenCredentialDoesNotExistOnTheUser(bool isUserIdentified)
1157+
{
1158+
var test = new AssertionTest
1159+
{
1160+
IsUserIdentified = isUserIdentified,
1161+
DoesCredentialExistOnUser = false
1162+
};
1163+
1164+
var result = await test.RunAsync();
1165+
1166+
Assert.False(result.Succeeded);
1167+
Assert.StartsWith("The provided credential does not belong to the specified user", result.Failure.Message);
1168+
}
1169+
10601170
private sealed class AssertionTest : PasskeyTestBase<PasskeyAssertionResult<PocoUser>>
10611171
{
10621172
private static readonly byte[] _defaultChallenge = [1, 2, 3, 4, 5, 6, 7, 8];
@@ -1075,6 +1185,7 @@ private sealed class AssertionTest : PasskeyTestBase<PasskeyAssertionResult<Poco
10751185
public bool IsUserIdentified { get; set; }
10761186
public bool IsStoredPasskeyBackupEligible { get; set; }
10771187
public bool IsStoredPasskeyBackedUp { get; set; }
1188+
public bool DoesCredentialExistOnUser { get; set; } = true;
10781189
public COSEAlgorithmIdentifier Algorithm { get; set; } = COSEAlgorithmIdentifier.ES256;
10791190
public ReadOnlyMemory<byte> Challenge { get; set; } = _defaultChallenge;
10801191
public ReadOnlyMemory<byte> CredentialId { get; set; } = _defaultCredentialId;
@@ -1087,11 +1198,11 @@ private sealed class AssertionTest : PasskeyTestBase<PasskeyAssertionResult<Poco
10871198
public ComputedJsonObject CredentialJson { get; } = new();
10881199
public ComputedValue<UserPasskeyInfo> StoredPasskey { get; } = new();
10891200

1090-
public void AddAllowCredentials(string userId)
1201+
public void AddAllowedCredential(ReadOnlyMemory<byte> credentialId)
10911202
{
10921203
_allowCredentials.Add(new()
10931204
{
1094-
Id = BufferSource.FromString(userId),
1205+
Id = BufferSource.FromBytes(credentialId),
10951206
Type = "public-key",
10961207
Transports = ["internal"],
10971208
});
@@ -1119,6 +1230,7 @@ protected override async Task<PasskeyAssertionResult<PocoUser>> RunCoreAsync()
11191230
{
11201231
RpIdHash = SHA256.HashData(Encoding.UTF8.GetBytes(RpId ?? string.Empty)),
11211232
Flags = AuthenticatorDataFlags.UserPresent,
1233+
SignCount = 1,
11221234
});
11231235
var authenticatorData = AuthenticatorData.Compute(MakeAuthenticatorData(authenticatorDataArgs));
11241236
var clientDataJson = ClientDataJson.Compute($$"""
@@ -1171,7 +1283,7 @@ protected override async Task<PasskeyAssertionResult<PocoUser>> RunCoreAsync()
11711283
userManager
11721284
.Setup(m => m.GetPasskeyAsync(It.IsAny<PocoUser>(), It.IsAny<byte[]>()))
11731285
.Returns((PocoUser user, byte[] credentialId) => Task.FromResult(
1174-
user == User && CredentialId.Span.SequenceEqual(credentialId)
1286+
DoesCredentialExistOnUser && user == User && CredentialId.Span.SequenceEqual(credentialId)
11751287
? storedPasskey
11761288
: null));
11771289

0 commit comments

Comments
 (0)