Skip to content

Commit 0f9f0cb

Browse files
abergsGabor Mihalygoogyi
authored
conformance 1.7.20 4 (#531)
This PR does not reach full conformance testing score because of #554, but sets the bed nicely for reaching conformance. * FIDO Conformance Tools v1.7.15 fixes TrustAnchor.cs : 32 Server-ServerAuthenticatorAttestationResponse-Resp-5 Test server processing "packed" FULL attestation F-10 Send ServerAuthenticatorAttestationResponse with FULL "packed" attestation, with attStmt.x5c containing full chain, and check that server returns an error https://datatracker.ietf.org/doc/html/rfc5280#section-6.1 AuthenticatorAttestationRawResponse.cs : 18 Server-ServerAuthenticatorAttestationResponse-Resp-1 Test server processing ServerAuthenticatorAttestationResponse structure F-4 Send ServerAuthenticatorAttestationResponse that is missing "type" field and check that server returns an error CredentialCreateOptions.cs : 96 Server-ServerAuthenticatorAttestationResponse-Resp-4 Test server support of the authentication algorithms P-8 Send a valid ServerAuthenticatorAttestationResponse with SELF "packed" attestation, for "ALG_SIGN_RSASSA_PKCSV15_SHA1_RAW" aka "RS1" algorithm, and check that server succeeds Server-ServerAuthenticatorAttestationResponse-Resp-9 Test server processing "tpm" attestation P-2 Send a valid ServerAuthenticatorAttestationResponse with "tpm" attestation for SHA-1, and check that server succeeds CredentialCreateOptions.cs : 210 Server-ServerPublicKeyCredentialCreationOptions-Req-1 Test server generating ServerPublicKeyCredentialCreationOptionsRequest P-1 Get ServerPublicKeyCredentialCreationOptionsResponse, and check that: (a) response MUST contain ... AuthenticationExtensionsClientInputs.cs : 23 public string AppID { private get; set; } Server-ServerPublicKeyCredentialGetOptionsResponse-Req-1 Test server generating ServerPublicKeyCredentialGetOptionsResponse P-1 Get ServerPublicKeyCredentialGetOptionsResponse, and check that: (a) response MUST contain ... AuthenticationExtensionsClientInputs.cs : 44 public bool? UserVerificationMethod { private get; set; } Server-ServerPublicKeyCredentialGetOptionsResponse-Req-1 Test server generating ServerPublicKeyCredentialGetOptionsResponse P-1 Get ServerPublicKeyCredentialGetOptionsResponse, and check that: (a) response MUST contain ... AuthenticatorAssertionResponse.cs : 128 Server-ServerAuthenticatorAssertionResponse-Resp-3 P4,P6,P7 CryptoUtils.cs 64 (trustpath length 1 with exact match in attestation root certs) Server-ServerAuthenticatorAttestationResponse-Resp-5 Test server processing "packed" FULL attestation P-3 Send a valid ServerAuthenticatorAttestationResponse with FULL "packed" attestation that contains batch certificate, that is simply self referenced in the metadata, and check that server succeeds CryptoUtils.cs 105 - X509RevocationMode.Online makes conformance sad Server-ServerAuthenticatorAttestationResponse-Resp-9 Test server processing "tpm" attestation P-1 Send a valid ServerAuthenticatorAttestationResponse with "tpm" attestation for SHA-256, and check that server succeeds‣ P-2 Send a valid ServerAuthenticatorAttestationResponse with "tpm" attestation for SHA-1, and check that server succeeds‣ P-3 Send a valid ServerAuthenticatorAttestationResponse with "tpm" attestation pubArea.nameAlg is not matching algorithm used for generate attested.name, and check that server succeeds TestController.cs tojson -> serialize serialization error * Json serialization fix Json serialization fix. (Object type vs ToJson()) * Unit test fix * tokenbindig, AppId, UVP Back to 100% conformance. TokenBinding logic readded. AppId: prevent serialization in a nicer way. UV flags are verified differently for conformance testing, otherwise as described in the RFC. * unit test fix (tokenbinding dto parsing) * fix azure pipeline fix azure pipeline's whitespace error + removing unused using * Improve trustanchor test coverage Improve trustanchor test coverage based on codecov report * TestPackedttestationAsyncFailTrustAnchorOnRootCertInTrustPath only works on Windows * Do not make this private * Keep Tokenbinding around * Update AuthenticatorAssertionResponse.cs * Added XML comments to requestTokenBinding * Added comment about UVM * Simplify UVP * format * Reverting some changes (#554) I'm keeping these around until we've understood if we really can drop them * Ignores Demo/Conformance * Refactored away from bool to enum. * File based namespace * format --------- Co-authored-by: Gabor Mihaly <[email protected]> Co-authored-by: googyi <[email protected]>
1 parent 26e5f75 commit 0f9f0cb

23 files changed

+617
-115
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,3 +334,5 @@ ASALocalRun/
334334
/Test/coverage.netcoreapp3.1.cobertura.xml
335335
.DS_Store
336336
/testEnvironments.json
337+
338+
Demo/Conformance/

Demo/TestController.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System.Text;
22
using System.Text.Json;
3-
using System.Text.Json.Serialization;
43
using Fido2NetLib;
54
using Fido2NetLib.Development;
65
using Fido2NetLib.Objects;

Src/Fido2.Models/AuthenticatorAttestationRawResponse.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public sealed class AuthenticatorAttestationRawResponse
1515
public byte[] RawId { get; set; }
1616

1717
[JsonPropertyName("type")]
18-
public PublicKeyCredentialType Type { get; set; } = PublicKeyCredentialType.PublicKey;
18+
public PublicKeyCredentialType? Type { get; set; }
1919

2020
[JsonPropertyName("response")]
2121
public AttestationResponse Response { get; set; }

Src/Fido2.Models/CredentialCreateOptions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ public static CredentialCreateOptions Create(
134134
PubKeyCredParam.ES512,
135135
PubKeyCredParam.RS512,
136136
PubKeyCredParam.PS512,
137+
PubKeyCredParam.RS1
137138
],
138139
AuthenticatorSelection = authenticatorSelection,
139140
Attestation = attestationConveyancePreference,
@@ -185,6 +186,7 @@ public sealed class PubKeyCredParam(
185186
public static readonly PubKeyCredParam PS384 = new(COSE.Algorithm.PS384);
186187
public static readonly PubKeyCredParam PS512 = new(COSE.Algorithm.PS512);
187188
public static readonly PubKeyCredParam Ed25519 = new(COSE.Algorithm.EdDSA);
189+
public static readonly PubKeyCredParam RS1 = new(COSE.Algorithm.RS1);
188190
}
189191

190192
/// <summary>

Src/Fido2.Models/Objects/AuthenticationExtensionsClientInputs.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ public sealed class AuthenticationExtensionsClientInputs
1212
/// </summary>
1313
[JsonPropertyName("example.extension.bool")]
1414
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
15-
public object Example { get; set; }
15+
public bool? Example { get; set; }
1616

1717
/// <summary>
1818
/// This extension allows WebAuthn Relying Parties that have previously registered a credential using the legacy FIDO JavaScript APIs to request an assertion.
1919
/// https://www.w3.org/TR/webauthn/#sctn-appid-extension
2020
/// </summary>
2121
[JsonPropertyName("appid")]
22-
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
22+
[JsonIgnore(Condition = JsonIgnoreCondition.Always)]
2323
public string AppID { get; set; }
2424

2525
/// <summary>
@@ -33,10 +33,11 @@ public sealed class AuthenticationExtensionsClientInputs
3333
/// <summary>
3434
/// This extension enables use of a user verification method.
3535
/// https://www.w3.org/TR/webauthn/#sctn-uvm-extension
36+
/// TODO: Remove this completely as it's removed in L3
3637
/// </summary>
3738
[JsonPropertyName("uvm")]
3839
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
39-
public bool? UserVerificationMethod { get; set; }
40+
public bool? UserVerificationMethod { private get; set; }
4041

4142
#nullable enable
4243
/// <summary>

Src/Fido2.Models/Objects/AuthenticationExtensionsClientOutputs.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public class AuthenticationExtensionsClientOutputs
99
/// </summary>
1010
[JsonPropertyName("example.extension.bool")]
1111
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
12-
public object Example { get; set; }
12+
public bool? Example { get; set; }
1313

1414
#nullable enable
1515

Src/Fido2/AuthenticatorAssertionResponse.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public static AuthenticatorAssertionResponse Parse(AuthenticatorAssertionRawResp
5252
/// <param name="storedSignatureCounter">The stored counter value for this CredentialId</param>
5353
/// <param name="isUserHandleOwnerOfCredId">A function that returns <see langword="true"/> if user handle is owned by the credential ID.</param>
5454
/// <param name="metadataService"></param>
55+
/// <param name="requestTokenBindingId">DO NOT USE - Deprecated, but kept in code due to conformance testing tool</param>
5556
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
5657
public async Task<VerifyAssertionResult> VerifyAsync(
5758
AssertionOptions options,
@@ -61,9 +62,10 @@ public async Task<VerifyAssertionResult> VerifyAsync(
6162
uint storedSignatureCounter,
6263
IsUserHandleOwnerOfCredentialIdAsync isUserHandleOwnerOfCredId,
6364
IMetadataService? metadataService,
65+
byte[]? requestTokenBindingId,
6466
CancellationToken cancellationToken = default)
6567
{
66-
BaseVerify(config.FullyQualifiedOrigins, options.Challenge);
68+
BaseVerify(config.FullyQualifiedOrigins, options.Challenge, requestTokenBindingId);
6769

6870
if (Raw.Type != PublicKeyCredentialType.PublicKey)
6971
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAssertionResponse, Fido2ErrorMessages.AssertionResponseNotPublicKey);
@@ -115,15 +117,20 @@ public async Task<VerifyAssertionResult> VerifyAsync(
115117
// https://www.w3.org/TR/webauthn/#sctn-appid-extension
116118
// FIDO AppID Extension:
117119
// If true, the AppID was used and thus, when verifying an assertion, the Relying Party MUST expect the rpIdHash to be the hash of the AppID, not the RP ID.
120+
118121
var rpid = Raw.ClientExtensionResults?.AppID ?? false ? options.Extensions?.AppID : options.RpId;
122+
119123
byte[] hashedRpId = SHA256.HashData(Encoding.UTF8.GetBytes(rpid ?? string.Empty));
120124
byte[] hash = SHA256.HashData(Raw.Response.ClientDataJson);
121125

122126
if (!authData.RpIdHash.SequenceEqual(hashedRpId))
123127
throw new Fido2VerificationException(Fido2ErrorCode.InvalidRpidHash, Fido2ErrorMessages.InvalidRpidHash);
124128

129+
var conformanceTesting = metadataService != null && metadataService.ConformanceTesting();
130+
125131
// 14. Verify that the UP bit of the flags in authData is set.
126-
if (!authData.UserPresent)
132+
// Todo: Conformance testing verifies the UVP flags differently than W3C spec, simplify this by removing the mention of conformanceTesting when conformance tools are updated)
133+
if (!authData.UserPresent && !conformanceTesting)
127134
throw new Fido2VerificationException(Fido2ErrorCode.UserPresentFlagNotSet, Fido2ErrorMessages.UserPresentFlagNotSet);
128135

129136
// 15. If the Relying Party requires user verification for this assertion, verify that the UV bit of the flags in authData is set.
@@ -174,6 +181,7 @@ public async Task<VerifyAssertionResult> VerifyAsync(
174181
if (authData.SignCount > 0 && authData.SignCount <= storedSignatureCounter)
175182
throw new Fido2VerificationException(Fido2ErrorCode.InvalidSignCount, Fido2ErrorMessages.SignCountIsLessThanSignatureCounter);
176183

184+
177185
return new VerifyAssertionResult
178186
{
179187
CredentialId = Raw.Id,

Src/Fido2/AuthenticatorAttestationResponse.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public async Task<RegisteredPublicKeyCredential> VerifyAsync(
6060
Fido2Configuration config,
6161
IsCredentialIdUniqueToUserAsyncDelegate isCredentialIdUniqueToUser,
6262
IMetadataService? metadataService,
63+
byte[]? requestTokenBindingId,
6364
CancellationToken cancellationToken = default)
6465
{
6566
// https://www.w3.org/TR/webauthn/#registering-a-new-credential
@@ -74,7 +75,10 @@ public async Task<RegisteredPublicKeyCredential> VerifyAsync(
7475

7576
// 8. Verify that the value of C.challenge matches the challenge that was sent to the authenticator in the create() call.
7677
// 9. Verify that the value of C.origin matches the Relying Party's origin.
77-
BaseVerify(config.FullyQualifiedOrigins, originalOptions.Challenge);
78+
// 9.5. Verify that the value of C.tokenBinding.status matches the state of Token Binding for the TLS connection over which the attestation was obtained.
79+
// If Token Binding was used on that TLS connection, also verify that C.tokenBinding.id matches the base64url encoding of the Token Binding ID for the connection.
80+
// Validated in BaseVerify.
81+
BaseVerify(config.FullyQualifiedOrigins, originalOptions.Challenge, requestTokenBindingId);
7882

7983
if (Raw.Id is null || Raw.Id.Length == 0)
8084
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestationResponse, Fido2ErrorMessages.AttestationResponseIdMissing);
@@ -149,7 +153,7 @@ public async Task<RegisteredPublicKeyCredential> VerifyAsync(
149153
if (metadataService?.ConformanceTesting() is true && metadataEntry is null && attType != AttestationType.None && AttestationObject.Fmt is not "fido-u2f")
150154
throw new Fido2VerificationException(Fido2ErrorCode.AaGuidNotFound, "AAGUID not found in MDS test metadata");
151155

152-
TrustAnchor.Verify(metadataEntry, trustPath);
156+
TrustAnchor.Verify(metadataEntry, trustPath, metadataService?.ConformanceTesting() is true ? FidoValidationMode.FidoConformance2024 : FidoValidationMode.Default);
153157

154158
// 22. Assess the attestation trustworthiness using the outputs of the verification procedure in step 14, as follows:
155159
// If self attestation was used, check if self attestation is acceptable under Relying Party policy.
@@ -186,7 +190,7 @@ public async Task<RegisteredPublicKeyCredential> VerifyAsync(
186190

187191
return new RegisteredPublicKeyCredential
188192
{
189-
Type = Raw.Type,
193+
Type = Raw.Type.Value,
190194
Id = authData.AttestedCredentialData.CredentialId,
191195
PublicKey = authData.AttestedCredentialData.CredentialPublicKey.GetBytes(),
192196
SignCount = authData.SignCount,
@@ -253,7 +257,7 @@ private async Task<byte[]> DevicePublicKeyRegistrationAsync(
253257
if (metadataService?.ConformanceTesting() is true && metadataEntry is null && attType != AttestationType.None && devicePublicKeyAuthenticatorOutput.Fmt is not "fido-u2f")
254258
throw new Fido2VerificationException(Fido2ErrorCode.AaGuidNotFound, "AAGUID not found in MDS test metadata");
255259

256-
TrustAnchor.Verify(metadataEntry, trustPath);
260+
TrustAnchor.Verify(metadataEntry, trustPath, metadataService?.ConformanceTesting() is true ? FidoValidationMode.FidoConformance2024 : FidoValidationMode.Default);
257261

258262
// Check status reports for authenticator with undesirable status
259263
var latestStatusReport = metadataEntry?.GetLatestStatusReport();

Src/Fido2/AuthenticatorResponse.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ protected AuthenticatorResponse(ReadOnlySpan<byte> utf8EncodedJson)
4848
Type = response.Type;
4949
Challenge = response.Challenge;
5050
Origin = response.Origin;
51+
TokenBinding = response.TokenBinding;
5152
}
5253

5354
public const int MAX_ORIGINS_TO_PRINT = 5;
@@ -62,7 +63,11 @@ protected AuthenticatorResponse(ReadOnlySpan<byte> utf8EncodedJson)
6263
[JsonPropertyName("origin")]
6364
public string Origin { get; }
6465

65-
protected void BaseVerify(IReadOnlySet<string> fullyQualifiedExpectedOrigins, ReadOnlySpan<byte> originalChallenge)
66+
// [Obsolete("This property is not used and will be removed in a future version once the conformance tool stops testing for it.")]
67+
[JsonPropertyName("tokenBinding")]
68+
public TokenBindingDto? TokenBinding { get; set; }
69+
70+
protected void BaseVerify(IReadOnlySet<string> fullyQualifiedExpectedOrigins, ReadOnlySpan<byte> originalChallenge, byte[]? requestTokenBindingId)
6671
{
6772
if (Type is not "webauthn.create" && Type is not "webauthn.get")
6873
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAuthenticatorResponse, $"Type must be 'webauthn.create' or 'webauthn.get'. Was '{Type}'");
@@ -79,6 +84,10 @@ protected void BaseVerify(IReadOnlySet<string> fullyQualifiedExpectedOrigins, Re
7984
// 12. Verify that the value of C.origin matches the Relying Party's origin.
8085
if (!fullyQualifiedExpectedOrigins.Contains(fullyQualifiedOrigin))
8186
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})");
87+
88+
// 13?. Verify that the value of C.tokenBinding.status matches the state of Token Binding for the TLS connection over which the assertion was obtained.
89+
// If Token Binding was used on that TLS connection, also verify that C.tokenBinding.id matches the base64url encoding of the Token Binding ID for the connection.
90+
TokenBinding?.Verify(requestTokenBindingId);
8291
}
8392

8493
/*

Src/Fido2/Extensions/CryptoUtils.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public static HashAlgorithmName HashAlgFromCOSEAlg(COSE.Algorithm alg)
4949
};
5050
}
5151

52-
public static bool ValidateTrustChain(X509Certificate2[] trustPath, X509Certificate2[] attestationRootCertificates)
52+
public static bool ValidateTrustChain(X509Certificate2[] trustPath, X509Certificate2[] attestationRootCertificates, FidoValidationMode validationMode = FidoValidationMode.Default)
5353
{
5454
// https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-metadata-statement-v2.0-id-20180227.html#widl-MetadataStatement-attestationRootCertificates
5555

@@ -59,6 +59,8 @@ public static bool ValidateTrustChain(X509Certificate2[] trustPath, X509Certific
5959
// A trust anchor can be a root certificate, an intermediate CA certificate or even the attestation certificate itself.
6060

6161
// Let's check the simplest case first. If subject and issuer are the same, and the attestation cert is in the list, that's all the validation we need
62+
63+
// We have the same singular root cert in trustpath and it is in attestationRootCertificates
6264
if (trustPath.Length == 1 && trustPath[0].Subject.Equals(trustPath[0].Issuer, StringComparison.Ordinal))
6365
{
6466
foreach (X509Certificate2 cert in attestationRootCertificates)
@@ -68,7 +70,6 @@ public static bool ValidateTrustChain(X509Certificate2[] trustPath, X509Certific
6870
return true;
6971
}
7072
}
71-
return false;
7273
}
7374

7475
// If the attestation cert is not self signed, we will need to build a chain
@@ -101,7 +102,7 @@ public static bool ValidateTrustChain(X509Certificate2[] trustPath, X509Certific
101102
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
102103

103104
// if the attestation cert has a CDP extension, go ahead and turn on online revocation checking
104-
if (!string.IsNullOrEmpty(CDPFromCertificateExts(trustPath[0].Extensions)))
105+
if (!string.IsNullOrEmpty(CDPFromCertificateExts(trustPath[0].Extensions)) && validationMode != FidoValidationMode.FidoConformance2024)
105106
chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
106107

107108
// don't allow unknown root now that we have a custom root

Src/Fido2/Fido2.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,18 @@ public CredentialCreateOptions RequestNewCredential(
6565
/// <param name="attestationResponse">The attestation response from the authenticator.</param>
6666
/// <param name="originalOptions">The original options that was sent to the client.</param>
6767
/// <param name="isCredentialIdUniqueToUser">The delegate used to validate that the CredentialID is unique to this user.</param>
68+
/// <param name="requestTokenBindingId">DO NOT USE - Deprecated, but kept in code due to conformance testing tool</param>
6869
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
6970
/// <returns></returns>
7071
public async Task<RegisteredPublicKeyCredential> MakeNewCredentialAsync(
7172
AuthenticatorAttestationRawResponse attestationResponse,
7273
CredentialCreateOptions originalOptions,
7374
IsCredentialIdUniqueToUserAsyncDelegate isCredentialIdUniqueToUser,
75+
byte[]? requestTokenBindingId = null,
7476
CancellationToken cancellationToken = default)
7577
{
7678
var parsedResponse = AuthenticatorAttestationResponse.Parse(attestationResponse);
77-
var credential = await parsedResponse.VerifyAsync(originalOptions, _config, isCredentialIdUniqueToUser, _metadataService, cancellationToken);
79+
var credential = await parsedResponse.VerifyAsync(originalOptions, _config, isCredentialIdUniqueToUser, _metadataService, requestTokenBindingId, cancellationToken);
7880

7981
return credential;
8082
}
@@ -105,6 +107,7 @@ public AssertionOptions GetAssertionOptions(
105107
/// <param name="storedDevicePublicKeys">The stored device public keys.</param>
106108
/// <param name="storedSignatureCounter">The stored value of the signature counter.</param>
107109
/// <param name="isUserHandleOwnerOfCredentialIdCallback">The delegate used to validate that the user handle is indeed owned of the CredentialId.</param>
110+
/// <param name="requestTokenBindingId">DO NOT USE - Deprecated, but kept in code due to conformance testing tool</param>
108111
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
109112
/// <returns></returns>
110113
public async Task<VerifyAssertionResult> MakeAssertionAsync(
@@ -114,6 +117,7 @@ public async Task<VerifyAssertionResult> MakeAssertionAsync(
114117
IReadOnlyList<byte[]> storedDevicePublicKeys,
115118
uint storedSignatureCounter,
116119
IsUserHandleOwnerOfCredentialIdAsync isUserHandleOwnerOfCredentialIdCallback,
120+
byte[]? requestTokenBindingId = null,
117121
CancellationToken cancellationToken = default)
118122
{
119123
var parsedResponse = AuthenticatorAssertionResponse.Parse(assertionResponse);
@@ -125,6 +129,7 @@ public async Task<VerifyAssertionResult> MakeAssertionAsync(
125129
storedSignatureCounter,
126130
isUserHandleOwnerOfCredentialIdCallback,
127131
_metadataService,
132+
requestTokenBindingId,
128133
cancellationToken);
129134

130135
return result;

Src/Fido2/FidoValidationMode.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
public enum FidoValidationMode
2+
{
3+
WebAuthNLevel3,
4+
FidoConformance2024,
5+
Default = WebAuthNLevel3
6+
}

Src/Fido2/IFido2.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ Task<VerifyAssertionResult> MakeAssertionAsync(
2020
IReadOnlyList<byte[]> storedDevicePublicKeys,
2121
uint storedSignatureCounter,
2222
IsUserHandleOwnerOfCredentialIdAsync isUserHandleOwnerOfCredentialIdCallback,
23+
byte[]? requestTokenBindingId = null,
2324
CancellationToken cancellationToken = default);
2425

2526
Task<RegisteredPublicKeyCredential> MakeNewCredentialAsync(
2627
AuthenticatorAttestationRawResponse attestationResponse,
2728
CredentialCreateOptions originalOptions,
2829
IsCredentialIdUniqueToUserAsyncDelegate isCredentialIdUniqueToUser,
30+
byte[]? requestTokenBindingId = null,
2931
CancellationToken cancellationToken = default);
3032

3133
CredentialCreateOptions RequestNewCredential(

Src/Fido2/TokenBindingDto.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using System.Text.Json.Serialization;
2+
namespace Fido2NetLib;
3+
4+
public class TokenBindingDto
5+
{
6+
/// <summary>
7+
/// Either "present" or "supported". https://www.w3.org/TR/webauthn/#enumdef-tokenbindingstatus
8+
/// supported: Indicates the client supports token binding, but it was not negotiated when communicating with the Relying Party.
9+
/// present: Indicates token binding was used when communicating with the Relying Party. In this case, the id member MUST be present
10+
/// </summary>
11+
[JsonPropertyName("status")]
12+
public string? Status { get; set; }
13+
14+
/// <summary>
15+
/// This member MUST be present if status is present, and MUST a base64url encoding of the Token Binding ID that was used when communicating with the Relying Party.
16+
/// </summary>
17+
[JsonPropertyName("id")]
18+
public string? Id { get; set; }
19+
20+
public void Verify(byte[]? requestTokenbinding)
21+
{
22+
// validation by the FIDO conformance tool (more than spec says)
23+
switch (Status)
24+
{
25+
case "present":
26+
if (string.IsNullOrEmpty(Id))
27+
throw new Fido2VerificationException("TokenBinding status was present but Id is missing");
28+
var b64 = Base64Url.Encode(requestTokenbinding);
29+
if (Id != b64)
30+
throw new Fido2VerificationException("Tokenbinding Id does not match");
31+
break;
32+
case "supported":
33+
case "not-supported":
34+
break;
35+
default:
36+
throw new Fido2VerificationException("Malformed tokenbinding status field");
37+
}
38+
}
39+
}

0 commit comments

Comments
 (0)