-
-
Notifications
You must be signed in to change notification settings - Fork 182
/
Copy pathAuthenticatorResponse.cs
94 lines (73 loc) · 3.85 KB
/
AuthenticatorResponse.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using Fido2NetLib.Serialization;
namespace Fido2NetLib
{
/// <summary>
/// Base class for responses sent by the Authenticator Client
/// </summary>
public class AuthenticatorResponse
{
protected AuthenticatorResponse(ReadOnlySpan<byte> utf8EncodedJson)
{
if (utf8EncodedJson.Length is 0)
throw new Fido2VerificationException("utf8EncodedJson may not be empty");
// 1. Let JSONtext be the result of running UTF-8 decode on the value of response.clientDataJSON
// 2. Let C, the client data claimed as collected during the credential creation, be the result of running an implementation-specific JSON parser on JSONtext
// Note: C may be any implementation-specific data structure representation, as long as C’s components are referenceable, as required by this algorithm.
// We call this AuthenticatorResponse
AuthenticatorResponse response;
try
{
response = JsonSerializer.Deserialize(utf8EncodedJson, FidoSerializerContext.Default.AuthenticatorResponse)!;
}
catch (Exception e) when (e is JsonException)
{
throw new Fido2VerificationException("Malformed clientDataJson");
}
if (response is null)
throw new Fido2VerificationException("Deserialized authenticator response cannot be null");
Type = response.Type;
Challenge = response.Challenge;
Origin = response.Origin;
}
#nullable disable
public AuthenticatorResponse() // for deserialization
{
}
#nullable enable
public const int MAX_ORIGINS_TO_PRINT = 5;
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonConverter(typeof(Base64UrlConverter))]
[JsonPropertyName("challenge")]
public byte[] Challenge { get; set; }
[JsonPropertyName("origin")]
public string Origin { get; set; }
// todo: add TokenBinding https://www.w3.org/TR/webauthn/#dictdef-tokenbinding
protected void BaseVerify(HashSet<string> fullyQualifiedExpectedOrigins, ReadOnlySpan<byte> originalChallenge, ReadOnlySpan<byte> requestTokenBindingId)
{
if (Type is not "webauthn.create" && Type is not "webauthn.get")
throw new Fido2VerificationException($"Type not equal to 'webauthn.create' or 'webauthn.get'. Was: '{Type}'");
if (Challenge is null)
throw new Fido2VerificationException("Challenge cannot be null");
// 4. Verify that the value of C.challenge matches the challenge that was sent to the authenticator in the create() call
if (!Challenge.AsSpan().SequenceEqual(originalChallenge))
throw new Fido2VerificationException("Challenge not equal to original challenge");
var fullyQualifiedOrigin = Origin.ToFullyQualifiedOrigin();
// 5. Verify that the value of C.origin matches the Relying Party's origin.
if (!fullyQualifiedExpectedOrigins.Contains(fullyQualifiedOrigin))
throw new Fido2VerificationException($"Fully qualified origin {fullyQualifiedOrigin} of {Origin} not equal to fully qualified original origin {string.Join(", ", fullyQualifiedExpectedOrigins.Take(MAX_ORIGINS_TO_PRINT))} ({fullyQualifiedExpectedOrigins.Count})");
}
private static string FullyQualifiedOrigin(string origin)
{
var uri = new Uri(origin);
if (UriHostNameType.Unknown != uri.HostNameType)
return uri.IsDefaultPort ? $"{uri.Scheme}://{uri.Host}" : $"{uri.Scheme}://{uri.Host}:{uri.Port}";
return origin;
}
}
}