Skip to content

Commit

Permalink
conformance 1.7.20 4 (#531)
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
3 people authored Oct 18, 2024
1 parent 26e5f75 commit 0f9f0cb
Show file tree
Hide file tree
Showing 23 changed files with 617 additions and 115 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -334,3 +334,5 @@ ASALocalRun/
/Test/coverage.netcoreapp3.1.cobertura.xml
.DS_Store
/testEnvironments.json

Demo/Conformance/
1 change: 0 additions & 1 deletion Demo/TestController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Fido2NetLib;
using Fido2NetLib.Development;
using Fido2NetLib.Objects;
Expand Down
2 changes: 1 addition & 1 deletion Src/Fido2.Models/AuthenticatorAttestationRawResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public sealed class AuthenticatorAttestationRawResponse
public byte[] RawId { get; set; }

[JsonPropertyName("type")]
public PublicKeyCredentialType Type { get; set; } = PublicKeyCredentialType.PublicKey;
public PublicKeyCredentialType? Type { get; set; }

[JsonPropertyName("response")]
public AttestationResponse Response { get; set; }
Expand Down
2 changes: 2 additions & 0 deletions Src/Fido2.Models/CredentialCreateOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ public static CredentialCreateOptions Create(
PubKeyCredParam.ES512,
PubKeyCredParam.RS512,
PubKeyCredParam.PS512,
PubKeyCredParam.RS1
],
AuthenticatorSelection = authenticatorSelection,
Attestation = attestationConveyancePreference,
Expand Down Expand Up @@ -185,6 +186,7 @@ public sealed class PubKeyCredParam(
public static readonly PubKeyCredParam PS384 = new(COSE.Algorithm.PS384);
public static readonly PubKeyCredParam PS512 = new(COSE.Algorithm.PS512);
public static readonly PubKeyCredParam Ed25519 = new(COSE.Algorithm.EdDSA);
public static readonly PubKeyCredParam RS1 = new(COSE.Algorithm.RS1);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ public sealed class AuthenticationExtensionsClientInputs
/// </summary>
[JsonPropertyName("example.extension.bool")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public object Example { get; set; }
public bool? Example { get; set; }

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

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

#nullable enable
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class AuthenticationExtensionsClientOutputs
/// </summary>
[JsonPropertyName("example.extension.bool")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public object Example { get; set; }
public bool? Example { get; set; }

#nullable enable

Expand Down
12 changes: 10 additions & 2 deletions Src/Fido2/AuthenticatorAssertionResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public static AuthenticatorAssertionResponse Parse(AuthenticatorAssertionRawResp
/// <param name="storedSignatureCounter">The stored counter value for this CredentialId</param>
/// <param name="isUserHandleOwnerOfCredId">A function that returns <see langword="true"/> if user handle is owned by the credential ID.</param>
/// <param name="metadataService"></param>
/// <param name="requestTokenBindingId">DO NOT USE - Deprecated, but kept in code due to conformance testing tool</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
public async Task<VerifyAssertionResult> VerifyAsync(
AssertionOptions options,
Expand All @@ -61,9 +62,10 @@ public async Task<VerifyAssertionResult> VerifyAsync(
uint storedSignatureCounter,
IsUserHandleOwnerOfCredentialIdAsync isUserHandleOwnerOfCredId,
IMetadataService? metadataService,
byte[]? requestTokenBindingId,
CancellationToken cancellationToken = default)
{
BaseVerify(config.FullyQualifiedOrigins, options.Challenge);
BaseVerify(config.FullyQualifiedOrigins, options.Challenge, requestTokenBindingId);

if (Raw.Type != PublicKeyCredentialType.PublicKey)
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAssertionResponse, Fido2ErrorMessages.AssertionResponseNotPublicKey);
Expand Down Expand Up @@ -115,15 +117,20 @@ public async Task<VerifyAssertionResult> VerifyAsync(
// https://www.w3.org/TR/webauthn/#sctn-appid-extension
// FIDO AppID Extension:
// 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.

var rpid = Raw.ClientExtensionResults?.AppID ?? false ? options.Extensions?.AppID : options.RpId;

byte[] hashedRpId = SHA256.HashData(Encoding.UTF8.GetBytes(rpid ?? string.Empty));
byte[] hash = SHA256.HashData(Raw.Response.ClientDataJson);

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

var conformanceTesting = metadataService != null && metadataService.ConformanceTesting();

// 14. Verify that the UP bit of the flags in authData is set.
if (!authData.UserPresent)
// Todo: Conformance testing verifies the UVP flags differently than W3C spec, simplify this by removing the mention of conformanceTesting when conformance tools are updated)
if (!authData.UserPresent && !conformanceTesting)
throw new Fido2VerificationException(Fido2ErrorCode.UserPresentFlagNotSet, Fido2ErrorMessages.UserPresentFlagNotSet);

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


return new VerifyAssertionResult
{
CredentialId = Raw.Id,
Expand Down
12 changes: 8 additions & 4 deletions Src/Fido2/AuthenticatorAttestationResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public async Task<RegisteredPublicKeyCredential> VerifyAsync(
Fido2Configuration config,
IsCredentialIdUniqueToUserAsyncDelegate isCredentialIdUniqueToUser,
IMetadataService? metadataService,
byte[]? requestTokenBindingId,
CancellationToken cancellationToken = default)
{
// https://www.w3.org/TR/webauthn/#registering-a-new-credential
Expand All @@ -74,7 +75,10 @@ public async Task<RegisteredPublicKeyCredential> VerifyAsync(

// 8. Verify that the value of C.challenge matches the challenge that was sent to the authenticator in the create() call.
// 9. Verify that the value of C.origin matches the Relying Party's origin.
BaseVerify(config.FullyQualifiedOrigins, originalOptions.Challenge);
// 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.
// 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.
// Validated in BaseVerify.
BaseVerify(config.FullyQualifiedOrigins, originalOptions.Challenge, requestTokenBindingId);

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

TrustAnchor.Verify(metadataEntry, trustPath);
TrustAnchor.Verify(metadataEntry, trustPath, metadataService?.ConformanceTesting() is true ? FidoValidationMode.FidoConformance2024 : FidoValidationMode.Default);

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

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

TrustAnchor.Verify(metadataEntry, trustPath);
TrustAnchor.Verify(metadataEntry, trustPath, metadataService?.ConformanceTesting() is true ? FidoValidationMode.FidoConformance2024 : FidoValidationMode.Default);

// Check status reports for authenticator with undesirable status
var latestStatusReport = metadataEntry?.GetLatestStatusReport();
Expand Down
11 changes: 10 additions & 1 deletion Src/Fido2/AuthenticatorResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ protected AuthenticatorResponse(ReadOnlySpan<byte> utf8EncodedJson)
Type = response.Type;
Challenge = response.Challenge;
Origin = response.Origin;
TokenBinding = response.TokenBinding;
}

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

protected void BaseVerify(IReadOnlySet<string> fullyQualifiedExpectedOrigins, ReadOnlySpan<byte> originalChallenge)
// [Obsolete("This property is not used and will be removed in a future version once the conformance tool stops testing for it.")]
[JsonPropertyName("tokenBinding")]
public TokenBindingDto? TokenBinding { get; set; }

protected void BaseVerify(IReadOnlySet<string> fullyQualifiedExpectedOrigins, ReadOnlySpan<byte> originalChallenge, byte[]? requestTokenBindingId)
{
if (Type is not "webauthn.create" && Type is not "webauthn.get")
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAuthenticatorResponse, $"Type must be 'webauthn.create' or 'webauthn.get'. Was '{Type}'");
Expand All @@ -79,6 +84,10 @@ protected void BaseVerify(IReadOnlySet<string> fullyQualifiedExpectedOrigins, Re
// 12. 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})");

// 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.
// 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.
TokenBinding?.Verify(requestTokenBindingId);
}

/*
Expand Down
7 changes: 4 additions & 3 deletions Src/Fido2/Extensions/CryptoUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public static HashAlgorithmName HashAlgFromCOSEAlg(COSE.Algorithm alg)
};
}

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

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

// 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

// We have the same singular root cert in trustpath and it is in attestationRootCertificates
if (trustPath.Length == 1 && trustPath[0].Subject.Equals(trustPath[0].Issuer, StringComparison.Ordinal))
{
foreach (X509Certificate2 cert in attestationRootCertificates)
Expand All @@ -68,7 +70,6 @@ public static bool ValidateTrustChain(X509Certificate2[] trustPath, X509Certific
return true;
}
}
return false;
}

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

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

// don't allow unknown root now that we have a custom root
Expand Down
7 changes: 6 additions & 1 deletion Src/Fido2/Fido2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,18 @@ public CredentialCreateOptions RequestNewCredential(
/// <param name="attestationResponse">The attestation response from the authenticator.</param>
/// <param name="originalOptions">The original options that was sent to the client.</param>
/// <param name="isCredentialIdUniqueToUser">The delegate used to validate that the CredentialID is unique to this user.</param>
/// <param name="requestTokenBindingId">DO NOT USE - Deprecated, but kept in code due to conformance testing tool</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns></returns>
public async Task<RegisteredPublicKeyCredential> MakeNewCredentialAsync(
AuthenticatorAttestationRawResponse attestationResponse,
CredentialCreateOptions originalOptions,
IsCredentialIdUniqueToUserAsyncDelegate isCredentialIdUniqueToUser,
byte[]? requestTokenBindingId = null,
CancellationToken cancellationToken = default)
{
var parsedResponse = AuthenticatorAttestationResponse.Parse(attestationResponse);
var credential = await parsedResponse.VerifyAsync(originalOptions, _config, isCredentialIdUniqueToUser, _metadataService, cancellationToken);
var credential = await parsedResponse.VerifyAsync(originalOptions, _config, isCredentialIdUniqueToUser, _metadataService, requestTokenBindingId, cancellationToken);

return credential;
}
Expand Down Expand Up @@ -105,6 +107,7 @@ public AssertionOptions GetAssertionOptions(
/// <param name="storedDevicePublicKeys">The stored device public keys.</param>
/// <param name="storedSignatureCounter">The stored value of the signature counter.</param>
/// <param name="isUserHandleOwnerOfCredentialIdCallback">The delegate used to validate that the user handle is indeed owned of the CredentialId.</param>
/// <param name="requestTokenBindingId">DO NOT USE - Deprecated, but kept in code due to conformance testing tool</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns></returns>
public async Task<VerifyAssertionResult> MakeAssertionAsync(
Expand All @@ -114,6 +117,7 @@ public async Task<VerifyAssertionResult> MakeAssertionAsync(
IReadOnlyList<byte[]> storedDevicePublicKeys,
uint storedSignatureCounter,
IsUserHandleOwnerOfCredentialIdAsync isUserHandleOwnerOfCredentialIdCallback,
byte[]? requestTokenBindingId = null,
CancellationToken cancellationToken = default)
{
var parsedResponse = AuthenticatorAssertionResponse.Parse(assertionResponse);
Expand All @@ -125,6 +129,7 @@ public async Task<VerifyAssertionResult> MakeAssertionAsync(
storedSignatureCounter,
isUserHandleOwnerOfCredentialIdCallback,
_metadataService,
requestTokenBindingId,
cancellationToken);

return result;
Expand Down
6 changes: 6 additions & 0 deletions Src/Fido2/FidoValidationMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
public enum FidoValidationMode
{
WebAuthNLevel3,
FidoConformance2024,
Default = WebAuthNLevel3
}
2 changes: 2 additions & 0 deletions Src/Fido2/IFido2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ Task<VerifyAssertionResult> MakeAssertionAsync(
IReadOnlyList<byte[]> storedDevicePublicKeys,
uint storedSignatureCounter,
IsUserHandleOwnerOfCredentialIdAsync isUserHandleOwnerOfCredentialIdCallback,
byte[]? requestTokenBindingId = null,
CancellationToken cancellationToken = default);

Task<RegisteredPublicKeyCredential> MakeNewCredentialAsync(
AuthenticatorAttestationRawResponse attestationResponse,
CredentialCreateOptions originalOptions,
IsCredentialIdUniqueToUserAsyncDelegate isCredentialIdUniqueToUser,
byte[]? requestTokenBindingId = null,
CancellationToken cancellationToken = default);

CredentialCreateOptions RequestNewCredential(
Expand Down
39 changes: 39 additions & 0 deletions Src/Fido2/TokenBindingDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System.Text.Json.Serialization;
namespace Fido2NetLib;

public class TokenBindingDto
{
/// <summary>
/// Either "present" or "supported". https://www.w3.org/TR/webauthn/#enumdef-tokenbindingstatus
/// supported: Indicates the client supports token binding, but it was not negotiated when communicating with the Relying Party.
/// present: Indicates token binding was used when communicating with the Relying Party. In this case, the id member MUST be present
/// </summary>
[JsonPropertyName("status")]
public string? Status { get; set; }

/// <summary>
/// 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.
/// </summary>
[JsonPropertyName("id")]
public string? Id { get; set; }

public void Verify(byte[]? requestTokenbinding)
{
// validation by the FIDO conformance tool (more than spec says)
switch (Status)
{
case "present":
if (string.IsNullOrEmpty(Id))
throw new Fido2VerificationException("TokenBinding status was present but Id is missing");
var b64 = Base64Url.Encode(requestTokenbinding);
if (Id != b64)
throw new Fido2VerificationException("Tokenbinding Id does not match");
break;
case "supported":
case "not-supported":
break;
default:
throw new Fido2VerificationException("Malformed tokenbinding status field");
}
}
}
Loading

0 comments on commit 0f9f0cb

Please sign in to comment.