Skip to content

Commit dc8916b

Browse files
kspearrinabergs
andauthored
Support for PRF Extension (#390)
* added prf to extension models * update comment summaries * added PRF to some existing tests for output * assert PRF enabled test * Small fixes --------- Co-authored-by: Anders Åberg <[email protected]>
1 parent 4446aea commit dc8916b

6 files changed

+119
-8
lines changed

Src/Fido2.Models/Objects/AuthenticationExtensionsClientInputs.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,5 +61,13 @@ public sealed class AuthenticationExtensionsClientInputs
6161
[JsonPropertyName("credProps")]
6262
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
6363
public bool? CredProps { get; set; }
64+
65+
/// <summary>
66+
/// This extension allows a Relying Party to evaluate outputs from a pseudo-random function (PRF) associated with a credential.
67+
/// https://w3c.github.io/webauthn/#prf-extension
68+
/// </summary>
69+
[JsonPropertyName("prf")]
70+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
71+
public AuthenticationExtensionsPRFInputs? PRF { get; set; }
6472
}
6573

Src/Fido2.Models/Objects/AuthenticationExtensionsClientOutputs.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,12 @@ public class AuthenticationExtensionsClientOutputs
5757
[JsonPropertyName("credProps")]
5858
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
5959
public bool? CredProps { get; set; }
60+
61+
/// <summary>
62+
/// This extension allows a Relying Party to evaluate outputs from a pseudo-random function (PRF) associated with a credential.
63+
/// https://w3c.github.io/webauthn/#prf-extension
64+
/// </summary>
65+
[JsonPropertyName("prf")]
66+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
67+
public AuthenticationExtensionsPRFOutputs? PRF { get; set; }
6068
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System.Collections.Generic;
2+
using System.Text.Json.Serialization;
3+
4+
namespace Fido2NetLib.Objects;
5+
6+
/// <summary>
7+
/// This is a dictionary containing the PRF extension input values
8+
/// </summary>
9+
public sealed class AuthenticationExtensionsPRFInputs
10+
{
11+
/// <summary>
12+
/// Inputs on which to evaluate PRF.
13+
/// https://w3c.github.io/webauthn/#dom-authenticationextensionsprfinputs-eval
14+
/// </summary>
15+
[JsonPropertyName("eval")]
16+
public AuthenticationExtensionsPRFValues Eval { get; set; }
17+
/// <summary>
18+
/// A record mapping base64url encoded credential IDs to PRF inputs to evaluate for that credential.
19+
/// https://w3c.github.io/webauthn/#dom-authenticationextensionsprfinputs-evalbycredential
20+
/// </summary>
21+
[JsonPropertyName("evalByCredential")]
22+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
23+
public KeyValuePair<string, AuthenticationExtensionsPRFValues>? EvalByCredential { get; set; }
24+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace Fido2NetLib.Objects;
4+
5+
/// <summary>
6+
/// This is a dictionary containing the PRF extension output values
7+
/// </summary>
8+
public sealed class AuthenticationExtensionsPRFOutputs
9+
{
10+
/// <summary>
11+
/// If PRFs are available for use with the created credential.
12+
/// </summary>
13+
[JsonPropertyName("enabled")]
14+
public bool Enabled { get; set; }
15+
/// <summary>
16+
/// The results of evaluating the PRF inputs.
17+
/// </summary>
18+
[JsonPropertyName("results")]
19+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
20+
public AuthenticationExtensionsPRFValues Results { get; set; }
21+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace Fido2NetLib.Objects;
4+
5+
/// <summary>
6+
/// Evaluated PRF values.
7+
/// </summary>
8+
public sealed class AuthenticationExtensionsPRFValues
9+
{
10+
/// <summary>
11+
/// salt1 value to the PRF evaluation.
12+
/// </summary>
13+
[JsonPropertyName("first")]
14+
[JsonConverter(typeof(Base64UrlConverter))]
15+
public byte[] First { get; set; }
16+
/// <summary>
17+
/// salt2 value to the PRF evaluation.
18+
/// </summary>
19+
[JsonPropertyName("second")]
20+
[JsonConverter(typeof(Base64UrlConverter))]
21+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
22+
public byte[] Second { get; set; }
23+
}
24+

Test/AuthenticatorResponse.cs

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public class AuthenticatorResponse
2020
[InlineData("https://www.passwordless.dev:443", "https://www.passwordless.dev:443")]
2121
[InlineData("https://www.passwordless.dev", "https://www.passwordless.dev:443")]
2222
[InlineData("https://www.passwordless.dev:443", "https://www.passwordless.dev")]
23-
[InlineData("https://www.passwordless.dev:443/foo/bar.html","https://www.passwordless.dev:443/foo/bar.html")]
23+
[InlineData("https://www.passwordless.dev:443/foo/bar.html", "https://www.passwordless.dev:443/foo/bar.html")]
2424
[InlineData("https://www.passwordless.dev:443/foo/bar.html", "https://www.passwordless.dev:443/bar/foo.html")]
2525
[InlineData("https://www.passwordless.dev:443/foo/bar.html", "https://www.passwordless.dev/bar/foo.html")]
2626
[InlineData("https://www.passwordless.dev:443/foo/bar.html", "https://www.passwordless.dev")]
@@ -38,7 +38,7 @@ public class AuthenticatorResponse
3838
[InlineData("lorem://ipsum:1234", "lorem://ipsum:1234")]
3939
[InlineData("lorem://ipsum:9876/sit", "lorem://ipsum:9876/sit")]
4040
[InlineData("foo://bar:321/path/", "foo://bar:321/path/")]
41-
[InlineData("foo://bar:321/path","foo://bar:321/path")]
41+
[InlineData("foo://bar:321/path", "foo://bar:321/path")]
4242
[InlineData("http://[0:0:0:0:0:0:0:1]", "http://[0:0:0:0:0:0:0:1]")]
4343
[InlineData("http://[0:0:0:0:0:0:0:1]", "http://[0:0:0:0:0:0:0:1]:80")]
4444
[InlineData("https://[0:0:0:0:0:0:0:1]", "https://[0:0:0:0:0:0:0:1]")]
@@ -55,7 +55,7 @@ public async Task TestAuthenticatorOriginsAsync(string origin, string expectedOr
5555
acd
5656
).ToByteArray();
5757

58-
byte[] clientDataJson = JsonSerializer.SerializeToUtf8Bytes(new
58+
byte[] clientDataJson = JsonSerializer.SerializeToUtf8Bytes(new
5959
{
6060
type = "webauthn.create",
6161
challenge = challenge,
@@ -257,6 +257,15 @@ public void TestAuthenticatorAttestationRawResponse()
257257
4 // USER_VERIFY_PASSCODE_INTERNAL
258258
},
259259
},
260+
PRF = new AuthenticationExtensionsPRFOutputs
261+
{
262+
Enabled = true,
263+
Results = new AuthenticationExtensionsPRFValues
264+
{
265+
First = new byte[] { 0xf1, 0xd0 },
266+
Second = new byte[] { 0xf1, 0xd0 }
267+
}
268+
}
260269
}
261270
};
262271
Assert.Equal(PublicKeyCredentialType.PublicKey, rawResponse.Type);
@@ -269,6 +278,9 @@ public void TestAuthenticatorAttestationRawResponse()
269278
Assert.Equal(rawResponse.Extensions.Extensions, new string[] { "foo", "bar" });
270279
Assert.Equal("test", rawResponse.Extensions.Example);
271280
Assert.Equal((ulong)4, rawResponse.Extensions.UserVerificationMethod[0][0]);
281+
Assert.True(rawResponse.Extensions.PRF.Enabled);
282+
Assert.True(rawResponse.Extensions.PRF.Results.First.SequenceEqual(new byte[] { 0xf1, 0xd0 }));
283+
Assert.True(rawResponse.Extensions.PRF.Results.Second.SequenceEqual(new byte[] { 0xf1, 0xd0 }));
272284
}
273285

