From 944f1682fa62cbf1972a1e8c1accbe6ce381c7eb Mon Sep 17 00:00:00 2001 From: Gabor Mihaly Date: Tue, 24 Oct 2023 18:27:56 +0200 Subject: [PATCH 1/8] FIDO Conformance Tools v1.7.15 fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- Demo/TestController.cs | 5 +++-- .../AuthenticatorAttestationRawResponse.cs | 2 +- Src/Fido2.Models/CredentialCreateOptions.cs | 4 +++- .../AuthenticationExtensionsClientInputs.cs | 9 ++++++-- Src/Fido2/AuthenticatorAssertionResponse.cs | 21 +++++++++++-------- Src/Fido2/AuthenticatorAttestationResponse.cs | 6 +++--- Src/Fido2/Extensions/CryptoUtils.cs | 15 ++++++------- Src/Fido2/TrustAnchor.cs | 9 ++++++-- 8 files changed, 44 insertions(+), 27 deletions(-) diff --git a/Demo/TestController.cs b/Demo/TestController.cs index 2d5e64f6..3785bd7f 100644 --- a/Demo/TestController.cs +++ b/Demo/TestController.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -74,7 +75,7 @@ public JsonResult MakeCredentialOptionsTest([FromBody] TEST_MakeCredentialParams var options = _fido2.RequestNewCredential(user, existingKeys, opts.AuthenticatorSelection, opts.Attestation, exts); // 4. Temporarily store options, session/in-memory cache/redis/db - HttpContext.Session.SetString("fido2.attestationOptions", options.ToJson()); + HttpContext.Session.SetString("fido2.attestationOptions", JsonSerializer.Serialize(options)); // 5. return options to client return Json(options); @@ -144,7 +145,7 @@ public IActionResult AssertionOptionsTest([FromBody] TEST_AssertionClientParams ); // 4. Temporarily store options, session/in-memory cache/redis/db - HttpContext.Session.SetString("fido2.assertionOptions", options.ToJson()); + HttpContext.Session.SetString("fido2.assertionOptions", JsonSerializer.Serialize(options)); // 5. Return options to client return Json(options); diff --git a/Src/Fido2.Models/AuthenticatorAttestationRawResponse.cs b/Src/Fido2.Models/AuthenticatorAttestationRawResponse.cs index ff05ee4d..25d5e456 100644 --- a/Src/Fido2.Models/AuthenticatorAttestationRawResponse.cs +++ b/Src/Fido2.Models/AuthenticatorAttestationRawResponse.cs @@ -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 ResponseData Response { get; set; } diff --git a/Src/Fido2.Models/CredentialCreateOptions.cs b/Src/Fido2.Models/CredentialCreateOptions.cs index 1625486c..29c48796 100644 --- a/Src/Fido2.Models/CredentialCreateOptions.cs +++ b/Src/Fido2.Models/CredentialCreateOptions.cs @@ -93,6 +93,7 @@ public static CredentialCreateOptions Create(Fido2Configuration config, byte[] c PubKeyCredParam.ES512, PubKeyCredParam.RS512, PubKeyCredParam.PS512, + PubKeyCredParam.RS1, }, AuthenticatorSelection = authenticatorSelection, Attestation = attestationConveyancePreference, @@ -148,6 +149,7 @@ public PubKeyCredParam(COSE.Algorithm alg, PublicKeyCredentialType type = Public 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); } /// @@ -205,7 +207,7 @@ public class AuthenticatorSelection [JsonPropertyName("residentKey")] public ResidentKeyRequirement ResidentKey { - get => _residentKey; + private get => _residentKey; set { _residentKey = value; diff --git a/Src/Fido2.Models/Objects/AuthenticationExtensionsClientInputs.cs b/Src/Fido2.Models/Objects/AuthenticationExtensionsClientInputs.cs index 74b850d1..3baed9ba 100644 --- a/Src/Fido2.Models/Objects/AuthenticationExtensionsClientInputs.cs +++ b/Src/Fido2.Models/Objects/AuthenticationExtensionsClientInputs.cs @@ -20,7 +20,12 @@ public sealed class AuthenticationExtensionsClientInputs /// [JsonPropertyName("appid")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string AppID { get; set; } + public string AppID { private get; set; } + + public string GetAppID() + { + return AppID; + } /// /// This extension enables the WebAuthn Relying Party to determine which extensions the authenticator supports. @@ -36,7 +41,7 @@ public sealed class AuthenticationExtensionsClientInputs /// [JsonPropertyName("uvm")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public bool? UserVerificationMethod { get; set; } + public bool? UserVerificationMethod { private get; set; } #nullable enable /// diff --git a/Src/Fido2/AuthenticatorAssertionResponse.cs b/Src/Fido2/AuthenticatorAssertionResponse.cs index 79b90147..4d363214 100644 --- a/Src/Fido2/AuthenticatorAssertionResponse.cs +++ b/Src/Fido2/AuthenticatorAssertionResponse.cs @@ -118,20 +118,23 @@ public async Task 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.Extensions?.AppID ?? false ? options.Extensions?.AppID : options.RpId; + var rpid = Raw.Extensions?.AppID ?? false ? options.Extensions?.GetAppID() : 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); - // 14. Verify that the UP bit of the flags in authData is set. - if (!authData.UserPresent) - 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. - if (options.UserVerification is UserVerificationRequirement.Required && !authData.UserVerified) - throw new Fido2VerificationException(Fido2ErrorCode.UserVerificationRequirementNotMet, Fido2ErrorMessages.UserVerificationRequirementNotMet); + if (options.UserVerification is UserVerificationRequirement.Required) + { + // 14. Verify that the UP bit of the flags in authData is set. + if (!authData.UserPresent) + 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. + if (!authData.UserVerified) + throw new Fido2VerificationException(Fido2ErrorCode.UserVerificationRequirementNotMet, Fido2ErrorMessages.UserVerificationRequirementNotMet); + } // 16. If the credential backup state is used as part of Relying Party business logic or policy, let currentBe and currentBs be the values of the BE and BS bits, respectively, of the flags in authData. // Compare currentBe and currentBs with credentialRecord.BE and credentialRecord.BS and apply Relying Party policy, if any. @@ -214,7 +217,7 @@ public async Task VerifyAsync( if (metadataService?.ConformanceTesting() is true && metadataEntry is null && attType != AttestationType.None && 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); } return new VerifyAssertionResult diff --git a/Src/Fido2/AuthenticatorAttestationResponse.cs b/Src/Fido2/AuthenticatorAttestationResponse.cs index a698ae90..6f693e7c 100644 --- a/Src/Fido2/AuthenticatorAttestationResponse.cs +++ b/Src/Fido2/AuthenticatorAttestationResponse.cs @@ -149,7 +149,7 @@ public async Task 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); // 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. @@ -186,7 +186,7 @@ public async Task VerifyAsync( return new RegisteredPublicKeyCredential { - Type = Raw.Type, + Type = Raw.Type.Value, Id = authData.AttestedCredentialData.CredentialId, PublicKey = authData.AttestedCredentialData.CredentialPublicKey.GetBytes(), SignCount = authData.SignCount, @@ -250,7 +250,7 @@ private async Task 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); // Check status reports for authenticator with undesirable status var latestStatusReport = metadataEntry?.GetLatestStatusReport(); diff --git a/Src/Fido2/Extensions/CryptoUtils.cs b/Src/Fido2/Extensions/CryptoUtils.cs index 94a6b258..a2992c9c 100644 --- a/Src/Fido2/Extensions/CryptoUtils.cs +++ b/Src/Fido2/Extensions/CryptoUtils.cs @@ -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, bool conformance = false) { // https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-metadata-statement-v2.0-id-20180227.html#widl-MetadataStatement-attestationRootCertificates @@ -59,7 +59,9 @@ 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 - if (trustPath.Length == 1 && trustPath[0].Subject.Equals(trustPath[0].Issuer, StringComparison.Ordinal)) + + // We have the same singular root cert in trustpath and it is in attestationRootCertificates + if (trustPath.Length == 1) { foreach (X509Certificate2 cert in attestationRootCertificates) { @@ -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 @@ -101,10 +102,10 @@ 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))) - chain.ChainPolicy.RevocationMode = X509RevocationMode.Online; - - // don't allow unknown root now that we have a custom root + if (!string.IsNullOrEmpty(CDPFromCertificateExts(trustPath[0].Extensions)) && !conformance) + chain.ChainPolicy.RevocationMode = X509RevocationMode.Online; + + // don't allow unknown root now that we have a custom root chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag; // now, verify chain again with all checks turned on diff --git a/Src/Fido2/TrustAnchor.cs b/Src/Fido2/TrustAnchor.cs index 6a02daed..708074c0 100644 --- a/Src/Fido2/TrustAnchor.cs +++ b/Src/Fido2/TrustAnchor.cs @@ -8,7 +8,7 @@ namespace Fido2NetLib; public static class TrustAnchor { - public static void Verify(MetadataBLOBPayloadEntry? metadataEntry, X509Certificate2[] trustPath) + public static void Verify(MetadataBLOBPayloadEntry? metadataEntry, X509Certificate2[] trustPath, bool conformance) { if (trustPath != null && metadataEntry?.MetadataStatement?.AttestationTypes is not null) { @@ -29,7 +29,12 @@ static bool ContainsAttestationType(MetadataBLOBPayloadEntry entry, MetadataAtte attestationRootCertificates[i] = new X509Certificate2(Convert.FromBase64String(certStrings[i])); } - if (!CryptoUtils.ValidateTrustChain(trustPath, attestationRootCertificates)) + if (trustPath.Length > 1 && attestationRootCertificates.Any(c => string.Equals(c.Thumbprint, trustPath[^1].Thumbprint, StringComparison.Ordinal))) + { + throw new Fido2VerificationException(Fido2ErrorMessages.InvalidCertificateChain); + } + + if (!CryptoUtils.ValidateTrustChain(trustPath, attestationRootCertificates, conformance)) { throw new Fido2VerificationException(Fido2ErrorMessages.InvalidCertificateChain); } From b700a2fe840acfe6dde39e29401918f7716de212 Mon Sep 17 00:00:00 2001 From: Gabor Mihaly Date: Wed, 25 Oct 2023 10:28:41 +0200 Subject: [PATCH 2/8] Json serialization fix Json serialization fix. (Object type vs ToJson()) --- Demo/TestController.cs | 4 +- .../AuthenticationExtensionsClientInputs.cs | 2 +- .../AuthenticationExtensionsClientOutputs.cs | 2 +- Test/AuthenticatorResponse.cs | 42 +++++++++---------- Test/Fido2Tests.cs | 2 +- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Demo/TestController.cs b/Demo/TestController.cs index 3785bd7f..bc361bec 100644 --- a/Demo/TestController.cs +++ b/Demo/TestController.cs @@ -75,7 +75,7 @@ public JsonResult MakeCredentialOptionsTest([FromBody] TEST_MakeCredentialParams var options = _fido2.RequestNewCredential(user, existingKeys, opts.AuthenticatorSelection, opts.Attestation, exts); // 4. Temporarily store options, session/in-memory cache/redis/db - HttpContext.Session.SetString("fido2.attestationOptions", JsonSerializer.Serialize(options)); + HttpContext.Session.SetString("fido2.attestationOptions", options.ToJson()); // 5. return options to client return Json(options); @@ -145,7 +145,7 @@ public IActionResult AssertionOptionsTest([FromBody] TEST_AssertionClientParams ); // 4. Temporarily store options, session/in-memory cache/redis/db - HttpContext.Session.SetString("fido2.assertionOptions", JsonSerializer.Serialize(options)); + HttpContext.Session.SetString("fido2.assertionOptions", options.ToJson()); // 5. Return options to client return Json(options); diff --git a/Src/Fido2.Models/Objects/AuthenticationExtensionsClientInputs.cs b/Src/Fido2.Models/Objects/AuthenticationExtensionsClientInputs.cs index 3baed9ba..b2742749 100644 --- a/Src/Fido2.Models/Objects/AuthenticationExtensionsClientInputs.cs +++ b/Src/Fido2.Models/Objects/AuthenticationExtensionsClientInputs.cs @@ -12,7 +12,7 @@ public sealed class AuthenticationExtensionsClientInputs /// [JsonPropertyName("example.extension.bool")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public object Example { get; set; } + public bool? Example { get; set; } /// /// This extension allows WebAuthn Relying Parties that have previously registered a credential using the legacy FIDO JavaScript APIs to request an assertion. diff --git a/Src/Fido2.Models/Objects/AuthenticationExtensionsClientOutputs.cs b/Src/Fido2.Models/Objects/AuthenticationExtensionsClientOutputs.cs index 6b80ee80..a96cd0b5 100644 --- a/Src/Fido2.Models/Objects/AuthenticationExtensionsClientOutputs.cs +++ b/Src/Fido2.Models/Objects/AuthenticationExtensionsClientOutputs.cs @@ -9,7 +9,7 @@ public class AuthenticationExtensionsClientOutputs /// [JsonPropertyName("example.extension.bool")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public object Example { get; set; } + public bool? Example { get; set; } #nullable enable diff --git a/Test/AuthenticatorResponse.cs b/Test/AuthenticatorResponse.cs index cd2221bf..46243605 100644 --- a/Test/AuthenticatorResponse.cs +++ b/Test/AuthenticatorResponse.cs @@ -257,7 +257,7 @@ public void TestAuthenticatorAttestationRawResponse() { AppID = true, Extensions = new string[] { "foo", "bar" }, - Example = "test", + Example = true, UserVerificationMethod = new ulong[][] { new ulong[] @@ -283,7 +283,7 @@ public void TestAuthenticatorAttestationRawResponse() Assert.Equal(clientDataJson, rawResponse.Response.ClientDataJson); Assert.True(rawResponse.Extensions.AppID); Assert.Equal(new string[] { "foo", "bar" }, rawResponse.Extensions.Extensions); - Assert.Equal("test", rawResponse.Extensions.Example); + Assert.Equal(true, rawResponse.Extensions.Example); Assert.Equal((ulong)4, rawResponse.Extensions.UserVerificationMethod[0][0]); Assert.True(rawResponse.Extensions.PRF.Enabled); Assert.Equal(rawResponse.Extensions.PRF.Results.First, new byte[] { 0xf1, 0xd0 }); @@ -1238,7 +1238,7 @@ public void TestAuthenticatorAssertionRawResponse() { AppID = true, Extensions = new string[] { "foo", "bar" }, - Example = "test", + Example = true, UserVerificationMethod = new ulong[][] { new ulong[] @@ -1266,7 +1266,7 @@ public void TestAuthenticatorAssertionRawResponse() Assert.Equal(new byte[] { 0xf1, 0xd0 }, assertionResponse.Response.UserHandle); Assert.True(assertionResponse.Extensions.AppID); Assert.Equal(new string[] { "foo", "bar" }, assertionResponse.Extensions.Extensions); - Assert.Equal("test", assertionResponse.Extensions.Example); + Assert.Equal(true, assertionResponse.Extensions.Example); Assert.Equal((ulong)4, assertionResponse.Extensions.UserVerificationMethod[0][0]); Assert.True(assertionResponse.Extensions.PRF.Enabled); Assert.Equal(new byte[] { 0xf1, 0xd0 }, assertionResponse.Extensions.PRF.Results.First); @@ -1314,7 +1314,7 @@ public void TestAuthenticatorAssertionTypeNotPublicKey() { AppID = false, Extensions = new string[] { "foo", "bar" }, - Example = "test", + Example = true, UserVerificationMethod = new ulong[][] { new ulong[] @@ -1382,7 +1382,7 @@ public void TestAuthenticatorAssertionIdMissing() { AppID = false, Extensions = new string[] { "foo", "bar" }, - Example = "test", + Example = true, UserVerificationMethod = new ulong[][] { new ulong[] @@ -1451,7 +1451,7 @@ public void TestAuthenticatorAssertionRawIdMissing() { AppID = false, Extensions = new string[] { "foo", "bar" }, - Example = "test", + Example = true, UserVerificationMethod = new ulong[][] { new ulong[] @@ -1520,7 +1520,7 @@ public void TestAuthenticatorAssertionUserHandleEmpty() { AppID = false, Extensions = new string[] { "foo", "bar" }, - Example = "test", + Example = true, UserVerificationMethod = new ulong[][] { new ulong[] @@ -1589,7 +1589,7 @@ public void TestAuthenticatorAssertionUserHandleNotOwnerOfPublicKey() { AppID = false, Extensions = new string[] { "foo", "bar" }, - Example = "test", + Example = true, UserVerificationMethod = new ulong[][] { new ulong[] @@ -1658,7 +1658,7 @@ public void TestAuthenticatorAssertionTypeNotWebAuthnGet() { AppID = false, Extensions = new string[] { "foo", "bar" }, - Example = "test", + Example = true, UserVerificationMethod = new ulong[][] { new ulong[] @@ -1729,7 +1729,7 @@ public void TestAuthenticatorAssertionAppId() { AppID = true, Extensions = new string[] { "foo", "bar" }, - Example = "test", + Example = true, UserVerificationMethod = new ulong[][] { new ulong[] @@ -1799,7 +1799,7 @@ public void TestAuthenticatorAssertionInvalidRpIdHash() { AppID = false, Extensions = new string[] { "foo", "bar" }, - Example = "test", + Example = true, UserVerificationMethod = new ulong[][] { new ulong[] @@ -1870,7 +1870,7 @@ public void TestAuthenticatorAssertionUPRequirementNotMet() { AppID = false, Extensions = new string[] { "foo", "bar" }, - Example = "test", + Example = true, UserVerificationMethod = new ulong[][] { new ulong[] @@ -1940,7 +1940,7 @@ public void TestAuthenticatorAssertionUVPolicyNotMet() { AppID = false, Extensions = new string[] { "foo", "bar" }, - Example = "test", + Example = true, UserVerificationMethod = new ulong[][] { new ulong[] @@ -2008,7 +2008,7 @@ public void TestAuthenticatorAssertionBEPolicyRequired() { AppID = false, Extensions = new string[] { "foo", "bar" }, - Example = "test", + Example = true, UserVerificationMethod = new ulong[][] { new ulong[] @@ -2077,7 +2077,7 @@ public void TestAuthenticatorAssertionBEPolicyDisallow() { AppID = false, Extensions = new string[] { "foo", "bar" }, - Example = "test", + Example = true, UserVerificationMethod = new ulong[][] { new ulong[] @@ -2146,7 +2146,7 @@ public void TestAuthenticatorAssertionBSPolicyRequired() { AppID = false, Extensions = new string[] { "foo", "bar" }, - Example = "test", + Example = true, UserVerificationMethod = new ulong[][] { new ulong[] @@ -2215,7 +2215,7 @@ public void TestAuthenticatorAssertionBSPolicyDisallow() { AppID = false, Extensions = new string[] { "foo", "bar" }, - Example = "test", + Example = true, UserVerificationMethod = new ulong[][] { new ulong[] @@ -2285,7 +2285,7 @@ public void TestAuthenticatorAssertionStoredPublicKeyMissing() { AppID = false, Extensions = new string[] { "foo", "bar" }, - Example = "test", + Example = true, UserVerificationMethod = new ulong[][] { new ulong[] @@ -2354,7 +2354,7 @@ public void TestAuthenticatorAssertionInvalidSignature() { AppID = false, Extensions = new string[] { "foo", "bar" }, - Example = "test", + Example = true, UserVerificationMethod = new ulong[][] { new ulong[] @@ -2430,7 +2430,7 @@ public void TestAuthenticatorAssertionSignCountSignature() { AppID = false, Extensions = new string[] { "foo", "bar" }, - Example = "test", + Example = true, UserVerificationMethod = new ulong[][] { new ulong[] diff --git a/Test/Fido2Tests.cs b/Test/Fido2Tests.cs index 3bc5ed99..78a4817d 100644 --- a/Test/Fido2Tests.cs +++ b/Test/Fido2Tests.cs @@ -168,7 +168,7 @@ public Attestation() { AppID = true, Extensions = new string[] { "foo", "bar" }, - Example = "test", + Example = true, UserVerificationMethod = new ulong[][] { new ulong[] From 2b2382b8ca35ee5539932da424f891e81e66e1c1 Mon Sep 17 00:00:00 2001 From: Gabor Mihaly Date: Wed, 25 Oct 2023 10:40:07 +0200 Subject: [PATCH 3/8] Unit test fix --- Test/CryptoUtilsTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Test/CryptoUtilsTests.cs b/Test/CryptoUtilsTests.cs index 2ce5d81a..505f75db 100644 --- a/Test/CryptoUtilsTests.cs +++ b/Test/CryptoUtilsTests.cs @@ -66,8 +66,8 @@ public void TestValidateTrustChainSubAnchor() Assert.False(0 == attestationRootCertificates[0].Issuer.CompareTo(attestationRootCertificates[0].Subject)); Assert.True(CryptoUtils.ValidateTrustChain(trustPath, attestationRootCertificates)); - Assert.False(CryptoUtils.ValidateTrustChain(trustPath, trustPath)); - Assert.False(CryptoUtils.ValidateTrustChain(attestationRootCertificates, attestationRootCertificates)); + Assert.True(CryptoUtils.ValidateTrustChain(trustPath, trustPath)); + Assert.True(CryptoUtils.ValidateTrustChain(attestationRootCertificates, attestationRootCertificates)); Assert.False(CryptoUtils.ValidateTrustChain(attestationRootCertificates, trustPath)); } From 13d2a3cc15166b6858fac492fbe4c582e5e5c427 Mon Sep 17 00:00:00 2001 From: googyi Date: Thu, 21 Dec 2023 12:45:21 +0100 Subject: [PATCH 4/8] 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. --- .../AuthenticationExtensionsClientInputs.cs | 9 +---- Src/Fido2/AuthenticatorAssertionResponse.cs | 22 +++++------ Src/Fido2/AuthenticatorAttestationResponse.cs | 6 ++- Src/Fido2/AuthenticatorResponse.cs | 14 ++++++- Src/Fido2/Fido2.cs | 5 ++- Src/Fido2/IFido2.cs | 2 + Src/Fido2/TokenBindingDto.cs | 37 +++++++++++++++++++ Test/Fido2Tests.cs | 30 +++++++-------- 8 files changed, 89 insertions(+), 36 deletions(-) create mode 100644 Src/Fido2/TokenBindingDto.cs diff --git a/Src/Fido2.Models/Objects/AuthenticationExtensionsClientInputs.cs b/Src/Fido2.Models/Objects/AuthenticationExtensionsClientInputs.cs index b2742749..61ce251f 100644 --- a/Src/Fido2.Models/Objects/AuthenticationExtensionsClientInputs.cs +++ b/Src/Fido2.Models/Objects/AuthenticationExtensionsClientInputs.cs @@ -19,13 +19,8 @@ public sealed class AuthenticationExtensionsClientInputs /// https://www.w3.org/TR/webauthn/#sctn-appid-extension /// [JsonPropertyName("appid")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string AppID { private get; set; } - - public string GetAppID() - { - return AppID; - } + [JsonIgnore(Condition = JsonIgnoreCondition.Always)] + public string AppID { get; set; } /// /// This extension enables the WebAuthn Relying Party to determine which extensions the authenticator supports. diff --git a/Src/Fido2/AuthenticatorAssertionResponse.cs b/Src/Fido2/AuthenticatorAssertionResponse.cs index 4d363214..2c08b362 100644 --- a/Src/Fido2/AuthenticatorAssertionResponse.cs +++ b/Src/Fido2/AuthenticatorAssertionResponse.cs @@ -64,9 +64,10 @@ public async Task 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); @@ -118,23 +119,22 @@ public async Task 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.Extensions?.AppID ?? false ? options.Extensions?.GetAppID() : options.RpId; + var rpid = Raw.Extensions?.AppID ?? false ? options.Extensions?.AppID : options.RpId; byte[] hashedRpId = SHA256.HashData(Encoding.UTF8.GetBytes(rpid ?? string.Empty)); byte[] hash = SHA256.HashData(Raw.Response.ClientDataJson); + bool conformanceTesting = metadataService != null && metadataService.ConformanceTesting(); if (!authData.RpIdHash.SequenceEqual(hashedRpId)) throw new Fido2VerificationException(Fido2ErrorCode.InvalidRpidHash, Fido2ErrorMessages.InvalidRpidHash); - if (options.UserVerification is UserVerificationRequirement.Required) - { - // 14. Verify that the UP bit of the flags in authData is set. - if (!authData.UserPresent) - throw new Fido2VerificationException(Fido2ErrorCode.UserPresentFlagNotSet, Fido2ErrorMessages.UserPresentFlagNotSet); + // 14. Verify that the UP bit of the flags in authData is set. + if (!authData.UserPresent && (!conformanceTesting || options.UserVerification is UserVerificationRequirement.Required)) + 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. - if (!authData.UserVerified) - throw new Fido2VerificationException(Fido2ErrorCode.UserVerificationRequirementNotMet, Fido2ErrorMessages.UserVerificationRequirementNotMet); - } + // 15. If the Relying Party requires user verification for this assertion, verify that the UV bit of the flags in authData is set. + if (options.UserVerification is UserVerificationRequirement.Required && !authData.UserVerified) + throw new Fido2VerificationException(Fido2ErrorCode.UserVerificationRequirementNotMet, Fido2ErrorMessages.UserVerificationRequirementNotMet); + // 16. If the credential backup state is used as part of Relying Party business logic or policy, let currentBe and currentBs be the values of the BE and BS bits, respectively, of the flags in authData. // Compare currentBe and currentBs with credentialRecord.BE and credentialRecord.BS and apply Relying Party policy, if any. diff --git a/Src/Fido2/AuthenticatorAttestationResponse.cs b/Src/Fido2/AuthenticatorAttestationResponse.cs index 6f693e7c..6f408fd3 100644 --- a/Src/Fido2/AuthenticatorAttestationResponse.cs +++ b/Src/Fido2/AuthenticatorAttestationResponse.cs @@ -60,6 +60,7 @@ public async Task VerifyAsync( Fido2Configuration config, IsCredentialIdUniqueToUserAsyncDelegate isCredentialIdUniqueToUser, IMetadataService? metadataService, + byte[]? requestTokenBindingId, CancellationToken cancellationToken = default) { // https://www.w3.org/TR/webauthn/#registering-a-new-credential @@ -74,7 +75,10 @@ public async Task 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); diff --git a/Src/Fido2/AuthenticatorResponse.cs b/Src/Fido2/AuthenticatorResponse.cs index 22c31635..ff3958fa 100644 --- a/Src/Fido2/AuthenticatorResponse.cs +++ b/Src/Fido2/AuthenticatorResponse.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using System.Text.Json; using System.Text.Json.Serialization; @@ -48,6 +49,7 @@ protected AuthenticatorResponse(ReadOnlySpan utf8EncodedJson) Type = response.Type; Challenge = response.Challenge; Origin = response.Origin; + TokenBinding = response.TokenBinding; } public const int MAX_ORIGINS_TO_PRINT = 5; @@ -62,7 +64,10 @@ protected AuthenticatorResponse(ReadOnlySpan utf8EncodedJson) [JsonPropertyName("origin")] public string Origin { get; } - protected void BaseVerify(IReadOnlySet fullyQualifiedExpectedOrigins, ReadOnlySpan originalChallenge) + [JsonPropertyName("tokenBinding")] + public TokenBindingDto? TokenBinding { get; set; } + + protected void BaseVerify(IReadOnlySet fullyQualifiedExpectedOrigins, ReadOnlySpan 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}'"); @@ -79,6 +84,13 @@ protected void BaseVerify(IReadOnlySet 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. + if (TokenBinding != null) + { + TokenBinding.Verify(requestTokenBindingId); + } } /* diff --git a/Src/Fido2/Fido2.cs b/Src/Fido2/Fido2.cs index c8e1585b..fd4b285c 100644 --- a/Src/Fido2/Fido2.cs +++ b/Src/Fido2/Fido2.cs @@ -66,10 +66,11 @@ public async Task MakeNewCredentialAsync( AuthenticatorAttestationRawResponse attestationResponse, CredentialCreateOptions origChallenge, IsCredentialIdUniqueToUserAsyncDelegate isCredentialIdUniqueToUser, + byte[]? requestTokenBindingId = null, CancellationToken cancellationToken = default) { var parsedResponse = AuthenticatorAttestationResponse.Parse(attestationResponse); - var success = await parsedResponse.VerifyAsync(origChallenge, _config, isCredentialIdUniqueToUser, _metadataService, cancellationToken); + var success = await parsedResponse.VerifyAsync(origChallenge, _config, isCredentialIdUniqueToUser, _metadataService, requestTokenBindingId, cancellationToken); // todo: Set Errormessage etc. return new CredentialMakeResult( @@ -104,6 +105,7 @@ public async Task MakeAssertionAsync( List storedDevicePublicKeys, uint storedSignatureCounter, IsUserHandleOwnerOfCredentialIdAsync isUserHandleOwnerOfCredentialIdCallback, + byte[]? requestTokenBindingId = null, CancellationToken cancellationToken = default) { var parsedResponse = AuthenticatorAssertionResponse.Parse(assertionResponse); @@ -115,6 +117,7 @@ public async Task MakeAssertionAsync( storedSignatureCounter, isUserHandleOwnerOfCredentialIdCallback, _metadataService, + requestTokenBindingId, cancellationToken); return result; diff --git a/Src/Fido2/IFido2.cs b/Src/Fido2/IFido2.cs index d49233cb..9133fc52 100644 --- a/Src/Fido2/IFido2.cs +++ b/Src/Fido2/IFido2.cs @@ -20,12 +20,14 @@ Task MakeAssertionAsync( List storedDevicePublicKeys, uint storedSignatureCounter, IsUserHandleOwnerOfCredentialIdAsync isUserHandleOwnerOfCredentialIdCallback, + byte[]? requestTokenBindingId = null, CancellationToken cancellationToken = default); Task MakeNewCredentialAsync( AuthenticatorAttestationRawResponse attestationResponse, CredentialCreateOptions origChallenge, IsCredentialIdUniqueToUserAsyncDelegate isCredentialIdUniqueToUser, + byte[]? requestTokenBindingId = null, CancellationToken cancellationToken = default); CredentialCreateOptions RequestNewCredential( diff --git a/Src/Fido2/TokenBindingDto.cs b/Src/Fido2/TokenBindingDto.cs new file mode 100644 index 00000000..e9e60083 --- /dev/null +++ b/Src/Fido2/TokenBindingDto.cs @@ -0,0 +1,37 @@ +namespace Fido2NetLib +{ + public class TokenBindingDto + { + /// + /// 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 + /// + public string? Status { get; set; } + + /// + /// 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. + /// + 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"); + } + } + } +} diff --git a/Test/Fido2Tests.cs b/Test/Fido2Tests.cs index 78a4817d..9946dbc4 100644 --- a/Test/Fido2Tests.cs +++ b/Test/Fido2Tests.cs @@ -413,7 +413,7 @@ public async Task TestFido2AssertionAsync() var response = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationNoneResponse.json")); var o = AuthenticatorAttestationResponse.Parse(response); - await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), _metadataService, CancellationToken.None); + await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), _metadataService, null, CancellationToken.None); var credId = "F1-3C-7F-08-3C-A2-29-E0-B4-03-E8-87-34-6E-FC-7F-98-53-10-3A-30-91-75-67-39-7A-D1-D8-AF-87-04-61-87-EF-95-31-85-60-F3-5A-1A-2A-CF-7D-B0-1D-06-B9-69-F9-AB-F4-EC-F3-07-3E-CF-0F-71-E8-84-E8-41-20"; var allowedCreds = new List() { @@ -485,7 +485,7 @@ public async Task TestParsingAsync() Assert.NotNull(jsonPost); var o = AuthenticatorAttestationResponse.Parse(jsonPost); - await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), _metadataService, CancellationToken.None); + await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), _metadataService, null, CancellationToken.None); } [Fact] @@ -536,7 +536,7 @@ public async Task TestU2FAttestationAsync() var jsonPost = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationResultsU2F.json")); var options = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationOptionsU2F.json")); var o = AuthenticatorAttestationResponse.Parse(jsonPost); - await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), _metadataService, CancellationToken.None); + await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), _metadataService, null, CancellationToken.None); } [Fact] @@ -546,7 +546,7 @@ public async Task TestPackedAttestationAsync() var options = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationOptionsPacked.json")); var o = AuthenticatorAttestationResponse.Parse(jsonPost); options.PubKeyCredParams.Add(new PubKeyCredParam(COSE.Algorithm.RS1, PublicKeyCredentialType.PublicKey)); - await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), _metadataService, CancellationToken.None); + await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), _metadataService, null, CancellationToken.None); var authData = o.AttestationObject.AuthData; var acdBytes = authData.AttestedCredentialData.ToByteArray(); var acd = AttestedCredentialData.Parse(acdBytes); @@ -560,7 +560,7 @@ public async Task TestNoneAttestationAsync() var options = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationOptionsNone.json")); var o = AuthenticatorAttestationResponse.Parse(jsonPost); - await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), _metadataService, CancellationToken.None); + await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), _metadataService, null, CancellationToken.None); } [Fact] @@ -569,7 +569,7 @@ public async Task TestTPMSHA256AttestationAsync() var jsonPost = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationTPMSHA256Response.json")); var options = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationTPMSHA256Options.json")); var o = AuthenticatorAttestationResponse.Parse(jsonPost); - await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), _metadataService, CancellationToken.None); + await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), _metadataService, null, CancellationToken.None); } [Fact] @@ -579,7 +579,7 @@ public async Task TestTPMSHA1AttestationAsync() var options = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationTPMSHA1Options.json")); var o = AuthenticatorAttestationResponse.Parse(jsonPost); options.PubKeyCredParams.Add(new PubKeyCredParam(COSE.Algorithm.RS1, PublicKeyCredentialType.PublicKey)); - await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), _metadataService, CancellationToken.None); + await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), _metadataService, null, CancellationToken.None); } [Fact] @@ -588,7 +588,7 @@ public async Task TestAndroidKeyAttestationAsync() var jsonPost = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationAndroidKeyResponse.json")); var options = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationAndroidKeyOptions.json")); var o = AuthenticatorAttestationResponse.Parse(jsonPost); - await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), _metadataService, CancellationToken.None); + await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), _metadataService, null, CancellationToken.None); } [Fact] @@ -597,7 +597,7 @@ public async Task TaskPackedAttestation512() var jsonPost = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationResultsPacked512.json")); var options = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationOptionsPacked512.json")); var o = AuthenticatorAttestationResponse.Parse(jsonPost); - await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), _metadataService, CancellationToken.None); + await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), _metadataService, null, CancellationToken.None); } [Fact] @@ -606,7 +606,7 @@ public async Task TestTrustKeyAttestationAsync() var jsonPost = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationResultTrustKeyT110.json")); var options = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationOptionsTrustKeyT110.json")); var o = AuthenticatorAttestationResponse.Parse(jsonPost); - await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), _metadataService, CancellationToken.None); + await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), _metadataService, null, CancellationToken.None); var authData = o.AttestationObject.AuthData; var acdBytes = authData.AttestedCredentialData.ToByteArray(); var acd = AttestedCredentialData.Parse(acdBytes); @@ -623,7 +623,7 @@ public async Task TestInvalidU2FAttestationAsync() var jsonPost = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationResultsATKey.json")); var options = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationOptionsATKey.json")); var o = AuthenticatorAttestationResponse.Parse(jsonPost); - await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), _metadataService, CancellationToken.None); + await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), _metadataService, null, CancellationToken.None); var authData = o.AttestationObject.AuthData; var acdBytes = authData.AttestedCredentialData.ToByteArray(); var acd = AttestedCredentialData.Parse(acdBytes); @@ -648,7 +648,7 @@ public async Task TestMdsStatusReportsSuccessAsync() mockMetadataService.Setup(m => m.ConformanceTesting()).Returns(false); var o = AuthenticatorAttestationResponse.Parse(response); - await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), mockMetadataService.Object, CancellationToken.None); + await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), mockMetadataService.Object, null, CancellationToken.None); } [Fact] @@ -671,7 +671,7 @@ public async Task TestMdsStatusReportsUndesiredAsync() var o = AuthenticatorAttestationResponse.Parse(response); await Assert.ThrowsAsync(() => - o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), mockMetadataService.Object, CancellationToken.None)); + o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), mockMetadataService.Object, null, CancellationToken.None)); } [Fact] @@ -694,7 +694,7 @@ public async Task TestMdsStatusReportsUndesiredFixedAsync() mockMetadataService.Setup(m => m.ConformanceTesting()).Returns(false); var o = AuthenticatorAttestationResponse.Parse(response); - await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), mockMetadataService.Object, CancellationToken.None); + await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), mockMetadataService.Object, null, CancellationToken.None); } [Fact] @@ -708,7 +708,7 @@ public async Task TestMdsStatusReportsNullAsync() mockMetadataService.Setup(m => m.ConformanceTesting()).Returns(false); var o = AuthenticatorAttestationResponse.Parse(response); - await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), mockMetadataService.Object, CancellationToken.None); + await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), mockMetadataService.Object, null, CancellationToken.None); } //public void TestHasCorrentAAguid() From 343282006e03449b4710400c75003339af2a2fb2 Mon Sep 17 00:00:00 2001 From: googyi Date: Thu, 21 Dec 2023 15:03:12 +0100 Subject: [PATCH 5/8] unit test fix (tokenbinding dto parsing) --- Demo/TestController.cs | 1 - Src/Fido2/TokenBindingDto.cs | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Demo/TestController.cs b/Demo/TestController.cs index bc361bec..2d5e64f6 100644 --- a/Demo/TestController.cs +++ b/Demo/TestController.cs @@ -1,7 +1,6 @@ using System; using System.Linq; using System.Text; -using System.Text.Json; using System.Threading; using System.Threading.Tasks; diff --git a/Src/Fido2/TokenBindingDto.cs b/Src/Fido2/TokenBindingDto.cs index e9e60083..cde63057 100644 --- a/Src/Fido2/TokenBindingDto.cs +++ b/Src/Fido2/TokenBindingDto.cs @@ -1,4 +1,6 @@ -namespace Fido2NetLib +using System.Text.Json.Serialization; + +namespace Fido2NetLib { public class TokenBindingDto { @@ -7,11 +9,13 @@ public class TokenBindingDto /// 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 /// + [JsonPropertyName("status")] public string? Status { get; set; } /// /// 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. /// + [JsonPropertyName("id")] public string? Id { get; set; } public void Verify(byte[]? requestTokenbinding) From c55c79995803a68f3b694b4a11c7c559601f736b Mon Sep 17 00:00:00 2001 From: googyi Date: Thu, 21 Dec 2023 17:02:35 +0100 Subject: [PATCH 6/8] fix azure pipeline fix azure pipeline's whitespace error + removing unused using --- Src/Fido2/AuthenticatorResponse.cs | 1 - Src/Fido2/TrustAnchor.cs | 126 ++++++++++++++--------------- 2 files changed, 63 insertions(+), 64 deletions(-) diff --git a/Src/Fido2/AuthenticatorResponse.cs b/Src/Fido2/AuthenticatorResponse.cs index ff3958fa..898c8b69 100644 --- a/Src/Fido2/AuthenticatorResponse.cs +++ b/Src/Fido2/AuthenticatorResponse.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/Src/Fido2/TrustAnchor.cs b/Src/Fido2/TrustAnchor.cs index 708074c0..d7dd7efd 100644 --- a/Src/Fido2/TrustAnchor.cs +++ b/Src/Fido2/TrustAnchor.cs @@ -1,65 +1,65 @@ -using System; -using System.Linq; -using System.Security.Cryptography.X509Certificates; - -using Fido2NetLib.Exceptions; - -namespace Fido2NetLib; - -public static class TrustAnchor -{ - public static void Verify(MetadataBLOBPayloadEntry? metadataEntry, X509Certificate2[] trustPath, bool conformance) - { - if (trustPath != null && metadataEntry?.MetadataStatement?.AttestationTypes is not null) - { - static bool ContainsAttestationType(MetadataBLOBPayloadEntry entry, MetadataAttestationType type) - { - return entry.MetadataStatement.AttestationTypes.Contains(type.ToEnumMemberValue()); - } - - // If the authenticator's metadata requires basic full attestation, build and verify the chain - if (ContainsAttestationType(metadataEntry, MetadataAttestationType.ATTESTATION_BASIC_FULL) || - ContainsAttestationType(metadataEntry, MetadataAttestationType.ATTESTATION_PRIVACY_CA)) - { - string[] certStrings = metadataEntry.MetadataStatement.AttestationRootCertificates; - var attestationRootCertificates = new X509Certificate2[certStrings.Length]; - - for (int i = 0; i < attestationRootCertificates.Length; i++) - { - attestationRootCertificates[i] = new X509Certificate2(Convert.FromBase64String(certStrings[i])); - } - - if (trustPath.Length > 1 && attestationRootCertificates.Any(c => string.Equals(c.Thumbprint, trustPath[^1].Thumbprint, StringComparison.Ordinal))) +using System; +using System.Linq; +using System.Security.Cryptography.X509Certificates; + +using Fido2NetLib.Exceptions; + +namespace Fido2NetLib; + +public static class TrustAnchor +{ + public static void Verify(MetadataBLOBPayloadEntry? metadataEntry, X509Certificate2[] trustPath, bool conformance) + { + if (trustPath != null && metadataEntry?.MetadataStatement?.AttestationTypes is not null) + { + static bool ContainsAttestationType(MetadataBLOBPayloadEntry entry, MetadataAttestationType type) + { + return entry.MetadataStatement.AttestationTypes.Contains(type.ToEnumMemberValue()); + } + + // If the authenticator's metadata requires basic full attestation, build and verify the chain + if (ContainsAttestationType(metadataEntry, MetadataAttestationType.ATTESTATION_BASIC_FULL) || + ContainsAttestationType(metadataEntry, MetadataAttestationType.ATTESTATION_PRIVACY_CA)) + { + string[] certStrings = metadataEntry.MetadataStatement.AttestationRootCertificates; + var attestationRootCertificates = new X509Certificate2[certStrings.Length]; + + for (int i = 0; i < attestationRootCertificates.Length; i++) + { + attestationRootCertificates[i] = new X509Certificate2(Convert.FromBase64String(certStrings[i])); + } + + if (trustPath.Length > 1 && attestationRootCertificates.Any(c => string.Equals(c.Thumbprint, trustPath[^1].Thumbprint, StringComparison.Ordinal))) + { + throw new Fido2VerificationException(Fido2ErrorMessages.InvalidCertificateChain); + } + + if (!CryptoUtils.ValidateTrustChain(trustPath, attestationRootCertificates, conformance)) { throw new Fido2VerificationException(Fido2ErrorMessages.InvalidCertificateChain); - } - - if (!CryptoUtils.ValidateTrustChain(trustPath, attestationRootCertificates, conformance)) - { - throw new Fido2VerificationException(Fido2ErrorMessages.InvalidCertificateChain); - } - } - - else if (ContainsAttestationType(metadataEntry, MetadataAttestationType.ATTESTATION_ANONCA)) - { - // skip verification for Anonymization CA (AnonCA) - } - else // otherwise, ensure the certificate is self signed - { - var trustPath0 = trustPath[0]; - - if (!string.Equals(trustPath0.Subject, trustPath0.Issuer, StringComparison.Ordinal)) - { - // TODO: Improve this error message - throw new Fido2VerificationException("Attestation with full attestation from authenticator that does not support full attestation"); - } - } - - // TODO: Verify all MetadataAttestationTypes are correctly handled - - // [ ] ATTESTATION_ECDAA "ecdaa" | currently handled as self signed w/ no test coverage - // [ ] ATTESTATION_ANONCA "anonca" | currently not verified w/ no test coverage - // [ ] ATTESTATION_NONE "none" | currently handled as self signed w/ no test coverage - } - } -} + } + } + + else if (ContainsAttestationType(metadataEntry, MetadataAttestationType.ATTESTATION_ANONCA)) + { + // skip verification for Anonymization CA (AnonCA) + } + else // otherwise, ensure the certificate is self signed + { + var trustPath0 = trustPath[0]; + + if (!string.Equals(trustPath0.Subject, trustPath0.Issuer, StringComparison.Ordinal)) + { + // TODO: Improve this error message + throw new Fido2VerificationException("Attestation with full attestation from authenticator that does not support full attestation"); + } + } + + // TODO: Verify all MetadataAttestationTypes are correctly handled + + // [ ] ATTESTATION_ECDAA "ecdaa" | currently handled as self signed w/ no test coverage + // [ ] ATTESTATION_ANONCA "anonca" | currently not verified w/ no test coverage + // [ ] ATTESTATION_NONE "none" | currently handled as self signed w/ no test coverage + } + } +} From 7534243ead68a391632ba4f63df11a0558fee4c7 Mon Sep 17 00:00:00 2001 From: googyi Date: Fri, 5 Jan 2024 17:26:18 +0100 Subject: [PATCH 7/8] Improve trustanchor test coverage Improve trustanchor test coverage based on codecov report --- Test/Fido2Tests.cs | 63 ++++++++- Test/Test.csproj | 1 + .../256K1 U2F Authenticator anonca.json | 68 ++++++++++ .../256K1 U2F Authenticator basic_full.json | 68 ++++++++++ ...6K1 U2F Authenticator basic_surrogate.json | 68 ++++++++++ .../Secp256R1 Packed Authenticator.json | 128 ++++++++++++++++++ Test/TestMetadataService.cs | 21 +++ 7 files changed, 416 insertions(+), 1 deletion(-) create mode 100644 Test/TestFiles/metadata/256K1 U2F Authenticator anonca.json create mode 100644 Test/TestFiles/metadata/256K1 U2F Authenticator basic_full.json create mode 100644 Test/TestFiles/metadata/256K1 U2F Authenticator basic_surrogate.json create mode 100644 Test/TestFiles/metadata/Secp256R1 Packed Authenticator.json create mode 100644 Test/TestMetadataService.cs diff --git a/Test/Fido2Tests.cs b/Test/Fido2Tests.cs index 7ee53c36..6f5424b6 100644 --- a/Test/Fido2Tests.cs +++ b/Test/Fido2Tests.cs @@ -16,7 +16,7 @@ using Moq; using NSec.Cryptography; - +using Test; using static Fido2NetLib.AuthenticatorAttestationResponse; namespace fido2_net_lib.Test; @@ -76,6 +76,17 @@ static Fido2Tests() }; } + private TestMetadataService CreateMetadataService(string cacheDir) + { + var repos = new List + { + new FileSystemMetadataRepository(cacheDir) + }; + var simpleService = new TestMetadataService(repos); + simpleService.InitializeAsync().Wait(); + return simpleService; + } + private async Task GetAsync(string filename) { return JsonSerializer.Deserialize(await File.ReadAllTextAsync(filename)); @@ -539,6 +550,56 @@ public async Task TestU2FAttestationAsync() await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), _metadataService, null, CancellationToken.None); } + [Fact] + public async Task TestPackedttestationAsyncFailTrustAnchorOnRootCertInTrustPath() + { + var targetGuid = new Guid("42383245-4437-3343-3846-423445354132"); + var metadataService = CreateMetadataService("./metadata"); + metadataService.ChangeEntryGuid(new Guid("00000000-0000-0000-0000-000000000004"), targetGuid); + var jsonPost = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationResultsPacked.json")); + var options = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationOptionsPacked.json")); + var o = AuthenticatorAttestationResponse.Parse(jsonPost); + CborArray X5c = o.AttestationObject.AttStmt["x5c"] as CborArray; + var entry = await metadataService.GetEntryAsync(targetGuid); + foreach (var attRootCert in entry.MetadataStatement.AttestationRootCertificates) + X5c.Add(Encoding.UTF8.GetBytes(attRootCert)); + + await Assert.ThrowsAsync(() => o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), metadataService, null, CancellationToken.None)); + } + + [Fact] + public async Task TestU2FAttestationAsyncFailTrustAnchorBasicFull() + { + var metadataService = CreateMetadataService("./metadata"); + metadataService.ChangeEntryGuid(new Guid("00000000-0000-0000-0000-000000000001"), new Guid("00000000-0000-0000-0000-000000000000")); + var jsonPost = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationResultsU2F.json")); + var options = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationOptionsU2F.json")); + var o = AuthenticatorAttestationResponse.Parse(jsonPost); + await Assert.ThrowsAsync(() => o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), metadataService, null, CancellationToken.None)); + } + + [Fact] + public async Task TestU2FAttestationAsyncCantFailTrustAnchorAnonca() + { + var metadataService = CreateMetadataService("./metadata"); + metadataService.ChangeEntryGuid(new Guid("00000000-0000-0000-0000-000000000002"), new Guid("00000000-0000-0000-0000-000000000000")); + var jsonPost = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationResultsU2F.json")); + var options = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationOptionsU2F.json")); + var o = AuthenticatorAttestationResponse.Parse(jsonPost); + await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), metadataService, null, CancellationToken.None); + } + + [Fact] + public async Task TestU2FAttestationAsyncFailTrustAnchorBasicSurrogate() + { + var metadataService = CreateMetadataService("./metadata"); + metadataService.ChangeEntryGuid(new Guid("00000000-0000-0000-0000-000000000003"), new Guid("00000000-0000-0000-0000-000000000000")); + var jsonPost = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationResultsU2F.json")); + var options = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationOptionsU2F.json")); + var o = AuthenticatorAttestationResponse.Parse(jsonPost); + await Assert.ThrowsAsync(() => o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), metadataService, null, CancellationToken.None)); + } + [Fact] public async Task TestPackedAttestationAsync() { diff --git a/Test/Test.csproj b/Test/Test.csproj index 32cd7380..05f46c57 100644 --- a/Test/Test.csproj +++ b/Test/Test.csproj @@ -18,6 +18,7 @@ + diff --git a/Test/TestFiles/metadata/256K1 U2F Authenticator anonca.json b/Test/TestFiles/metadata/256K1 U2F Authenticator anonca.json new file mode 100644 index 00000000..e844de94 --- /dev/null +++ b/Test/TestFiles/metadata/256K1 U2F Authenticator anonca.json @@ -0,0 +1,68 @@ +{ + "legalHeader": "https://fidoalliance.org/metadata/metadata-statement-legal-header/", + "description": "Virtual Secp256K1 U2F authenticator", + "aaguid": "00000000-0000-0000-0000-000000000002", + "alternativeDescriptions": { + "en-GB": "Virtual Secp256K1 U2F authenticator" + }, + "attestationCertificateKeyIdentifiers": [ + "564df7c0f8c655b6a11f6c4d19f3bf41e2fd0179" + ], + "protocolFamily": "u2f", + "schema": 3, + "authenticatorVersion": 2, + "upv": [ + { + "major": 1, + "minor": 0 + }, + { + "major": 1, + "minor": 1 + }, + { + "major": 1, + "minor": 2 + } + ], + "authenticationAlgorithms": [ + "secp256r1_ecdsa_sha256_raw" + ], + "publicKeyAlgAndEncodings": [ + "ecc_x962_raw" + ], + "attestationTypes": [ + "anonca" + ], + "userVerificationDetails": [ + [ + { + "userVerificationMethod": "none" + } + ], + [ + { + "userVerificationMethod": "presence_internal" + } + ] + ], + "keyProtection": [ + "hardware", + "secure_element" + ], + "matcherProtection": [ + "on_chip" + ], + "cryptoStrength": 128, + "attachmentHint": [ + "external", + "wired", + "nfc", + "wireless" + ], + "tcDisplay": [], + "attestationRootCertificates": [ + "MIIFwDCCA6gCCQCNm1u56oRwXTANBgkqhkiG9w0BAQsFADCBoTEYMBYGA1UEAwwPRklETzIgVEVTVCBST09UMTEwLwYJKoZIhvcNAQkBFiJjb25mb3JtYW5jZS10b29sc0BmaWRvYWxsaWFuY2Uub3JnMRYwFAYDVQQKDA1GSURPIEFsbGlhbmNlMQwwCgYDVQQLDANDV0cxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJNWTESMBAGA1UEBwwJV2FrZWZpZWxkMB4XDTE4MDMxNjE0MzUyN1oXDTQ1MDgwMTE0MzUyN1owgaExGDAWBgNVBAMMD0ZJRE8yIFRFU1QgUk9PVDExMC8GCSqGSIb3DQEJARYiY29uZm9ybWFuY2UtdG9vbHNAZmlkb2FsbGlhbmNlLm9yZzEWMBQGA1UECgwNRklETyBBbGxpYW5jZTEMMAoGA1UECwwDQ1dHMQswCQYDVQQGEwJVUzELMAkGA1UECAwCTVkxEjAQBgNVBAcMCVdha2VmaWVsZDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL11U5yAIVLMrL3xS8u8ysMSdOkDeoTO+RcAy+uXXp6k4SC+jOy37gICEtYI+MKQV1EMeMMf3rM1ueZAO3iPFa0NEdi/oQ7npnGjBNI8wMzD8FfNe6rWtzkDaHpsZW///MwWDpGyJR+Xyjcq6U4vS9bS6zZ7jslw0Oczx4UsYgOsIUXSSBaGOrRbxJ/JC5gnDYEYvtNM+PDPczLNKAyhdvBZWNWHr7MZ0P5TeJQcXsAoShRX2Y8U8fRNJm7SeiFKDP0Nn/QKxOSt7zGP4xt9nMasE1q2ZTdar2+W13CRz37RI0ZWpq/+YquoEbZ7Uj7NmBTcqhb260nmDER2FpwwYwPSark92IZbamozB8d7OEI1jJgsrjJhKan0EmRaWVBpHT4xYKdEu7r09S0JhKyU+52WDmmVQTMpYLrm4Xl7hRxyPyBYkalrozsGmPs8vlhNq3VsVbyBSMSpEmUaeAa7LLE9/Vh0agJLVFHh1ehYKJpzHnmmBXUqx0Fz3afmDm1NX0sr3O/6xIx1VSTViT3KNxBYpVH1qjHATLzuxcWmm+75fcJMiPYPSMXVmRb3Q1l91AM4BBeWhlP3Fbc7gDy0r+s7m0sGS6PT2J2rGog2rUxnJ+zCM11M7DeO0XM2nny4uRYPPk9w2EXzfvtdvieYU/5RB4RDm5TGxHhGXVZUgac5AgMBAAEwDQYJKoZIhvcNAQELBQADggIBAFt2XGd3k5GpbO1EUm3u60zT1fE6u6pOscp156k5VnsHgaHRHdIAPNLeLNmR7y5OnrXbh13CrGwU1q84jjJXpv+v14xUCc5i01yopFTQFLr4A7NHp2nNYfNhhIVSFAgW43EflJflbLEelCJzxLlWb5BoDsZeeNmEQsXIM1mJ26R3r0dzsHBb0uy+8LNR1gdVqdjhC8BLy3gh4+BWuidyZNt07LveDsSFW5rcj5wRrSx9hXPIyVpjQSljNvY7MVTouqJzNAAQMsTKkXPkTXldCop9Qo9UPkHRRm0l7LLtdaOoXrct0Ymocf8zxf9bFNiw9f4WRYQM6sMhzt8+s/oDilo4QhcUgeJEiEPESi6ynYTV62SHA4eMunUJ5dlCaRnFiR9DTImFa5IRzie326/nW/SPCaKc/yrFIihMMjJoSAPhpTb/K6yHOUG8r+KiQut7NzqGV301pQ9u62dGL5Oi1VXmCFlE2ramZs15BNOUyAo2CBbRJg3jKcdu/8QC6ojjDvQ863+7LPtn74wJC5RpUJsS0GhQWgq5pAXO3wA61Uobxi6MkOpCC0zBWx/d4CqpS4j4hFgxWBTXX48ihPu+hIxIF/AxbqtPvqLMExW/xZITn6ArpWyQ9e4SUVr3n3F33ap1XdDyZ0vwFcm18JQAtsvXT6qCLrWOXnHUgfn/+Viu" + ], + "icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAACXBIWXMAAC4jAAAuIwF4pT92AAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAthJREFUeNrslt9Lk1EYx7/vNte0vXOk7yS7qyWBYvnjIktGU0vDCwktV4KXpv3wB/4BBiIa/QC1wjkVUxNsUuuuzd1k6iBLCxIFzcDXOTZwY8r2sr1rp4uXZuoggryJfS8eeL6c53w45+E5HIoQgoOUCAesGCAGiAEAyX6LZdn19XWGYdRq9T8gkN1qa20VDlVZcZUQYpuZKS0tHTca9ywz6Hurq6s/zs6SP2kXwGI2AzjKqHQ63ft3k4SQpoYGAMWFRXvKLmoLAAwODPwdoLdHD2BkaOh3843J5HK59pTV1dwE8Gp8fP+OS4tL5rfmH6GQkO70oLuzc2jwuSop2dBrOCynk5KO9PX3Z2ZkMCkpqyvfGIYBcL+9w2qdKCoqCgQCAHieF2ofP3xkMr1W0IraulptQYHP7wNF7e2BNl8DIO34CQANd+u7u7oASEABqKupJYRU6a4DoGXxqaoUpZwWA9aJCUJI4QUtgFPqkwnSQwD69ProVxQMBtvb2iiKetDRwfN8KBTiOO7Zk6cA+noNLMsCyMo8zfn9HMflnMkCsLS4OD01DUB39RohxOl0yhMS4iiR3W6PbLszB3FxcbRCQQhRJCZKJBKxWCyTyeRyGoBUKv0y/xmATlcpi4+XyWQajQaAz+ebmpwEUF5RDkClUhVqC3gSnp+biz4HnN8PwO/3R5xAgMvNzk5mkkWUCMDq6nfBdzg2BDCtUABwOl2/fIdAig4IBoORKIjneQVNb3m3ii+XiEHp+wzpGelut/ul0QggEAiUXSm7def2vZaWtLS0hYWvH+Y+5Z/Ny8nNjf5USCSSSIw44XDY4dhQKpXDw8NiiqpvbBwdeVF1owoAu7aWmnrM0KPf3t6+VFLc1Nx8Pu/c6NiYSCSKPsket2d5ednj8UQcr9drX7e73ZtCyrJrVqs1HA4TQpZXVrxer+C7N90Wi8Vms+0fCyr2q4gBYoD/APBzAI6VNqGQPUqnAAAAAElFTkSuQmCC" +} \ No newline at end of file diff --git a/Test/TestFiles/metadata/256K1 U2F Authenticator basic_full.json b/Test/TestFiles/metadata/256K1 U2F Authenticator basic_full.json new file mode 100644 index 00000000..c09f42b7 --- /dev/null +++ b/Test/TestFiles/metadata/256K1 U2F Authenticator basic_full.json @@ -0,0 +1,68 @@ +{ + "legalHeader": "https://fidoalliance.org/metadata/metadata-statement-legal-header/", + "description": "Virtual Secp256K1 U2F authenticator", + "aaguid": "00000000-0000-0000-0000-000000000001", + "alternativeDescriptions": { + "en-GB": "Virtual Secp256K1 U2F authenticator" + }, + "attestationCertificateKeyIdentifiers": [ + "564df7c0f8c655b6a11f6c4d19f3bf41e2fd0179" + ], + "protocolFamily": "u2f", + "schema": 3, + "authenticatorVersion": 2, + "upv": [ + { + "major": 1, + "minor": 0 + }, + { + "major": 1, + "minor": 1 + }, + { + "major": 1, + "minor": 2 + } + ], + "authenticationAlgorithms": [ + "secp256r1_ecdsa_sha256_raw" + ], + "publicKeyAlgAndEncodings": [ + "ecc_x962_raw" + ], + "attestationTypes": [ + "basic_full" + ], + "userVerificationDetails": [ + [ + { + "userVerificationMethod": "none" + } + ], + [ + { + "userVerificationMethod": "presence_internal" + } + ] + ], + "keyProtection": [ + "hardware", + "secure_element" + ], + "matcherProtection": [ + "on_chip" + ], + "cryptoStrength": 128, + "attachmentHint": [ + "external", + "wired", + "nfc", + "wireless" + ], + "tcDisplay": [], + "attestationRootCertificates": [ + "MIIFwDCCA6gCCQCNm1u56oRwXTANBgkqhkiG9w0BAQsFADCBoTEYMBYGA1UEAwwPRklETzIgVEVTVCBST09UMTEwLwYJKoZIhvcNAQkBFiJjb25mb3JtYW5jZS10b29sc0BmaWRvYWxsaWFuY2Uub3JnMRYwFAYDVQQKDA1GSURPIEFsbGlhbmNlMQwwCgYDVQQLDANDV0cxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJNWTESMBAGA1UEBwwJV2FrZWZpZWxkMB4XDTE4MDMxNjE0MzUyN1oXDTQ1MDgwMTE0MzUyN1owgaExGDAWBgNVBAMMD0ZJRE8yIFRFU1QgUk9PVDExMC8GCSqGSIb3DQEJARYiY29uZm9ybWFuY2UtdG9vbHNAZmlkb2FsbGlhbmNlLm9yZzEWMBQGA1UECgwNRklETyBBbGxpYW5jZTEMMAoGA1UECwwDQ1dHMQswCQYDVQQGEwJVUzELMAkGA1UECAwCTVkxEjAQBgNVBAcMCVdha2VmaWVsZDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL11U5yAIVLMrL3xS8u8ysMSdOkDeoTO+RcAy+uXXp6k4SC+jOy37gICEtYI+MKQV1EMeMMf3rM1ueZAO3iPFa0NEdi/oQ7npnGjBNI8wMzD8FfNe6rWtzkDaHpsZW///MwWDpGyJR+Xyjcq6U4vS9bS6zZ7jslw0Oczx4UsYgOsIUXSSBaGOrRbxJ/JC5gnDYEYvtNM+PDPczLNKAyhdvBZWNWHr7MZ0P5TeJQcXsAoShRX2Y8U8fRNJm7SeiFKDP0Nn/QKxOSt7zGP4xt9nMasE1q2ZTdar2+W13CRz37RI0ZWpq/+YquoEbZ7Uj7NmBTcqhb260nmDER2FpwwYwPSark92IZbamozB8d7OEI1jJgsrjJhKan0EmRaWVBpHT4xYKdEu7r09S0JhKyU+52WDmmVQTMpYLrm4Xl7hRxyPyBYkalrozsGmPs8vlhNq3VsVbyBSMSpEmUaeAa7LLE9/Vh0agJLVFHh1ehYKJpzHnmmBXUqx0Fz3afmDm1NX0sr3O/6xIx1VSTViT3KNxBYpVH1qjHATLzuxcWmm+75fcJMiPYPSMXVmRb3Q1l91AM4BBeWhlP3Fbc7gDy0r+s7m0sGS6PT2J2rGog2rUxnJ+zCM11M7DeO0XM2nny4uRYPPk9w2EXzfvtdvieYU/5RB4RDm5TGxHhGXVZUgac5AgMBAAEwDQYJKoZIhvcNAQELBQADggIBAFt2XGd3k5GpbO1EUm3u60zT1fE6u6pOscp156k5VnsHgaHRHdIAPNLeLNmR7y5OnrXbh13CrGwU1q84jjJXpv+v14xUCc5i01yopFTQFLr4A7NHp2nNYfNhhIVSFAgW43EflJflbLEelCJzxLlWb5BoDsZeeNmEQsXIM1mJ26R3r0dzsHBb0uy+8LNR1gdVqdjhC8BLy3gh4+BWuidyZNt07LveDsSFW5rcj5wRrSx9hXPIyVpjQSljNvY7MVTouqJzNAAQMsTKkXPkTXldCop9Qo9UPkHRRm0l7LLtdaOoXrct0Ymocf8zxf9bFNiw9f4WRYQM6sMhzt8+s/oDilo4QhcUgeJEiEPESi6ynYTV62SHA4eMunUJ5dlCaRnFiR9DTImFa5IRzie326/nW/SPCaKc/yrFIihMMjJoSAPhpTb/K6yHOUG8r+KiQut7NzqGV301pQ9u62dGL5Oi1VXmCFlE2ramZs15BNOUyAo2CBbRJg3jKcdu/8QC6ojjDvQ863+7LPtn74wJC5RpUJsS0GhQWgq5pAXO3wA61Uobxi6MkOpCC0zBWx/d4CqpS4j4hFgxWBTXX48ihPu+hIxIF/AxbqtPvqLMExW/xZITn6ArpWyQ9e4SUVr3n3F33ap1XdDyZ0vwFcm18JQAtsvXT6qCLrWOXnHUgfn/+Viu" + ], + "icon": "data:image/png;base64,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" +} \ No newline at end of file diff --git a/Test/TestFiles/metadata/256K1 U2F Authenticator basic_surrogate.json b/Test/TestFiles/metadata/256K1 U2F Authenticator basic_surrogate.json new file mode 100644 index 00000000..c2c55c06 --- /dev/null +++ b/Test/TestFiles/metadata/256K1 U2F Authenticator basic_surrogate.json @@ -0,0 +1,68 @@ +{ + "legalHeader": "https://fidoalliance.org/metadata/metadata-statement-legal-header/", + "description": "Virtual Secp256K1 U2F authenticator", + "aaguid": "00000000-0000-0000-0000-000000000003", + "alternativeDescriptions": { + "en-GB": "Virtual Secp256K1 U2F authenticator" + }, + "attestationCertificateKeyIdentifiers": [ + "564df7c0f8c655b6a11f6c4d19f3bf41e2fd0179" + ], + "protocolFamily": "u2f", + "schema": 3, + "authenticatorVersion": 2, + "upv": [ + { + "major": 1, + "minor": 0 + }, + { + "major": 1, + "minor": 1 + }, + { + "major": 1, + "minor": 2 + } + ], + "authenticationAlgorithms": [ + "secp256r1_ecdsa_sha256_raw" + ], + "publicKeyAlgAndEncodings": [ + "ecc_x962_raw" + ], + "attestationTypes": [ + "basic_surrogate" + ], + "userVerificationDetails": [ + [ + { + "userVerificationMethod": "none" + } + ], + [ + { + "userVerificationMethod": "presence_internal" + } + ] + ], + "keyProtection": [ + "hardware", + "secure_element" + ], + "matcherProtection": [ + "on_chip" + ], + "cryptoStrength": 128, + "attachmentHint": [ + "external", + "wired", + "nfc", + "wireless" + ], + "tcDisplay": [], + "attestationRootCertificates": [ + "MIIFwDCCA6gCCQCNm1u56oRwXTANBgkqhkiG9w0BAQsFADCBoTEYMBYGA1UEAwwPRklETzIgVEVTVCBST09UMTEwLwYJKoZIhvcNAQkBFiJjb25mb3JtYW5jZS10b29sc0BmaWRvYWxsaWFuY2Uub3JnMRYwFAYDVQQKDA1GSURPIEFsbGlhbmNlMQwwCgYDVQQLDANDV0cxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJNWTESMBAGA1UEBwwJV2FrZWZpZWxkMB4XDTE4MDMxNjE0MzUyN1oXDTQ1MDgwMTE0MzUyN1owgaExGDAWBgNVBAMMD0ZJRE8yIFRFU1QgUk9PVDExMC8GCSqGSIb3DQEJARYiY29uZm9ybWFuY2UtdG9vbHNAZmlkb2FsbGlhbmNlLm9yZzEWMBQGA1UECgwNRklETyBBbGxpYW5jZTEMMAoGA1UECwwDQ1dHMQswCQYDVQQGEwJVUzELMAkGA1UECAwCTVkxEjAQBgNVBAcMCVdha2VmaWVsZDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL11U5yAIVLMrL3xS8u8ysMSdOkDeoTO+RcAy+uXXp6k4SC+jOy37gICEtYI+MKQV1EMeMMf3rM1ueZAO3iPFa0NEdi/oQ7npnGjBNI8wMzD8FfNe6rWtzkDaHpsZW///MwWDpGyJR+Xyjcq6U4vS9bS6zZ7jslw0Oczx4UsYgOsIUXSSBaGOrRbxJ/JC5gnDYEYvtNM+PDPczLNKAyhdvBZWNWHr7MZ0P5TeJQcXsAoShRX2Y8U8fRNJm7SeiFKDP0Nn/QKxOSt7zGP4xt9nMasE1q2ZTdar2+W13CRz37RI0ZWpq/+YquoEbZ7Uj7NmBTcqhb260nmDER2FpwwYwPSark92IZbamozB8d7OEI1jJgsrjJhKan0EmRaWVBpHT4xYKdEu7r09S0JhKyU+52WDmmVQTMpYLrm4Xl7hRxyPyBYkalrozsGmPs8vlhNq3VsVbyBSMSpEmUaeAa7LLE9/Vh0agJLVFHh1ehYKJpzHnmmBXUqx0Fz3afmDm1NX0sr3O/6xIx1VSTViT3KNxBYpVH1qjHATLzuxcWmm+75fcJMiPYPSMXVmRb3Q1l91AM4BBeWhlP3Fbc7gDy0r+s7m0sGS6PT2J2rGog2rUxnJ+zCM11M7DeO0XM2nny4uRYPPk9w2EXzfvtdvieYU/5RB4RDm5TGxHhGXVZUgac5AgMBAAEwDQYJKoZIhvcNAQELBQADggIBAFt2XGd3k5GpbO1EUm3u60zT1fE6u6pOscp156k5VnsHgaHRHdIAPNLeLNmR7y5OnrXbh13CrGwU1q84jjJXpv+v14xUCc5i01yopFTQFLr4A7NHp2nNYfNhhIVSFAgW43EflJflbLEelCJzxLlWb5BoDsZeeNmEQsXIM1mJ26R3r0dzsHBb0uy+8LNR1gdVqdjhC8BLy3gh4+BWuidyZNt07LveDsSFW5rcj5wRrSx9hXPIyVpjQSljNvY7MVTouqJzNAAQMsTKkXPkTXldCop9Qo9UPkHRRm0l7LLtdaOoXrct0Ymocf8zxf9bFNiw9f4WRYQM6sMhzt8+s/oDilo4QhcUgeJEiEPESi6ynYTV62SHA4eMunUJ5dlCaRnFiR9DTImFa5IRzie326/nW/SPCaKc/yrFIihMMjJoSAPhpTb/K6yHOUG8r+KiQut7NzqGV301pQ9u62dGL5Oi1VXmCFlE2ramZs15BNOUyAo2CBbRJg3jKcdu/8QC6ojjDvQ863+7LPtn74wJC5RpUJsS0GhQWgq5pAXO3wA61Uobxi6MkOpCC0zBWx/d4CqpS4j4hFgxWBTXX48ihPu+hIxIF/AxbqtPvqLMExW/xZITn6ArpWyQ9e4SUVr3n3F33ap1XdDyZ0vwFcm18JQAtsvXT6qCLrWOXnHUgfn/+Viu" + ], + "icon": "data:image/png;base64,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" +} \ No newline at end of file diff --git a/Test/TestFiles/metadata/Secp256R1 Packed Authenticator.json b/Test/TestFiles/metadata/Secp256R1 Packed Authenticator.json new file mode 100644 index 00000000..db5b0b68 --- /dev/null +++ b/Test/TestFiles/metadata/Secp256R1 Packed Authenticator.json @@ -0,0 +1,128 @@ +{ + "legalHeader": "https://fidoalliance.org/metadata/metadata-statement-legal-header/", + "description": "Virtual Packed authenticator", + "aaguid": "00000000-0000-0000-0000-000000000004", + "alternativeDescriptions": { + "en-GB": "Virtual Packed authenticator" + }, + "protocolFamily": "fido2", + "authenticatorVersion": 2, + "upv": [ + { + "major": 1, + "minor": 0 + } + ], + "authenticationAlgorithms": [ + "secp256r1_ecdsa_sha256_raw" + ], + "publicKeyAlgAndEncodings": [ + "cose" + ], + "attestationTypes": [ + "basic_full", + "basic_surrogate" + ], + "schema": 3, + "userVerificationDetails": [ + [ + { + "userVerificationMethod": "none" + } + ], + [ + { + "userVerificationMethod": "presence_internal" + } + ], + [ + { + "userVerificationMethod": "passcode_external", + "caDesc": { + "base": 10, + "minLength": 4 + } + } + ], + [ + { + "userVerificationMethod": "passcode_external", + "caDesc": { + "base": 10, + "minLength": 4 + } + }, + { + "userVerificationMethod": "presence_internal" + } + ] + ], + "keyProtection": [ + "hardware", + "secure_element" + ], + "matcherProtection": [ + "on_chip" + ], + "cryptoStrength": 128, + "attachmentHint": [ + "external", + "wired", + "wireless", + "nfc" + ], + "tcDisplay": [], + "attestationRootCertificates": [ + "MIIFwDCCA6gCCQCNm1u56oRwXTANBgkqhkiG9w0BAQsFADCBoTEYMBYGA1UEAwwPRklETzIgVEVTVCBST09UMTEwLwYJKoZIhvcNAQkBFiJjb25mb3JtYW5jZS10b29sc0BmaWRvYWxsaWFuY2Uub3JnMRYwFAYDVQQKDA1GSURPIEFsbGlhbmNlMQwwCgYDVQQLDANDV0cxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJNWTESMBAGA1UEBwwJV2FrZWZpZWxkMB4XDTE4MDMxNjE0MzUyN1oXDTQ1MDgwMTE0MzUyN1owgaExGDAWBgNVBAMMD0ZJRE8yIFRFU1QgUk9PVDExMC8GCSqGSIb3DQEJARYiY29uZm9ybWFuY2UtdG9vbHNAZmlkb2FsbGlhbmNlLm9yZzEWMBQGA1UECgwNRklETyBBbGxpYW5jZTEMMAoGA1UECwwDQ1dHMQswCQYDVQQGEwJVUzELMAkGA1UECAwCTVkxEjAQBgNVBAcMCVdha2VmaWVsZDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL11U5yAIVLMrL3xS8u8ysMSdOkDeoTO+RcAy+uXXp6k4SC+jOy37gICEtYI+MKQV1EMeMMf3rM1ueZAO3iPFa0NEdi/oQ7npnGjBNI8wMzD8FfNe6rWtzkDaHpsZW///MwWDpGyJR+Xyjcq6U4vS9bS6zZ7jslw0Oczx4UsYgOsIUXSSBaGOrRbxJ/JC5gnDYEYvtNM+PDPczLNKAyhdvBZWNWHr7MZ0P5TeJQcXsAoShRX2Y8U8fRNJm7SeiFKDP0Nn/QKxOSt7zGP4xt9nMasE1q2ZTdar2+W13CRz37RI0ZWpq/+YquoEbZ7Uj7NmBTcqhb260nmDER2FpwwYwPSark92IZbamozB8d7OEI1jJgsrjJhKan0EmRaWVBpHT4xYKdEu7r09S0JhKyU+52WDmmVQTMpYLrm4Xl7hRxyPyBYkalrozsGmPs8vlhNq3VsVbyBSMSpEmUaeAa7LLE9/Vh0agJLVFHh1ehYKJpzHnmmBXUqx0Fz3afmDm1NX0sr3O/6xIx1VSTViT3KNxBYpVH1qjHATLzuxcWmm+75fcJMiPYPSMXVmRb3Q1l91AM4BBeWhlP3Fbc7gDy0r+s7m0sGS6PT2J2rGog2rUxnJ+zCM11M7DeO0XM2nny4uRYPPk9w2EXzfvtdvieYU/5RB4RDm5TGxHhGXVZUgac5AgMBAAEwDQYJKoZIhvcNAQELBQADggIBAFt2XGd3k5GpbO1EUm3u60zT1fE6u6pOscp156k5VnsHgaHRHdIAPNLeLNmR7y5OnrXbh13CrGwU1q84jjJXpv+v14xUCc5i01yopFTQFLr4A7NHp2nNYfNhhIVSFAgW43EflJflbLEelCJzxLlWb5BoDsZeeNmEQsXIM1mJ26R3r0dzsHBb0uy+8LNR1gdVqdjhC8BLy3gh4+BWuidyZNt07LveDsSFW5rcj5wRrSx9hXPIyVpjQSljNvY7MVTouqJzNAAQMsTKkXPkTXldCop9Qo9UPkHRRm0l7LLtdaOoXrct0Ymocf8zxf9bFNiw9f4WRYQM6sMhzt8+s/oDilo4QhcUgeJEiEPESi6ynYTV62SHA4eMunUJ5dlCaRnFiR9DTImFa5IRzie326/nW/SPCaKc/yrFIihMMjJoSAPhpTb/K6yHOUG8r+KiQut7NzqGV301pQ9u62dGL5Oi1VXmCFlE2ramZs15BNOUyAo2CBbRJg3jKcdu/8QC6ojjDvQ863+7LPtn74wJC5RpUJsS0GhQWgq5pAXO3wA61Uobxi6MkOpCC0zBWx/d4CqpS4j4hFgxWBTXX48ihPu+hIxIF/AxbqtPvqLMExW/xZITn6ArpWyQ9e4SUVr3n3F33ap1XdDyZ0vwFcm18JQAtsvXT6qCLrWOXnHUgfn/+Viu" + ], + "icon": "data:image/png;base64,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", + "supportedExtensions": [ + { + "id": "hmac-secret", + "fail_if_unknown": false + }, + { + "id": "credProtect", + "fail_if_unknown": false + } + ], + "authenticatorGetInfo": { + "versions": [ + "U2F_V2", + "FIDO_2_0" + ], + "extensions": [ + "credProtect", + "hmac-secret" + ], + "aaguid": "326adcf00cef46d0939298d6c4a84a72", + "options": { + "plat": false, + "rk": true, + "clientPin": true, + "up": true, + "uv": true, + "uvToken": false, + "config": false + }, + "maxMsgSize": 1200, + "pinUvAuthProtocols": [ + 1 + ], + "maxCredentialCountInList": 16, + "maxCredentialIdLength": 128, + "transports": [ + "usb", + "nfc" + ], + "algorithms": [ + { + "type": "public-key", + "alg": -7 + } + ], + "maxAuthenticatorConfigLength": 1024, + "defaultCredProtect": 2, + "firmwareVersion": 5 + } +} \ No newline at end of file diff --git a/Test/TestMetadataService.cs b/Test/TestMetadataService.cs new file mode 100644 index 00000000..760656cc --- /dev/null +++ b/Test/TestMetadataService.cs @@ -0,0 +1,21 @@ +using Fido2NetLib; + +namespace Test +{ + public class TestMetadataService : ConformanceMetadataService + { + public TestMetadataService(IEnumerable repositories) : base(repositories) + { + } + + public void ChangeEntryGuid(Guid oldGuid, Guid newGuid) + { + if (!_entries.ContainsKey(oldGuid)) + return; + + _entries.Remove(oldGuid, out var entry); + entry.AaGuid = newGuid; + _entries.TryAdd(newGuid, entry); + } + } +} From cfaa1f9a81558bbe118f7cb7db27975d21103da8 Mon Sep 17 00:00:00 2001 From: googyi Date: Fri, 5 Jan 2024 18:13:06 +0100 Subject: [PATCH 8/8] TestPackedttestationAsyncFailTrustAnchorOnRootCertInTrustPath only works on Windows --- Test/Fido2Tests.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Test/Fido2Tests.cs b/Test/Fido2Tests.cs index ca08481f..3e2ccb8b 100644 --- a/Test/Fido2Tests.cs +++ b/Test/Fido2Tests.cs @@ -551,6 +551,9 @@ public async Task TestU2FAttestationAsync() [Fact] public async Task TestPackedttestationAsyncFailTrustAnchorOnRootCertInTrustPath() { + if (!OperatingSystem.IsWindows()) + return; + var targetGuid = new Guid("42383245-4437-3343-3846-423445354132"); var metadataService = CreateMetadataService("./metadata"); metadataService.ChangeEntryGuid(new Guid("00000000-0000-0000-0000-000000000004"), targetGuid);