274286
[Fact]
@@ -357,7 +369,7 @@ public void TestAuthenticatorAttestationResponseInvalidType()
357369
{
358370
var challenge = RandomNumberGenerator.GetBytes(128);
359371
var rp = "https://www.passwordless.dev";
360-
var clientDataJson = JsonSerializer.SerializeToUtf8Bytes(new
372+
var clientDataJson = JsonSerializer.SerializeToUtf8Bytes(new
361373
{
362374
Type = "webauthn.get",
363375
Challenge = challenge,
@@ -429,7 +441,8 @@ public void TestAuthenticatorAttestationResponseInvalidRawId(byte[] value)
429441
{
430442
var challenge = RandomNumberGenerator.GetBytes(128);
431443
var rp = "https://www.passwordless.dev";
432-
byte[] clientDataJson = JsonSerializer.SerializeToUtf8Bytes(new {
444+
byte[] clientDataJson = JsonSerializer.SerializeToUtf8Bytes(new
445+
{
433446
type = "webauthn.create",
434447
challenge = challenge,
435448
origin = rp,
@@ -498,7 +511,8 @@ public void TestAuthenticatorAttestationResponseInvalidRawType()
498511
{
499512
var challenge = RandomNumberGenerator.GetBytes(128);
500513
var rp = "https://www.passwordless.dev";
501-
var clientDataJson = JsonSerializer.SerializeToUtf8Bytes(new {
514+
var clientDataJson = JsonSerializer.SerializeToUtf8Bytes(new
515+
{
502516
type = "webauthn.create",
503517
challenge = challenge,
504518
origin = rp,
@@ -652,7 +666,7 @@ public async Task TestAuthenticatorAttestationResponseNotUserPresentAsync()
652666
null
653667
).ToByteArray();
654668

655-
var clientDataJson = JsonSerializer.SerializeToUtf8Bytes(new
669+
var clientDataJson = JsonSerializer.SerializeToUtf8Bytes(new
656670
{
657671
type = "webauthn.create",
658672
challenge = challenge,
@@ -972,7 +986,7 @@ public void TestAuthenticatorAttestationResponseUnknownAttestationType()
972986
challenge = challenge,
973987
origin = rp,
974988
});
975-
989+
976990
var rawResponse = new AuthenticatorAttestationRawResponse
977991
{
978992
Type = PublicKeyCredentialType.PublicKey,
@@ -1224,6 +1238,15 @@ public void TestAuthenticatorAssertionRawResponse()
12241238
4 // USER_VERIFY_PASSCODE_INTERNAL
12251239
},
12261240
},
1241+
PRF = new AuthenticationExtensionsPRFOutputs
1242+
{
1243+
Enabled = true,
1244+
Results = new AuthenticationExtensionsPRFValues
1245+
{
1246+
First = new byte[] { 0xf1, 0xd0 },
1247+
Second = new byte[] { 0xf1, 0xd0 }
1248+
}
1249+
}
12271250
}
12281251
};
12291252
Assert.Equal(PublicKeyCredentialType.PublicKey, assertionResponse.Type);
@@ -1238,6 +1261,9 @@ public void TestAuthenticatorAssertionRawResponse()
12381261
Assert.Equal(assertionResponse.Extensions.Extensions, new string[] { "foo", "bar" });
12391262
Assert.Equal("test", assertionResponse.Extensions.Example);
12401263
Assert.Equal((ulong)4, assertionResponse.Extensions.UserVerificationMethod[0][0]);
1264+
Assert.True(assertionResponse.Extensions.PRF.Enabled);
1265+
Assert.True(assertionResponse.Extensions.PRF.Results.First.SequenceEqual(new byte[] { 0xf1, 0xd0 }));
1266+
Assert.True(assertionResponse.Extensions.PRF.Results.Second.SequenceEqual(new byte[] { 0xf1, 0xd0 }));
12411267
}
12421268

12431269
[Fact]

0 commit comments

Comments
 (0)