From d119d58e9202ccf07bf59ff4aaee2e98d8e8d20b Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Fri, 4 Oct 2024 13:33:51 +0000 Subject: [PATCH] Move Test folder to Tests/Fido2.Tests --- Src/Fido2/Fido2.csproj | 8 +- .../Fido2.Tests}/.config/dotnet-tools.json | 0 {Test => Tests/Fido2.Tests}/Asn1Tests.cs | 0 .../Fido2.Tests}/Attestation/AndroidKey.cs | 0 .../Attestation/AndroidSafetyNet.cs | 0 .../Fido2.Tests}/Attestation/Apple.cs | 0 .../Attestation/AppleAppAttest.cs | 0 .../Attestation/DevicePublicKey.cs | 0 .../Fido2.Tests}/Attestation/FidoU2f.cs | 0 .../Fido2.Tests}/Attestation/None.cs | 0 .../Fido2.Tests}/Attestation/Packed.cs | 0 .../Fido2.Tests}/Attestation/Tpm.cs | 0 .../Fido2.Tests}/AttestationTypeTests.cs | 0 .../Fido2.Tests}/AuthenticatorDataTests.cs | 0 .../Fido2.Tests}/AuthenticatorResponse.cs | 0 {Test => Tests/Fido2.Tests}/Base64UrlTest.cs | 0 {Test => Tests/Fido2.Tests}/CborTests.cs | 0 .../Converters/FidoEnumConverterTests.cs | 0 .../Fido2.Tests}/CredentialPublicKeyTests.cs | 0 .../Fido2.Tests}/CryptoUtilsTests.cs | 0 .../Fido2.Tests}/EnumExtensionTest.cs | 0 .../ExistingU2fRegistrationDataTests.cs | 0 .../Fido2.Tests}/Extensions/AsnHelper.cs | 0 .../Fido2.Tests}/Extensions/CertInfoHelper.cs | 0 .../Fido2.Tests}/Extensions/PubAreaHelper.cs | 0 .../Extensions/SignatureHelper.cs | 0 .../Extensions/TpmAlgExtensions.cs | 0 .../Fido2.Tests}/Extensions/TpmSanEncoder.cs | 0 .../Fido2.Tests/Fido2.Tests.csproj | 6 +- {Test => Tests/Fido2.Tests}/Fido2Tests.cs | 2124 ++++++++--------- .../Fido2.Tests}/MetadataServiceTests.cs | 356 +-- .../Fido2.Tests}/PubKeyCredParamTests.cs | 0 .../TestFiles/assertionNoneOptions.json | 0 .../TestFiles/assertionNoneResponse.json | 0 .../attestationAndroidKeyOptions.json | 0 .../attestationAndroidKeyResponse.json | 0 .../TestFiles/attestationAppleOptions.json | 0 .../TestFiles/attestationAppleResponse.json | 0 .../TestFiles/attestationNoneOptions.json | 0 .../TestFiles/attestationNoneResponse.json | 0 .../TestFiles/attestationOptionsATKey.json | 0 .../TestFiles/attestationOptionsNone.json | 0 .../TestFiles/attestationOptionsPacked.json | 0 .../attestationOptionsPacked512.json | 0 .../attestationOptionsTrustKeyT110.json | 0 .../TestFiles/attestationOptionsU2F.json | 0 .../attestationResultTrustKeyT110.json | 0 .../TestFiles/attestationResultsATKey.json | 0 .../TestFiles/attestationResultsNone.json | 0 .../TestFiles/attestationResultsPacked.json | 0 .../attestationResultsPacked512.json | 0 .../TestFiles/attestationResultsU2F.json | 0 .../TestFiles/attestationTPMSHA1Options.json | 0 .../TestFiles/attestationTPMSHA1Response.json | 0 .../attestationTPMSHA256Options.json | 0 .../attestationTPMSHA256Response.json | 0 .../Fido2.Tests}/TestFiles/json1.json | 0 .../Fido2.Tests}/TestFiles/json2.json | 0 .../Fido2.Tests}/TestFiles/mdsCA.cer | 0 .../Fido2.Tests}/TestFiles/mdsCA.crl | 0 .../Fido2.Tests}/TestFiles/mdsRoot.cer | 0 .../Fido2.Tests}/TestFiles/mdsRoot.crl | 0 .../Fido2.Tests}/TestFiles/mdsSigning.cer | 0 .../Fido2.Tests}/TestFiles/options1.json | 0 .../Fido2.Tests}/TestFiles/options2.json | 0 {Test => Tests/Fido2.Tests}/xunit.runner.json | 0 fido2-net-lib.sln | 14 +- 67 files changed, 1254 insertions(+), 1254 deletions(-) rename {Test => Tests/Fido2.Tests}/.config/dotnet-tools.json (100%) rename {Test => Tests/Fido2.Tests}/Asn1Tests.cs (100%) rename {Test => Tests/Fido2.Tests}/Attestation/AndroidKey.cs (100%) rename {Test => Tests/Fido2.Tests}/Attestation/AndroidSafetyNet.cs (100%) rename {Test => Tests/Fido2.Tests}/Attestation/Apple.cs (100%) rename {Test => Tests/Fido2.Tests}/Attestation/AppleAppAttest.cs (100%) rename {Test => Tests/Fido2.Tests}/Attestation/DevicePublicKey.cs (100%) rename {Test => Tests/Fido2.Tests}/Attestation/FidoU2f.cs (100%) rename {Test => Tests/Fido2.Tests}/Attestation/None.cs (100%) rename {Test => Tests/Fido2.Tests}/Attestation/Packed.cs (100%) rename {Test => Tests/Fido2.Tests}/Attestation/Tpm.cs (100%) rename {Test => Tests/Fido2.Tests}/AttestationTypeTests.cs (100%) rename {Test => Tests/Fido2.Tests}/AuthenticatorDataTests.cs (100%) rename {Test => Tests/Fido2.Tests}/AuthenticatorResponse.cs (100%) rename {Test => Tests/Fido2.Tests}/Base64UrlTest.cs (100%) rename {Test => Tests/Fido2.Tests}/CborTests.cs (100%) rename {Test => Tests/Fido2.Tests}/Converters/FidoEnumConverterTests.cs (100%) rename {Test => Tests/Fido2.Tests}/CredentialPublicKeyTests.cs (100%) rename {Test => Tests/Fido2.Tests}/CryptoUtilsTests.cs (100%) rename {Test => Tests/Fido2.Tests}/EnumExtensionTest.cs (100%) rename {Test => Tests/Fido2.Tests}/ExistingU2fRegistrationDataTests.cs (100%) rename {Test => Tests/Fido2.Tests}/Extensions/AsnHelper.cs (100%) rename {Test => Tests/Fido2.Tests}/Extensions/CertInfoHelper.cs (100%) rename {Test => Tests/Fido2.Tests}/Extensions/PubAreaHelper.cs (100%) rename {Test => Tests/Fido2.Tests}/Extensions/SignatureHelper.cs (100%) rename {Test => Tests/Fido2.Tests}/Extensions/TpmAlgExtensions.cs (100%) rename {Test => Tests/Fido2.Tests}/Extensions/TpmSanEncoder.cs (100%) rename Test/Test.csproj => Tests/Fido2.Tests/Fido2.Tests.csproj (88%) rename {Test => Tests/Fido2.Tests}/Fido2Tests.cs (98%) rename {Test => Tests/Fido2.Tests}/MetadataServiceTests.cs (96%) rename {Test => Tests/Fido2.Tests}/PubKeyCredParamTests.cs (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/assertionNoneOptions.json (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/assertionNoneResponse.json (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/attestationAndroidKeyOptions.json (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/attestationAndroidKeyResponse.json (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/attestationAppleOptions.json (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/attestationAppleResponse.json (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/attestationNoneOptions.json (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/attestationNoneResponse.json (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/attestationOptionsATKey.json (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/attestationOptionsNone.json (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/attestationOptionsPacked.json (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/attestationOptionsPacked512.json (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/attestationOptionsTrustKeyT110.json (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/attestationOptionsU2F.json (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/attestationResultTrustKeyT110.json (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/attestationResultsATKey.json (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/attestationResultsNone.json (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/attestationResultsPacked.json (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/attestationResultsPacked512.json (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/attestationResultsU2F.json (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/attestationTPMSHA1Options.json (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/attestationTPMSHA1Response.json (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/attestationTPMSHA256Options.json (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/attestationTPMSHA256Response.json (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/json1.json (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/json2.json (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/mdsCA.cer (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/mdsCA.crl (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/mdsRoot.cer (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/mdsRoot.crl (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/mdsSigning.cer (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/options1.json (100%) rename {Test => Tests/Fido2.Tests}/TestFiles/options2.json (100%) rename {Test => Tests/Fido2.Tests}/xunit.runner.json (100%) diff --git a/Src/Fido2/Fido2.csproj b/Src/Fido2/Fido2.csproj index 287e9e63..31b3a5c8 100644 --- a/Src/Fido2/Fido2.csproj +++ b/Src/Fido2/Fido2.csproj @@ -19,13 +19,13 @@ - + - + - diff --git a/Test/.config/dotnet-tools.json b/Tests/Fido2.Tests/.config/dotnet-tools.json similarity index 100% rename from Test/.config/dotnet-tools.json rename to Tests/Fido2.Tests/.config/dotnet-tools.json diff --git a/Test/Asn1Tests.cs b/Tests/Fido2.Tests/Asn1Tests.cs similarity index 100% rename from Test/Asn1Tests.cs rename to Tests/Fido2.Tests/Asn1Tests.cs diff --git a/Test/Attestation/AndroidKey.cs b/Tests/Fido2.Tests/Attestation/AndroidKey.cs similarity index 100% rename from Test/Attestation/AndroidKey.cs rename to Tests/Fido2.Tests/Attestation/AndroidKey.cs diff --git a/Test/Attestation/AndroidSafetyNet.cs b/Tests/Fido2.Tests/Attestation/AndroidSafetyNet.cs similarity index 100% rename from Test/Attestation/AndroidSafetyNet.cs rename to Tests/Fido2.Tests/Attestation/AndroidSafetyNet.cs diff --git a/Test/Attestation/Apple.cs b/Tests/Fido2.Tests/Attestation/Apple.cs similarity index 100% rename from Test/Attestation/Apple.cs rename to Tests/Fido2.Tests/Attestation/Apple.cs diff --git a/Test/Attestation/AppleAppAttest.cs b/Tests/Fido2.Tests/Attestation/AppleAppAttest.cs similarity index 100% rename from Test/Attestation/AppleAppAttest.cs rename to Tests/Fido2.Tests/Attestation/AppleAppAttest.cs diff --git a/Test/Attestation/DevicePublicKey.cs b/Tests/Fido2.Tests/Attestation/DevicePublicKey.cs similarity index 100% rename from Test/Attestation/DevicePublicKey.cs rename to Tests/Fido2.Tests/Attestation/DevicePublicKey.cs diff --git a/Test/Attestation/FidoU2f.cs b/Tests/Fido2.Tests/Attestation/FidoU2f.cs similarity index 100% rename from Test/Attestation/FidoU2f.cs rename to Tests/Fido2.Tests/Attestation/FidoU2f.cs diff --git a/Test/Attestation/None.cs b/Tests/Fido2.Tests/Attestation/None.cs similarity index 100% rename from Test/Attestation/None.cs rename to Tests/Fido2.Tests/Attestation/None.cs diff --git a/Test/Attestation/Packed.cs b/Tests/Fido2.Tests/Attestation/Packed.cs similarity index 100% rename from Test/Attestation/Packed.cs rename to Tests/Fido2.Tests/Attestation/Packed.cs diff --git a/Test/Attestation/Tpm.cs b/Tests/Fido2.Tests/Attestation/Tpm.cs similarity index 100% rename from Test/Attestation/Tpm.cs rename to Tests/Fido2.Tests/Attestation/Tpm.cs diff --git a/Test/AttestationTypeTests.cs b/Tests/Fido2.Tests/AttestationTypeTests.cs similarity index 100% rename from Test/AttestationTypeTests.cs rename to Tests/Fido2.Tests/AttestationTypeTests.cs diff --git a/Test/AuthenticatorDataTests.cs b/Tests/Fido2.Tests/AuthenticatorDataTests.cs similarity index 100% rename from Test/AuthenticatorDataTests.cs rename to Tests/Fido2.Tests/AuthenticatorDataTests.cs diff --git a/Test/AuthenticatorResponse.cs b/Tests/Fido2.Tests/AuthenticatorResponse.cs similarity index 100% rename from Test/AuthenticatorResponse.cs rename to Tests/Fido2.Tests/AuthenticatorResponse.cs diff --git a/Test/Base64UrlTest.cs b/Tests/Fido2.Tests/Base64UrlTest.cs similarity index 100% rename from Test/Base64UrlTest.cs rename to Tests/Fido2.Tests/Base64UrlTest.cs diff --git a/Test/CborTests.cs b/Tests/Fido2.Tests/CborTests.cs similarity index 100% rename from Test/CborTests.cs rename to Tests/Fido2.Tests/CborTests.cs diff --git a/Test/Converters/FidoEnumConverterTests.cs b/Tests/Fido2.Tests/Converters/FidoEnumConverterTests.cs similarity index 100% rename from Test/Converters/FidoEnumConverterTests.cs rename to Tests/Fido2.Tests/Converters/FidoEnumConverterTests.cs diff --git a/Test/CredentialPublicKeyTests.cs b/Tests/Fido2.Tests/CredentialPublicKeyTests.cs similarity index 100% rename from Test/CredentialPublicKeyTests.cs rename to Tests/Fido2.Tests/CredentialPublicKeyTests.cs diff --git a/Test/CryptoUtilsTests.cs b/Tests/Fido2.Tests/CryptoUtilsTests.cs similarity index 100% rename from Test/CryptoUtilsTests.cs rename to Tests/Fido2.Tests/CryptoUtilsTests.cs diff --git a/Test/EnumExtensionTest.cs b/Tests/Fido2.Tests/EnumExtensionTest.cs similarity index 100% rename from Test/EnumExtensionTest.cs rename to Tests/Fido2.Tests/EnumExtensionTest.cs diff --git a/Test/ExistingU2fRegistrationDataTests.cs b/Tests/Fido2.Tests/ExistingU2fRegistrationDataTests.cs similarity index 100% rename from Test/ExistingU2fRegistrationDataTests.cs rename to Tests/Fido2.Tests/ExistingU2fRegistrationDataTests.cs diff --git a/Test/Extensions/AsnHelper.cs b/Tests/Fido2.Tests/Extensions/AsnHelper.cs similarity index 100% rename from Test/Extensions/AsnHelper.cs rename to Tests/Fido2.Tests/Extensions/AsnHelper.cs diff --git a/Test/Extensions/CertInfoHelper.cs b/Tests/Fido2.Tests/Extensions/CertInfoHelper.cs similarity index 100% rename from Test/Extensions/CertInfoHelper.cs rename to Tests/Fido2.Tests/Extensions/CertInfoHelper.cs diff --git a/Test/Extensions/PubAreaHelper.cs b/Tests/Fido2.Tests/Extensions/PubAreaHelper.cs similarity index 100% rename from Test/Extensions/PubAreaHelper.cs rename to Tests/Fido2.Tests/Extensions/PubAreaHelper.cs diff --git a/Test/Extensions/SignatureHelper.cs b/Tests/Fido2.Tests/Extensions/SignatureHelper.cs similarity index 100% rename from Test/Extensions/SignatureHelper.cs rename to Tests/Fido2.Tests/Extensions/SignatureHelper.cs diff --git a/Test/Extensions/TpmAlgExtensions.cs b/Tests/Fido2.Tests/Extensions/TpmAlgExtensions.cs similarity index 100% rename from Test/Extensions/TpmAlgExtensions.cs rename to Tests/Fido2.Tests/Extensions/TpmAlgExtensions.cs diff --git a/Test/Extensions/TpmSanEncoder.cs b/Tests/Fido2.Tests/Extensions/TpmSanEncoder.cs similarity index 100% rename from Test/Extensions/TpmSanEncoder.cs rename to Tests/Fido2.Tests/Extensions/TpmSanEncoder.cs diff --git a/Test/Test.csproj b/Tests/Fido2.Tests/Fido2.Tests.csproj similarity index 88% rename from Test/Test.csproj rename to Tests/Fido2.Tests/Fido2.Tests.csproj index 5fb30e72..9356eccf 100644 --- a/Test/Test.csproj +++ b/Tests/Fido2.Tests/Fido2.Tests.csproj @@ -16,9 +16,9 @@ - - - + + + diff --git a/Test/Fido2Tests.cs b/Tests/Fido2.Tests/Fido2Tests.cs similarity index 98% rename from Test/Fido2Tests.cs rename to Tests/Fido2.Tests/Fido2Tests.cs index a6b578d7..3d2b83a7 100644 --- a/Test/Fido2Tests.cs +++ b/Tests/Fido2.Tests/Fido2Tests.cs @@ -1,1062 +1,1062 @@ -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Text.Json; - -using Fido2NetLib; -using Fido2NetLib.Cbor; -using Fido2NetLib.Objects; - -using Microsoft.Extensions.Caching.Distributed; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Internal; -using Microsoft.Extensions.Logging; - -using Moq; - -using NSec.Cryptography; - -using static Fido2NetLib.AuthenticatorAttestationResponse; - -namespace fido2_net_lib.Test; - -// todo: Create tests and name Facts and json files better. -public class Fido2Tests -{ - private static readonly IMetadataService _metadataService; - private static readonly Fido2Configuration _config; - public static readonly List<(COSE.KeyType, COSE.Algorithm, COSE.EllipticCurve)> _validCOSEParameters; - - static Fido2Tests() - { - var services = new ServiceCollection(); - - services.AddDistributedMemoryCache(); - services.AddMemoryCache(); - services.AddLogging(); - services.AddHttpClient(); - - var provider = services.BuildServiceProvider(); - - var distributedCache = provider.GetService(); - var memCache = provider.GetService(); - - var repos = new List - { - new Fido2MetadataServiceRepository(provider.GetService()) - }; - - IMetadataService service = new DistributedCacheMetadataService( - repos, - distributedCache, - memCache, - provider.GetService>(), - new SystemClock()); - - _metadataService = service; - - _config = new Fido2Configuration { Origins = new HashSet { "https://localhost:44329" } }; - - var noCurve = COSE.EllipticCurve.Reserved; - - _validCOSEParameters = - [ - new(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256), - new(COSE.KeyType.EC2, COSE.Algorithm.ES384, COSE.EllipticCurve.P384), - new(COSE.KeyType.EC2, COSE.Algorithm.ES512, COSE.EllipticCurve.P521), - new(COSE.KeyType.RSA, COSE.Algorithm.RS256, noCurve), - new(COSE.KeyType.RSA, COSE.Algorithm.RS384, noCurve), - new(COSE.KeyType.RSA, COSE.Algorithm.RS512, noCurve), - new(COSE.KeyType.RSA, COSE.Algorithm.PS256, noCurve), - new(COSE.KeyType.RSA, COSE.Algorithm.PS384, noCurve), - new(COSE.KeyType.RSA, COSE.Algorithm.PS512, noCurve), - new(COSE.KeyType.OKP, COSE.Algorithm.EdDSA, COSE.EllipticCurve.Ed25519), - new(COSE.KeyType.EC2, COSE.Algorithm.ES256K, COSE.EllipticCurve.P256K) - ]; - } - - private async Task GetAsync(string filename) - { - return JsonSerializer.Deserialize(await File.ReadAllTextAsync(filename)); - } - - public abstract class Attestation - { - public CborMap _attestationObject; - public CredentialPublicKey _credentialPublicKey; - public const string rp = "https://www.passwordless.dev"; - public byte[] _challenge; - public X500DistinguishedName rootDN = new("CN=Testing, O=FIDO2-NET-LIB, C=US"); - public Oid oidIdFidoGenCeAaGuid = new("1.3.6.1.4.1.45724.1.1.4"); - //private byte[] asnEncodedAaGuid = new byte[] { 0x04, 0x10, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; - //public byte[] asnEncodedAaGuid = new byte[] { 0x04, 0x10, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; - public byte[] _asnEncodedAaguid; - protected X509BasicConstraintsExtension caExt = new(true, true, 2, false); - protected X509BasicConstraintsExtension notCAExt = new(false, false, 0, false); - public X509Extension idFidoGenCeAaGuidExt; - - public byte[] _rpIdHash => SHA256.HashData(Encoding.UTF8.GetBytes(rp)); - - public byte[] _clientDataJson - { - get - { - return JsonSerializer.SerializeToUtf8Bytes(new - { - type = "webauthn.create", - challenge = _challenge, - origin = rp - }); - } - } - - public byte[] _clientDataHash => SHA256.HashData(_clientDataJson); - public byte[] _attToBeSigned => [.. _authData.ToByteArray(), .. _clientDataHash]; - - public byte[] _attToBeSignedHash(HashAlgorithmName alg) - { - return CryptoUtils.HashData(alg, _attToBeSigned); - } - - public byte[] _credentialID; - public const AuthenticatorFlags _flags = AuthenticatorFlags.AT | AuthenticatorFlags.ED | AuthenticatorFlags.UP | AuthenticatorFlags.UV; - public ushort _signCount; - protected Guid _aaguid = new("F1D0F1D0-F1D0-F1D0-F1D0-F1D0F1D0F1D0"); - public Extensions GetExtensions() - { - var extBytes = new CborMap { { "testing", true } }.Encode(); - return new Extensions(extBytes); - } - - public AuthenticatorData _authData => new(_rpIdHash, _flags, _signCount, _acd, GetExtensions()); - - public AttestedCredentialData _acd => new(_aaguid, _credentialID, _credentialPublicKey); - - public Attestation() - { - _credentialID = RandomNumberGenerator.GetBytes(16); - _challenge = RandomNumberGenerator.GetBytes(128); - - byte[] signCount = RandomNumberGenerator.GetBytes(2); - - _signCount = BitConverter.ToUInt16(signCount, 0); - - _attestationObject = new CborMap(); - - _asnEncodedAaguid = AsnHelper.GetAaguidBlob(_aaguid); - - idFidoGenCeAaGuidExt = new X509Extension(oidIdFidoGenCeAaGuid, _asnEncodedAaguid, false); - } - - public async Task MakeAttestationResponseAsync() - { - _attestationObject.Set("authData", new CborByteString(_authData.ToByteArray())); - - var attestationResponse = new AuthenticatorAttestationRawResponse - { - Type = PublicKeyCredentialType.PublicKey, - Id = [0xf1, 0xd0], - RawId = [0xf1, 0xd0], - Response = new AuthenticatorAttestationRawResponse.AttestationResponse - { - AttestationObject = _attestationObject.Encode(), - ClientDataJson = _clientDataJson, - Transports = [AuthenticatorTransport.Internal] - }, - ClientExtensionResults = new AuthenticationExtensionsClientOutputs() - { - AppID = true, - Extensions = ["foo", "bar"], - Example = "test", - UserVerificationMethod = new ulong[][] - { - new ulong[] - { - 4 // USER_VERIFY_PASSCODE_INTERNAL - }, - }, - } - }; - - var originalOptions = new CredentialCreateOptions - { - Attestation = AttestationConveyancePreference.Direct, - AuthenticatorSelection = new AuthenticatorSelection - { - AuthenticatorAttachment = AuthenticatorAttachment.CrossPlatform, - ResidentKey = ResidentKeyRequirement.Required, - UserVerification = UserVerificationRequirement.Discouraged, - }, - Challenge = _challenge, - PubKeyCredParams = new List() - { - new(COSE.Algorithm.ES256), - new(COSE.Algorithm.ES384), - new(COSE.Algorithm.ES512), - new(COSE.Algorithm.RS1), - new(COSE.Algorithm.RS256), - new(COSE.Algorithm.RS384), - new(COSE.Algorithm.RS512), - new(COSE.Algorithm.PS256), - new(COSE.Algorithm.PS384), - new(COSE.Algorithm.PS512), - new(COSE.Algorithm.EdDSA), - new(COSE.Algorithm.ES256K), - }, - Rp = new PublicKeyCredentialRpEntity(rp, rp, ""), - User = new Fido2User - { - Name = "testuser", - Id = "testuser"u8.ToArray(), - DisplayName = "Test User", - }, - Timeout = 60000, - }; - - IsCredentialIdUniqueToUserAsyncDelegate callback = (args, cancellationToken) => - { - return Task.FromResult(true); - }; - - var lib = new Fido2(new Fido2Configuration - { - ServerDomain = rp, - ServerName = rp, - Origins = new HashSet { rp }, - }); - - var credentialMakeResult = await lib.MakeNewCredentialAsync(attestationResponse, originalOptions, callback); - - return credentialMakeResult; - } - - internal byte[] SignData(COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve crv) - { - ECDsa ecdsa = null; - RSA rsa = null; - Key privateKey = null; - byte[] publicKey = null; - - switch (kty) - { - case COSE.KeyType.EC2: - { - ecdsa = MakeECDsa(alg, crv); - break; - } - case COSE.KeyType.RSA: - { - rsa = RSA.Create(); - break; - } - case COSE.KeyType.OKP: - { - MakeEdDSA(out var privateKeySeed, out publicKey, out byte[] expandedPrivateKey); - privateKey = Key.Import(SignatureAlgorithm.Ed25519, expandedPrivateKey, KeyBlobFormat.RawPrivateKey); - break; - } - default: - throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}"); - } - - return SignData(kty, alg, crv, ecdsa, rsa, privateKey, publicKey); - } - - internal byte[] SignData(COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve curve, ECDsa ecdsa = null, RSA rsa = null, Key expandedPrivateKey = null, byte[] publicKey = null) - { - switch (kty) - { - case COSE.KeyType.EC2: - { - var ecParams = ecdsa.ExportParameters(true); - _credentialPublicKey = MakeCredentialPublicKey(kty, alg, curve, ecParams.Q.X, ecParams.Q.Y); - var signature = ecdsa.SignData(_attToBeSigned, CryptoUtils.HashAlgFromCOSEAlg(alg)); - return SignatureHelper.EcDsaSigFromSig(signature, ecdsa.KeySize); - } - case COSE.KeyType.RSA: - { - RSASignaturePadding padding; - switch (alg) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms - { - case COSE.Algorithm.PS256: - case COSE.Algorithm.PS384: - case COSE.Algorithm.PS512: - padding = RSASignaturePadding.Pss; - break; - - case COSE.Algorithm.RS1: - case COSE.Algorithm.RS256: - case COSE.Algorithm.RS384: - case COSE.Algorithm.RS512: - padding = RSASignaturePadding.Pkcs1; - break; - default: - throw new ArgumentOutOfRangeException(nameof(alg), $"Missing or unknown alg {alg}"); - } - - var rsaParams = rsa.ExportParameters(true); - _credentialPublicKey = MakeCredentialPublicKey(kty, alg, rsaParams.Modulus, rsaParams.Exponent); - return rsa.SignData(_attToBeSigned, CryptoUtils.HashAlgFromCOSEAlg(alg), padding); - } - case COSE.KeyType.OKP: - { - _credentialPublicKey = MakeCredentialPublicKey(kty, alg, COSE.EllipticCurve.Ed25519, publicKey); - return SignatureAlgorithm.Ed25519.Sign(expandedPrivateKey, _attToBeSigned); - } - - default: - throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}"); - } - } - } - - internal static byte[] SignData(COSE.KeyType kty, COSE.Algorithm alg, byte[] data, ECDsa ecdsa = null, RSA rsa = null, byte[] expandedPrivateKey = null) - { - switch (kty) - { - case COSE.KeyType.EC2: - { - var signature = ecdsa.SignData(data, CryptoUtils.HashAlgFromCOSEAlg(alg)); - return SignatureHelper.EcDsaSigFromSig(signature, ecdsa.KeySize); - } - case COSE.KeyType.RSA: - { - RSASignaturePadding padding; - switch (alg) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms - { - case COSE.Algorithm.PS256: - case COSE.Algorithm.PS384: - case COSE.Algorithm.PS512: - padding = RSASignaturePadding.Pss; - break; - - case COSE.Algorithm.RS1: - case COSE.Algorithm.RS256: - case COSE.Algorithm.RS384: - case COSE.Algorithm.RS512: - padding = RSASignaturePadding.Pkcs1; - break; - default: - throw new ArgumentOutOfRangeException(nameof(alg), $"Missing or unknown alg {alg}"); - } - return rsa.SignData(data, CryptoUtils.HashAlgFromCOSEAlg(alg), padding); - } - case COSE.KeyType.OKP: - { - Key privateKey = Key.Import(SignatureAlgorithm.Ed25519, expandedPrivateKey, KeyBlobFormat.RawPrivateKey); - return SignatureAlgorithm.Ed25519.Sign(privateKey, data); - } - - default: - throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}"); - } - } - - [Fact] - public void TestStringIsSerializable() - { - var x2 = new AuthenticatorSelection - { - UserVerification = UserVerificationRequirement.Discouraged - }; - - var json = JsonSerializer.Serialize(x2); - var c3 = JsonSerializer.Deserialize(json); - - Assert.Equal(UserVerificationRequirement.Discouraged, c3.UserVerification); - - Assert.NotEqual(UserVerificationRequirement.Required, c3.UserVerification); - - // Assert.True("discouraged" == UserVerificationRequirement.Discouraged); - // Assert.False("discouraged" != UserVerificationRequirement.Discouraged); - - Assert.False(UserVerificationRequirement.Required == UserVerificationRequirement.Discouraged); - Assert.True(UserVerificationRequirement.Required != UserVerificationRequirement.Discouraged); - - // testing where string and member name mismatch - - var y1 = AuthenticatorAttachment.CrossPlatform; - var yjson = JsonSerializer.Serialize(y1); - Assert.Equal("\"cross-platform\"", yjson); - - var y2 = JsonSerializer.Deserialize(yjson); - - Assert.Equal(AuthenticatorAttachment.CrossPlatform, y2); - - // test list of typed strings - var z1 = new[] { - AuthenticatorTransport.Ble, - AuthenticatorTransport.Usb, - AuthenticatorTransport.Nfc, - AuthenticatorTransport.Internal - }; - - var zjson = JsonSerializer.Serialize(z1); - var z2 = JsonSerializer.Deserialize(zjson); - - Assert.All(z2, (x) => z1.Contains(x)); - Assert.Equal(z1, z2); - } - - [Fact] - public async Task TestFido2AssertionAsync() - { - //var existingKey = "45-43-53-31-20-00-00-00-0E-B4-F3-73-C2-AC-7D-F7-7E-7D-17-D3-A3-A2-CC-AB-E5-C6-B1-42-ED-10-AC-7C-15-72-39-8D-75-C6-5B-B9-76-09-33-A0-30-F2-44-51-C8-31-AF-72-9B-4F-7B-AB-4F-85-2D-7D-1F-E0-B5-BD-A3-3D-0E-D6-18-04-CD-98"; - - //var key2 = "45-43-53-31-20-00-00-00-1D-60-44-D7-92-A0-0C-1E-3B-F9-58-5A-28-43-92-FD-F6-4F-BB-7F-8E-86-33-38-30-A4-30-5D-4E-2C-71-E3-53-3C-7B-98-81-99-FE-A9-DA-D9-24-8E-04-BD-C7-86-40-D3-03-1E-6E-00-81-7D-85-C3-A2-19-C9-21-85-8D"; - //var key2 = "45-43-53-31-20-00-00-00-A9-E9-12-2A-37-8A-F0-74-E7-BA-52-54-B0-91-55-46-DB-21-E5-2C-01-B8-FB-69-CD-E5-ED-02-B6-C3-16-E3-1A-59-16-C1-43-87-0D-04-B9-94-7F-CF-56-E5-AA-5E-96-8C-5B-27-8F-83-F4-E2-50-AB-B3-F6-28-A1-F8-9E"; - - var options = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationNoneOptions.json")); - 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); - - 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() { - new PublicKeyCredentialDescriptor(Convert.FromHexString(credId.Replace("-", ""))) - }; - - // assertion - - var aoptions = await GetAsync("./assertionNoneOptions.json"); - var aresponse = await GetAsync("./assertionNoneResponse.json"); - } - - [Fact] - public async Task TestAppleAppAttestDev() - { - var b64 = "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"; - var cbor = Convert.FromBase64String(b64); - var json = (CborMap)CborObject.Decode(cbor); - - var AttestationObject = new ParsedAttestationObject - ( - fmt: (string)json["fmt"], - attStmt: (CborMap)json["attStmt"], - authData: AuthenticatorData.Parse((byte[])json["authData"]) - ); - - var clientDataJson = SHA256.HashData(Encoding.UTF8.GetBytes("This is a test. This will need to be removed before merging.")); - - var verifier = new AppleAppAttest(); - var verifyResult = await verifier.VerifyAsync(AttestationObject.AttStmt, AttestationObject.AuthData, clientDataJson); - Assert.True(verifyResult.Type.Equals(AttestationType.Basic)); - } - - [Fact] - public async Task TestAppleAppAttestProd() - { - var b64 = "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"; - var cbor = Convert.FromBase64String(b64); - var json = (CborMap)CborObject.Decode(cbor); - - var AttestationObject = new ParsedAttestationObject - ( - fmt: (string)json["fmt"], - attStmt: (CborMap)json["attStmt"], - authData: AuthenticatorData.Parse((byte[])json["authData"]) - ); - - var clientDataJson = SHA256.HashData(Encoding.UTF8.GetBytes("1234567890abcdefgh")); - - var verifier = new AppleAppAttest(); - var ex = await Assert.ThrowsAsync(async () => _ = await verifier.VerifyAsync(AttestationObject.AttStmt, AttestationObject.AuthData, clientDataJson)); - - const string windowsErrorMessage = "Failed to build chain in Apple AppAttest attestation: A required certificate is not within its validity period when verifying against the current system clock or the timestamp in the signed file."; - const string cryptoKitErrorMessage = "Failed to build chain in Apple AppAttest attestation: An expired certificate was detected."; - const string linuxErrorMessage = "Failed to build chain in Apple AppAttest attestation: certificate has expired"; - - Assert.True(ex.Message is windowsErrorMessage or cryptoKitErrorMessage or linuxErrorMessage); - } - - [Fact] - public async Task TestParsingAsync() - { - var jsonPost = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./json1.json")); - var options = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./options1.json")); - - Assert.NotNull(jsonPost); - - var o = AuthenticatorAttestationResponse.Parse(jsonPost); - await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), _metadataService, CancellationToken.None); - } - - [Fact] - public void MetadataBLOBPayloadEntry_Can_Be_JSON_Roundtripped() - { - var input = new MetadataBLOBPayloadEntry() - { - AaGuid = Guid.NewGuid(), - MetadataStatement = new MetadataStatement(), - StatusReports = Array.Empty(), - TimeOfLastStatusChange = DateTime.UtcNow.ToString("o") - }; - - input.MetadataStatement.AaGuid = Guid.NewGuid(); - input.MetadataStatement.Description = "Test entry"; - input.MetadataStatement.AuthenticatorVersion = 1; - input.MetadataStatement.Upv = new[] { new UafVersion(1, 0) }; - input.MetadataStatement.ProtocolFamily = "foo"; - input.MetadataStatement.AttestationTypes = ["bar"]; - input.MetadataStatement.AuthenticationAlgorithms = ["alg0", "alg1"]; - input.MetadataStatement.PublicKeyAlgAndEncodings = ["example0", "example1"]; - input.MetadataStatement.TcDisplay = ["transaction", "confirmation"]; - input.MetadataStatement.KeyProtection = ["protector"]; - input.MetadataStatement.MatcherProtection = ["stuff", "things"]; - input.MetadataStatement.UserVerificationDetails = Array.Empty(); - input.MetadataStatement.AttestationRootCertificates = ["..."]; - - var json = JsonSerializer.Serialize(input); - - var output = JsonSerializer.Deserialize(json); - - Assert.Equal(input.AaGuid, output.AaGuid); - } - - [Fact] - public void TestAuthenticatorDataPa2rsing() - { - var bs = new byte[] { 1, 2, 3 }; - var x = new CborMap { { "bytes", bs } }; - var s = (byte[])x["bytes"]; - - Assert.Equal(s, bs); - } - - [Fact] - 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); - } - - [Fact] - public async Task TestPackedAttestationAsync() - { - var jsonPost = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationResultsPacked.json")); - 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); - var authData = o.AttestationObject.AuthData; - var acdBytes = authData.AttestedCredentialData.ToByteArray(); - var acd = AttestedCredentialData.Parse(acdBytes); - Assert.Equal(acd.ToByteArray(), acdBytes); - } - - [Fact] - public async Task TestNoneAttestationAsync() - { - var jsonPost = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationResultsNone.json")); - 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); - } - - [Fact] - 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); - } - - [Fact] - public async Task TestTPMSHA1AttestationAsync() - { - var jsonPost = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationTPMSHA1Response.json")); - 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); - } - - [Fact] - 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); - } - - [Fact] - 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); - } - - [Fact] - 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); - var authData = o.AttestationObject.AuthData; - var acdBytes = authData.AttestedCredentialData.ToByteArray(); - var acd = AttestedCredentialData.Parse(acdBytes); - Assert.Equal(acd.ToByteArray(), acdBytes); - } - - [Fact] - public async Task TestInvalidU2FAttestationAsync() - { - // TODO: Figure out why this test fails on macOS and Linux - if (!OperatingSystem.IsWindows()) - return; - - 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); - var authData = o.AttestationObject.AuthData; - var acdBytes = authData.AttestedCredentialData.ToByteArray(); - var acd = AttestedCredentialData.Parse(acdBytes); - Assert.Equal(acd.ToByteArray(), acdBytes); - } - - [Fact] - public async Task TestMdsStatusReportsSuccessAsync() - { - var options = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationNoneOptions.json")); - var response = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationNoneResponse.json")); - - var mockMetadataService = new Mock(MockBehavior.Strict); - mockMetadataService.Setup(m => m.GetEntryAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new MetadataBLOBPayloadEntry() - { - StatusReports = - [ - new StatusReport { Status = AuthenticatorStatus.FIDO_CERTIFIED } - ] - }); - 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); - } - - [Fact] - public async Task TestMdsStatusReportsUndesiredAsync() - { - var options = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationNoneOptions.json")); - var response = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationNoneResponse.json")); - - var mockMetadataService = new Mock(MockBehavior.Strict); - mockMetadataService.Setup(m => m.GetEntryAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new MetadataBLOBPayloadEntry() - { - StatusReports = - [ - new StatusReport { Status = AuthenticatorStatus.FIDO_CERTIFIED }, - new StatusReport { Status = AuthenticatorStatus.REVOKED } - ] - }); - mockMetadataService.Setup(m => m.ConformanceTesting()).Returns(false); - - var o = AuthenticatorAttestationResponse.Parse(response); - await Assert.ThrowsAsync(() => - o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), mockMetadataService.Object, CancellationToken.None)); - } - - [Fact] - public async Task TestMdsStatusReportsUndesiredFixedAsync() - { - var options = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationNoneOptions.json")); - var response = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationNoneResponse.json")); - - var mockMetadataService = new Mock(MockBehavior.Strict); - mockMetadataService.Setup(m => m.GetEntryAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new MetadataBLOBPayloadEntry() - { - StatusReports = new StatusReport[] - { - new StatusReport() { Status = AuthenticatorStatus.FIDO_CERTIFIED }, - new StatusReport() { Status = AuthenticatorStatus.REVOKED }, - new StatusReport() { Status = AuthenticatorStatus.UPDATE_AVAILABLE } - } - }); - 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); - } - - [Fact] - public async Task TestMdsStatusReportsNullAsync() - { - var options = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationNoneOptions.json")); - var response = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationNoneResponse.json")); - - var mockMetadataService = new Mock(MockBehavior.Strict); - mockMetadataService.Setup(m => m.GetEntryAsync(It.IsAny(), It.IsAny())).ReturnsAsync((MetadataBLOBPayloadEntry)null); - 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); - } - - //public void TestHasCorrentAAguid() - //{ - // var expectedAaguid = new Uint8Array([ - // 0x42, 0x38, 0x32, 0x45, 0x44, 0x37, 0x33, 0x43, 0x38, 0x46, 0x42, 0x34, 0x45, 0x35, 0x41, 0x32 - //]).buffer; - //} - [Fact] - public void TestAttestedCredentialDataES256() - { - var aaguid = new Guid("F1D0F1D0-F1D0-F1D0-F1D0-F1D0F1D0F1D0"); - var credentialID = new byte[] { 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; - var ecdsa = MakeECDsa(COSE.Algorithm.ES256, COSE.EllipticCurve.P256); - var ecParams = ecdsa.ExportParameters(true); - var cpk = MakeCredentialPublicKey(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecParams.Q.X, ecParams.Q.Y); - - var acdFromConst = new AttestedCredentialData(aaguid, credentialID, cpk); - var acdBytes = acdFromConst.ToByteArray(); - var acdFromBytes = AttestedCredentialData.Parse(acdBytes); - Assert.Equal(acdFromBytes.ToByteArray(), acdFromConst.ToByteArray()); - } - - [Fact] - public void TestAttestedCredentialDataRSA() - { - var aaguid = new Guid("F1D0F1D0-F1D0-F1D0-F1D0-F1D0F1D0F1D0"); - var credentialID = new byte[] { 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; - var rsa = RSA.Create(); - var rsaParams = rsa.ExportParameters(true); - var cpk = MakeCredentialPublicKey(COSE.KeyType.RSA, COSE.Algorithm.RS256, rsaParams.Modulus, rsaParams.Exponent); - - var acdFromConst = new AttestedCredentialData(aaguid, credentialID, cpk); - var acdBytes = acdFromConst.ToByteArray(); - var acdFromBytes = AttestedCredentialData.Parse(acdBytes); - Assert.Equal(acdFromBytes.ToByteArray(), acdFromConst.ToByteArray()); - - var sig = SignData(COSE.KeyType.RSA, COSE.Algorithm.RS256, acdBytes, null, rsa, null); - - Assert.True(cpk.Verify(acdBytes, sig)); - sig[^1] ^= 0xff; - Assert.False(cpk.Verify(acdBytes, sig)); - } - - [Fact] - public void TestAttestedCredentialDataOKP() - { - var aaGuid = new Guid("F1D0F1D0-F1D0-F1D0-F1D0-F1D0F1D0F1D0"); - var credentialID = new byte[] { 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; - MakeEdDSA(out _, out var publicKey, out var privateKey); - var cpk = MakeCredentialPublicKey(COSE.KeyType.OKP, COSE.Algorithm.EdDSA, COSE.EllipticCurve.Ed25519, publicKey); - - var acdFromConst = new AttestedCredentialData(aaGuid, credentialID, cpk); - var acdBytes = acdFromConst.ToByteArray(); - var acdFromBytes = AttestedCredentialData.Parse(acdBytes); - Assert.Equal(acdFromBytes.ToByteArray(), acdFromConst.ToByteArray()); - - var sig = SignData(COSE.KeyType.OKP, COSE.Algorithm.EdDSA, acdBytes, null, null, privateKey); - - Assert.True(cpk.Verify(acdBytes, sig)); - sig[^1] ^= 0xff; - Assert.False(cpk.Verify(acdBytes, sig)); - } - - [Fact] - public void TestAuthenticatorData() - { - var rpId = "fido2.azurewebsites.net/"u8; - var rpIdHash = SHA256.HashData(rpId); - var flags = AuthenticatorFlags.AT | AuthenticatorFlags.ED | AuthenticatorFlags.UP | AuthenticatorFlags.UV; - const ushort signCount = 0xf1d0; - var aaGuid = new Guid("F1D0F1D0-F1D0-F1D0-F1D0-F1D0F1D0F1D0"); - var credentialID = new byte[] { 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; - var ecdsa = MakeECDsa(COSE.Algorithm.ES256, COSE.EllipticCurve.P256); - var ecParams = ecdsa.ExportParameters(true); - var cpk = MakeCredentialPublicKey(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecParams.Q.X, ecParams.Q.Y); - - var acd = new AttestedCredentialData(aaGuid, credentialID, cpk); - var extBytes = new CborMap { { "testing", true } }.Encode(); - var exts = new Extensions(extBytes); - - var ad = new AuthenticatorData(rpIdHash, flags, signCount, acd, exts); - Assert.Equal(rpIdHash, ad.RpIdHash); - Assert.True(ad.HasAttestedCredentialData | ad.UserPresent | ad.UserVerified | ad.HasExtensionsData); - Assert.Equal(signCount, ad.SignCount); - Assert.Equal(ad.AttestedCredentialData.ToByteArray(), acd.ToByteArray()); - Assert.Equal(extBytes, ad.Extensions.GetBytes()); - } - - [Fact] - public async Task TestAssertionResponse() - { - VerifyAssertionResult avr; - foreach (var (type, alg, curve) in _validCOSEParameters) - { - // No support for P256K on OSX - if (OperatingSystem.IsMacOS() && curve is COSE.EllipticCurve.P256K) - return; - - if (curve != default) - { - avr = await MakeAssertionResponseAsync(type, alg, curve); - } - else - { - avr = await MakeAssertionResponseAsync(type, alg); - } - - Assert.Equal([0xf1, 0xd0], avr.CredentialId); - Assert.Equal("1", avr.SignCount.ToString("X")); - } - } - - internal static async Task MakeAssertionResponseAsync( - COSE.KeyType kty, - COSE.Algorithm alg, - COSE.EllipticCurve crv = COSE.EllipticCurve.P256, - CredentialPublicKey cpk = null, - ushort signCount = 0, - ECDsa ecdsa = null, - RSA rsa = null, - byte[] expandedPrivateKey = null) - { - const string rp = "https://www.passwordless.dev"; - byte[] rpId = Encoding.UTF8.GetBytes(rp); - var rpIdHash = SHA256.HashData(rpId); - var flags = AuthenticatorFlags.AT | AuthenticatorFlags.ED | AuthenticatorFlags.UP | AuthenticatorFlags.UV; - var aaGuid = new Guid("F1D0F1D0-F1D0-F1D0-F1D0-F1D0F1D0F1D0"); - var credentialId = new byte[] { 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; - if (cpk == null) - { - switch (kty) - { - case COSE.KeyType.EC2: - { - ecdsa ??= MakeECDsa(alg, crv); - - var ecParams = ecdsa.ExportParameters(true); - cpk = MakeCredentialPublicKey(kty, alg, crv, ecParams.Q.X, ecParams.Q.Y); - break; - } - case COSE.KeyType.RSA: - { - rsa ??= RSA.Create(); - - var rsaParams = rsa.ExportParameters(true); - cpk = MakeCredentialPublicKey(kty, alg, rsaParams.Modulus, rsaParams.Exponent); - break; - } - case COSE.KeyType.OKP: - { - byte[] publicKey = null; - if (expandedPrivateKey == null) - { - MakeEdDSA(out var privateKeySeed, out publicKey, out expandedPrivateKey); - } - - cpk = MakeCredentialPublicKey(kty, alg, COSE.EllipticCurve.Ed25519, publicKey); - break; - } - default: - throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}"); - } - } - var acd = new AttestedCredentialData(aaGuid, credentialId, cpk); - var extBytes = new CborMap { { "testing", true } }.Encode(); - var exts = new Extensions(extBytes); - - var ad = new AuthenticatorData(rpIdHash, flags, (uint)(signCount + 1), acd, exts); - var authData = ad.ToByteArray(); - - var challenge = new byte[128]; - RandomNumberGenerator.Fill(challenge); - - var clientData = new - { - type = "webauthn.get", - challenge = challenge, - origin = rp, - }; - var clientDataJson = JsonSerializer.SerializeToUtf8Bytes(clientData); - - var hashedClientDataJson = SHA256.HashData(clientDataJson); - byte[] data = [.. authData, .. hashedClientDataJson]; - byte[] signature = SignData(kty, alg, data, ecdsa, rsa, expandedPrivateKey); - - var userHandle = new byte[16]; - RandomNumberGenerator.Fill(userHandle); - - var assertion = new AuthenticatorAssertionRawResponse.AssertionResponse() - { - AuthenticatorData = authData, - Signature = signature, - ClientDataJson = clientDataJson, - UserHandle = userHandle, - }; - - var lib = new Fido2(new Fido2Configuration - { - ServerDomain = rp, - ServerName = rp, - Origins = new HashSet { rp }, - }); - var existingCredentials = new List(); - var cred = new PublicKeyCredentialDescriptor([0xf1, 0xd0]); - existingCredentials.Add(cred); - - var options = lib.GetAssertionOptions(existingCredentials, null, null); - options.Challenge = challenge; - var response = new AuthenticatorAssertionRawResponse() - { - Response = assertion, - Type = PublicKeyCredentialType.PublicKey, - Id = [0xf1, 0xd0], - RawId = [0xf1, 0xd0], - }; - IsUserHandleOwnerOfCredentialIdAsync callback = (args, cancellationToken) => - { - return Task.FromResult(true); - }; - return await lib.MakeAssertionAsync(response, options, cpk.GetBytes(), null, signCount, callback); - } - - internal static void MakeEdDSA(out byte[] privateKeySeed, out byte[] publicKey, out byte[] expandedPrivateKey) - { - privateKeySeed = new byte[32]; - RandomNumberGenerator.Fill(privateKeySeed); - var key = Key.Create(SignatureAlgorithm.Ed25519, new KeyCreationParameters() { ExportPolicy = KeyExportPolicies.AllowPlaintextExport }); - expandedPrivateKey = key.Export(KeyBlobFormat.RawPrivateKey); - publicKey = key.Export(KeyBlobFormat.RawPublicKey); - } - - internal static ECDsa MakeECDsa(COSE.Algorithm alg, COSE.EllipticCurve crv) - { - ECCurve curve; - switch (alg) - { - case COSE.Algorithm.ES256K: - switch (crv) - { - case COSE.EllipticCurve.P256K: - if (OperatingSystem.IsMacOS()) - { - // see https://github.com/dotnet/runtime/issues/47770 - throw new PlatformNotSupportedException($"No support currently for secP256k1 on macOS"); - } - curve = ECCurve.CreateFromFriendlyName("secP256k1"); - break; - default: - throw new ArgumentOutOfRangeException(nameof(crv), $"Missing or unknown crv {crv}"); - } - break; - case COSE.Algorithm.ES256: - curve = crv switch - { - COSE.EllipticCurve.P256 => ECCurve.NamedCurves.nistP256, - _ => throw new ArgumentOutOfRangeException(nameof(crv), $"Missing or unknown crv {crv}"), - }; - break; - case COSE.Algorithm.ES384: - curve = crv switch // https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves - { - COSE.EllipticCurve.P384 => ECCurve.NamedCurves.nistP384, - _ => throw new ArgumentOutOfRangeException(nameof(crv), $"Missing or unknown crv {crv}"), - }; - break; - case COSE.Algorithm.ES512: - curve = crv switch // https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves - { - COSE.EllipticCurve.P521 => ECCurve.NamedCurves.nistP521, - _ => throw new ArgumentOutOfRangeException(nameof(crv), $"Missing or unknown crv {crv}"), - }; - break; - default: - throw new ArgumentOutOfRangeException(nameof(alg), $"Missing or unknown alg {alg}"); - } - return ECDsa.Create(curve); - } - - internal static CredentialPublicKey MakeCredentialPublicKey(COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve crv, byte[] x, byte[] y) - { - return MakeCredentialPublicKey(kty, alg, crv, x, y, null, null); - } - - internal static CredentialPublicKey MakeCredentialPublicKey(COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve crv, byte[] x) - { - return MakeCredentialPublicKey(kty, alg, crv, x, null, null, null); - } - - internal static CredentialPublicKey MakeCredentialPublicKey(COSE.KeyType kty, COSE.Algorithm alg, byte[] n, byte[] e) - { - return MakeCredentialPublicKey(kty, alg, null, null, null, n, e); - } - - internal static CredentialPublicKey MakeCredentialPublicKey(COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve? crv, byte[] x, byte[] y, byte[] n, byte[] e) - { - var cpk = new CborMap - { - { COSE.KeyCommonParameter.KeyType, kty }, - { COSE.KeyCommonParameter.Alg, alg } - }; - - switch (kty) - { - case COSE.KeyType.EC2: - cpk.Add(COSE.KeyTypeParameter.X, x); - cpk.Add(COSE.KeyTypeParameter.Y, y); - cpk.Add((int)COSE.KeyTypeParameter.Crv, (int)crv); - break; - case COSE.KeyType.RSA: - cpk.Add(COSE.KeyTypeParameter.N, n); - cpk.Add(COSE.KeyTypeParameter.E, e); - break; - case COSE.KeyType.OKP: - cpk.Add(COSE.KeyTypeParameter.X, x); - cpk.Add((int)COSE.KeyTypeParameter.Crv, (int)crv); - break; - default: - throw new ArgumentOutOfRangeException(nameof(kty), kty, "Invalid COSE key type"); - } - return new CredentialPublicKey(cpk); - } - - internal static CredentialPublicKey MakeCredentialPublicKey((COSE.KeyType, COSE.Algorithm, COSE.EllipticCurve) param) - { - var (kty, alg, crv) = param; - - CredentialPublicKey cpk; - switch (kty) - { - case COSE.KeyType.EC2: - { - var ecdsa = MakeECDsa(alg, crv); - var ecParams = ecdsa.ExportParameters(true); - cpk = MakeCredentialPublicKey(kty, alg, crv, ecParams.Q.X, ecParams.Q.Y); - break; - } - case COSE.KeyType.RSA: - { - var rsa = RSA.Create(); - var rsaParams = rsa.ExportParameters(true); - cpk = MakeCredentialPublicKey(kty, alg, rsaParams.Modulus, rsaParams.Exponent); - break; - } - case COSE.KeyType.OKP: - { - MakeEdDSA(out var privateKeySeed, out byte[] publicKey, out _); - cpk = MakeCredentialPublicKey(kty, alg, COSE.EllipticCurve.Ed25519, publicKey); - break; - } - default: - throw new ArgumentException(nameof(kty), $"Missing or unknown kty {kty}"); - } - return cpk; - } -} +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Text.Json; + +using Fido2NetLib; +using Fido2NetLib.Cbor; +using Fido2NetLib.Objects; + +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Internal; +using Microsoft.Extensions.Logging; + +using Moq; + +using NSec.Cryptography; + +using static Fido2NetLib.AuthenticatorAttestationResponse; + +namespace fido2_net_lib.Test; + +// todo: Create tests and name Facts and json files better. +public class Fido2Tests +{ + private static readonly IMetadataService _metadataService; + private static readonly Fido2Configuration _config; + public static readonly List<(COSE.KeyType, COSE.Algorithm, COSE.EllipticCurve)> _validCOSEParameters; + + static Fido2Tests() + { + var services = new ServiceCollection(); + + services.AddDistributedMemoryCache(); + services.AddMemoryCache(); + services.AddLogging(); + services.AddHttpClient(); + + var provider = services.BuildServiceProvider(); + + var distributedCache = provider.GetService(); + var memCache = provider.GetService(); + + var repos = new List + { + new Fido2MetadataServiceRepository(provider.GetService()) + }; + + IMetadataService service = new DistributedCacheMetadataService( + repos, + distributedCache, + memCache, + provider.GetService>(), + new SystemClock()); + + _metadataService = service; + + _config = new Fido2Configuration { Origins = new HashSet { "https://localhost:44329" } }; + + var noCurve = COSE.EllipticCurve.Reserved; + + _validCOSEParameters = + [ + new(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256), + new(COSE.KeyType.EC2, COSE.Algorithm.ES384, COSE.EllipticCurve.P384), + new(COSE.KeyType.EC2, COSE.Algorithm.ES512, COSE.EllipticCurve.P521), + new(COSE.KeyType.RSA, COSE.Algorithm.RS256, noCurve), + new(COSE.KeyType.RSA, COSE.Algorithm.RS384, noCurve), + new(COSE.KeyType.RSA, COSE.Algorithm.RS512, noCurve), + new(COSE.KeyType.RSA, COSE.Algorithm.PS256, noCurve), + new(COSE.KeyType.RSA, COSE.Algorithm.PS384, noCurve), + new(COSE.KeyType.RSA, COSE.Algorithm.PS512, noCurve), + new(COSE.KeyType.OKP, COSE.Algorithm.EdDSA, COSE.EllipticCurve.Ed25519), + new(COSE.KeyType.EC2, COSE.Algorithm.ES256K, COSE.EllipticCurve.P256K) + ]; + } + + private async Task GetAsync(string filename) + { + return JsonSerializer.Deserialize(await File.ReadAllTextAsync(filename)); + } + + public abstract class Attestation + { + public CborMap _attestationObject; + public CredentialPublicKey _credentialPublicKey; + public const string rp = "https://www.passwordless.dev"; + public byte[] _challenge; + public X500DistinguishedName rootDN = new("CN=Testing, O=FIDO2-NET-LIB, C=US"); + public Oid oidIdFidoGenCeAaGuid = new("1.3.6.1.4.1.45724.1.1.4"); + //private byte[] asnEncodedAaGuid = new byte[] { 0x04, 0x10, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; + //public byte[] asnEncodedAaGuid = new byte[] { 0x04, 0x10, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; + public byte[] _asnEncodedAaguid; + protected X509BasicConstraintsExtension caExt = new(true, true, 2, false); + protected X509BasicConstraintsExtension notCAExt = new(false, false, 0, false); + public X509Extension idFidoGenCeAaGuidExt; + + public byte[] _rpIdHash => SHA256.HashData(Encoding.UTF8.GetBytes(rp)); + + public byte[] _clientDataJson + { + get + { + return JsonSerializer.SerializeToUtf8Bytes(new + { + type = "webauthn.create", + challenge = _challenge, + origin = rp + }); + } + } + + public byte[] _clientDataHash => SHA256.HashData(_clientDataJson); + public byte[] _attToBeSigned => [.. _authData.ToByteArray(), .. _clientDataHash]; + + public byte[] _attToBeSignedHash(HashAlgorithmName alg) + { + return CryptoUtils.HashData(alg, _attToBeSigned); + } + + public byte[] _credentialID; + public const AuthenticatorFlags _flags = AuthenticatorFlags.AT | AuthenticatorFlags.ED | AuthenticatorFlags.UP | AuthenticatorFlags.UV; + public ushort _signCount; + protected Guid _aaguid = new("F1D0F1D0-F1D0-F1D0-F1D0-F1D0F1D0F1D0"); + public Extensions GetExtensions() + { + var extBytes = new CborMap { { "testing", true } }.Encode(); + return new Extensions(extBytes); + } + + public AuthenticatorData _authData => new(_rpIdHash, _flags, _signCount, _acd, GetExtensions()); + + public AttestedCredentialData _acd => new(_aaguid, _credentialID, _credentialPublicKey); + + public Attestation() + { + _credentialID = RandomNumberGenerator.GetBytes(16); + _challenge = RandomNumberGenerator.GetBytes(128); + + byte[] signCount = RandomNumberGenerator.GetBytes(2); + + _signCount = BitConverter.ToUInt16(signCount, 0); + + _attestationObject = new CborMap(); + + _asnEncodedAaguid = AsnHelper.GetAaguidBlob(_aaguid); + + idFidoGenCeAaGuidExt = new X509Extension(oidIdFidoGenCeAaGuid, _asnEncodedAaguid, false); + } + + public async Task MakeAttestationResponseAsync() + { + _attestationObject.Set("authData", new CborByteString(_authData.ToByteArray())); + + var attestationResponse = new AuthenticatorAttestationRawResponse + { + Type = PublicKeyCredentialType.PublicKey, + Id = [0xf1, 0xd0], + RawId = [0xf1, 0xd0], + Response = new AuthenticatorAttestationRawResponse.AttestationResponse + { + AttestationObject = _attestationObject.Encode(), + ClientDataJson = _clientDataJson, + Transports = [AuthenticatorTransport.Internal] + }, + ClientExtensionResults = new AuthenticationExtensionsClientOutputs() + { + AppID = true, + Extensions = ["foo", "bar"], + Example = "test", + UserVerificationMethod = new ulong[][] + { + new ulong[] + { + 4 // USER_VERIFY_PASSCODE_INTERNAL + }, + }, + } + }; + + var originalOptions = new CredentialCreateOptions + { + Attestation = AttestationConveyancePreference.Direct, + AuthenticatorSelection = new AuthenticatorSelection + { + AuthenticatorAttachment = AuthenticatorAttachment.CrossPlatform, + ResidentKey = ResidentKeyRequirement.Required, + UserVerification = UserVerificationRequirement.Discouraged, + }, + Challenge = _challenge, + PubKeyCredParams = new List() + { + new(COSE.Algorithm.ES256), + new(COSE.Algorithm.ES384), + new(COSE.Algorithm.ES512), + new(COSE.Algorithm.RS1), + new(COSE.Algorithm.RS256), + new(COSE.Algorithm.RS384), + new(COSE.Algorithm.RS512), + new(COSE.Algorithm.PS256), + new(COSE.Algorithm.PS384), + new(COSE.Algorithm.PS512), + new(COSE.Algorithm.EdDSA), + new(COSE.Algorithm.ES256K), + }, + Rp = new PublicKeyCredentialRpEntity(rp, rp, ""), + User = new Fido2User + { + Name = "testuser", + Id = "testuser"u8.ToArray(), + DisplayName = "Test User", + }, + Timeout = 60000, + }; + + IsCredentialIdUniqueToUserAsyncDelegate callback = (args, cancellationToken) => + { + return Task.FromResult(true); + }; + + var lib = new Fido2(new Fido2Configuration + { + ServerDomain = rp, + ServerName = rp, + Origins = new HashSet { rp }, + }); + + var credentialMakeResult = await lib.MakeNewCredentialAsync(attestationResponse, originalOptions, callback); + + return credentialMakeResult; + } + + internal byte[] SignData(COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve crv) + { + ECDsa ecdsa = null; + RSA rsa = null; + Key privateKey = null; + byte[] publicKey = null; + + switch (kty) + { + case COSE.KeyType.EC2: + { + ecdsa = MakeECDsa(alg, crv); + break; + } + case COSE.KeyType.RSA: + { + rsa = RSA.Create(); + break; + } + case COSE.KeyType.OKP: + { + MakeEdDSA(out var privateKeySeed, out publicKey, out byte[] expandedPrivateKey); + privateKey = Key.Import(SignatureAlgorithm.Ed25519, expandedPrivateKey, KeyBlobFormat.RawPrivateKey); + break; + } + default: + throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}"); + } + + return SignData(kty, alg, crv, ecdsa, rsa, privateKey, publicKey); + } + + internal byte[] SignData(COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve curve, ECDsa ecdsa = null, RSA rsa = null, Key expandedPrivateKey = null, byte[] publicKey = null) + { + switch (kty) + { + case COSE.KeyType.EC2: + { + var ecParams = ecdsa.ExportParameters(true); + _credentialPublicKey = MakeCredentialPublicKey(kty, alg, curve, ecParams.Q.X, ecParams.Q.Y); + var signature = ecdsa.SignData(_attToBeSigned, CryptoUtils.HashAlgFromCOSEAlg(alg)); + return SignatureHelper.EcDsaSigFromSig(signature, ecdsa.KeySize); + } + case COSE.KeyType.RSA: + { + RSASignaturePadding padding; + switch (alg) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.PS256: + case COSE.Algorithm.PS384: + case COSE.Algorithm.PS512: + padding = RSASignaturePadding.Pss; + break; + + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + default: + throw new ArgumentOutOfRangeException(nameof(alg), $"Missing or unknown alg {alg}"); + } + + var rsaParams = rsa.ExportParameters(true); + _credentialPublicKey = MakeCredentialPublicKey(kty, alg, rsaParams.Modulus, rsaParams.Exponent); + return rsa.SignData(_attToBeSigned, CryptoUtils.HashAlgFromCOSEAlg(alg), padding); + } + case COSE.KeyType.OKP: + { + _credentialPublicKey = MakeCredentialPublicKey(kty, alg, COSE.EllipticCurve.Ed25519, publicKey); + return SignatureAlgorithm.Ed25519.Sign(expandedPrivateKey, _attToBeSigned); + } + + default: + throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}"); + } + } + } + + internal static byte[] SignData(COSE.KeyType kty, COSE.Algorithm alg, byte[] data, ECDsa ecdsa = null, RSA rsa = null, byte[] expandedPrivateKey = null) + { + switch (kty) + { + case COSE.KeyType.EC2: + { + var signature = ecdsa.SignData(data, CryptoUtils.HashAlgFromCOSEAlg(alg)); + return SignatureHelper.EcDsaSigFromSig(signature, ecdsa.KeySize); + } + case COSE.KeyType.RSA: + { + RSASignaturePadding padding; + switch (alg) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + { + case COSE.Algorithm.PS256: + case COSE.Algorithm.PS384: + case COSE.Algorithm.PS512: + padding = RSASignaturePadding.Pss; + break; + + case COSE.Algorithm.RS1: + case COSE.Algorithm.RS256: + case COSE.Algorithm.RS384: + case COSE.Algorithm.RS512: + padding = RSASignaturePadding.Pkcs1; + break; + default: + throw new ArgumentOutOfRangeException(nameof(alg), $"Missing or unknown alg {alg}"); + } + return rsa.SignData(data, CryptoUtils.HashAlgFromCOSEAlg(alg), padding); + } + case COSE.KeyType.OKP: + { + Key privateKey = Key.Import(SignatureAlgorithm.Ed25519, expandedPrivateKey, KeyBlobFormat.RawPrivateKey); + return SignatureAlgorithm.Ed25519.Sign(privateKey, data); + } + + default: + throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}"); + } + } + + [Fact] + public void TestStringIsSerializable() + { + var x2 = new AuthenticatorSelection + { + UserVerification = UserVerificationRequirement.Discouraged + }; + + var json = JsonSerializer.Serialize(x2); + var c3 = JsonSerializer.Deserialize(json); + + Assert.Equal(UserVerificationRequirement.Discouraged, c3.UserVerification); + + Assert.NotEqual(UserVerificationRequirement.Required, c3.UserVerification); + + // Assert.True("discouraged" == UserVerificationRequirement.Discouraged); + // Assert.False("discouraged" != UserVerificationRequirement.Discouraged); + + Assert.False(UserVerificationRequirement.Required == UserVerificationRequirement.Discouraged); + Assert.True(UserVerificationRequirement.Required != UserVerificationRequirement.Discouraged); + + // testing where string and member name mismatch + + var y1 = AuthenticatorAttachment.CrossPlatform; + var yjson = JsonSerializer.Serialize(y1); + Assert.Equal("\"cross-platform\"", yjson); + + var y2 = JsonSerializer.Deserialize(yjson); + + Assert.Equal(AuthenticatorAttachment.CrossPlatform, y2); + + // test list of typed strings + var z1 = new[] { + AuthenticatorTransport.Ble, + AuthenticatorTransport.Usb, + AuthenticatorTransport.Nfc, + AuthenticatorTransport.Internal + }; + + var zjson = JsonSerializer.Serialize(z1); + var z2 = JsonSerializer.Deserialize(zjson); + + Assert.All(z2, (x) => z1.Contains(x)); + Assert.Equal(z1, z2); + } + + [Fact] + public async Task TestFido2AssertionAsync() + { + //var existingKey = "45-43-53-31-20-00-00-00-0E-B4-F3-73-C2-AC-7D-F7-7E-7D-17-D3-A3-A2-CC-AB-E5-C6-B1-42-ED-10-AC-7C-15-72-39-8D-75-C6-5B-B9-76-09-33-A0-30-F2-44-51-C8-31-AF-72-9B-4F-7B-AB-4F-85-2D-7D-1F-E0-B5-BD-A3-3D-0E-D6-18-04-CD-98"; + + //var key2 = "45-43-53-31-20-00-00-00-1D-60-44-D7-92-A0-0C-1E-3B-F9-58-5A-28-43-92-FD-F6-4F-BB-7F-8E-86-33-38-30-A4-30-5D-4E-2C-71-E3-53-3C-7B-98-81-99-FE-A9-DA-D9-24-8E-04-BD-C7-86-40-D3-03-1E-6E-00-81-7D-85-C3-A2-19-C9-21-85-8D"; + //var key2 = "45-43-53-31-20-00-00-00-A9-E9-12-2A-37-8A-F0-74-E7-BA-52-54-B0-91-55-46-DB-21-E5-2C-01-B8-FB-69-CD-E5-ED-02-B6-C3-16-E3-1A-59-16-C1-43-87-0D-04-B9-94-7F-CF-56-E5-AA-5E-96-8C-5B-27-8F-83-F4-E2-50-AB-B3-F6-28-A1-F8-9E"; + + var options = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationNoneOptions.json")); + 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); + + 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() { + new PublicKeyCredentialDescriptor(Convert.FromHexString(credId.Replace("-", ""))) + }; + + // assertion + + var aoptions = await GetAsync("./assertionNoneOptions.json"); + var aresponse = await GetAsync("./assertionNoneResponse.json"); + } + + [Fact] + public async Task TestAppleAppAttestDev() + { + var b64 = "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"; + var cbor = Convert.FromBase64String(b64); + var json = (CborMap)CborObject.Decode(cbor); + + var AttestationObject = new ParsedAttestationObject + ( + fmt: (string)json["fmt"], + attStmt: (CborMap)json["attStmt"], + authData: AuthenticatorData.Parse((byte[])json["authData"]) + ); + + var clientDataJson = SHA256.HashData(Encoding.UTF8.GetBytes("This is a test. This will need to be removed before merging.")); + + var verifier = new AppleAppAttest(); + var verifyResult = await verifier.VerifyAsync(AttestationObject.AttStmt, AttestationObject.AuthData, clientDataJson); + Assert.True(verifyResult.Type.Equals(AttestationType.Basic)); + } + + [Fact] + public async Task TestAppleAppAttestProd() + { + var b64 = "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"; + var cbor = Convert.FromBase64String(b64); + var json = (CborMap)CborObject.Decode(cbor); + + var AttestationObject = new ParsedAttestationObject + ( + fmt: (string)json["fmt"], + attStmt: (CborMap)json["attStmt"], + authData: AuthenticatorData.Parse((byte[])json["authData"]) + ); + + var clientDataJson = SHA256.HashData(Encoding.UTF8.GetBytes("1234567890abcdefgh")); + + var verifier = new AppleAppAttest(); + var ex = await Assert.ThrowsAsync(async () => _ = await verifier.VerifyAsync(AttestationObject.AttStmt, AttestationObject.AuthData, clientDataJson)); + + const string windowsErrorMessage = "Failed to build chain in Apple AppAttest attestation: A required certificate is not within its validity period when verifying against the current system clock or the timestamp in the signed file."; + const string cryptoKitErrorMessage = "Failed to build chain in Apple AppAttest attestation: An expired certificate was detected."; + const string linuxErrorMessage = "Failed to build chain in Apple AppAttest attestation: certificate has expired"; + + Assert.True(ex.Message is windowsErrorMessage or cryptoKitErrorMessage or linuxErrorMessage); + } + + [Fact] + public async Task TestParsingAsync() + { + var jsonPost = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./json1.json")); + var options = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./options1.json")); + + Assert.NotNull(jsonPost); + + var o = AuthenticatorAttestationResponse.Parse(jsonPost); + await o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), _metadataService, CancellationToken.None); + } + + [Fact] + public void MetadataBLOBPayloadEntry_Can_Be_JSON_Roundtripped() + { + var input = new MetadataBLOBPayloadEntry() + { + AaGuid = Guid.NewGuid(), + MetadataStatement = new MetadataStatement(), + StatusReports = Array.Empty(), + TimeOfLastStatusChange = DateTime.UtcNow.ToString("o") + }; + + input.MetadataStatement.AaGuid = Guid.NewGuid(); + input.MetadataStatement.Description = "Test entry"; + input.MetadataStatement.AuthenticatorVersion = 1; + input.MetadataStatement.Upv = new[] { new UafVersion(1, 0) }; + input.MetadataStatement.ProtocolFamily = "foo"; + input.MetadataStatement.AttestationTypes = ["bar"]; + input.MetadataStatement.AuthenticationAlgorithms = ["alg0", "alg1"]; + input.MetadataStatement.PublicKeyAlgAndEncodings = ["example0", "example1"]; + input.MetadataStatement.TcDisplay = ["transaction", "confirmation"]; + input.MetadataStatement.KeyProtection = ["protector"]; + input.MetadataStatement.MatcherProtection = ["stuff", "things"]; + input.MetadataStatement.UserVerificationDetails = Array.Empty(); + input.MetadataStatement.AttestationRootCertificates = ["..."]; + + var json = JsonSerializer.Serialize(input); + + var output = JsonSerializer.Deserialize(json); + + Assert.Equal(input.AaGuid, output.AaGuid); + } + + [Fact] + public void TestAuthenticatorDataPa2rsing() + { + var bs = new byte[] { 1, 2, 3 }; + var x = new CborMap { { "bytes", bs } }; + var s = (byte[])x["bytes"]; + + Assert.Equal(s, bs); + } + + [Fact] + 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); + } + + [Fact] + public async Task TestPackedAttestationAsync() + { + var jsonPost = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationResultsPacked.json")); + 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); + var authData = o.AttestationObject.AuthData; + var acdBytes = authData.AttestedCredentialData.ToByteArray(); + var acd = AttestedCredentialData.Parse(acdBytes); + Assert.Equal(acd.ToByteArray(), acdBytes); + } + + [Fact] + public async Task TestNoneAttestationAsync() + { + var jsonPost = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationResultsNone.json")); + 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); + } + + [Fact] + 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); + } + + [Fact] + public async Task TestTPMSHA1AttestationAsync() + { + var jsonPost = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationTPMSHA1Response.json")); + 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); + } + + [Fact] + 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); + } + + [Fact] + 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); + } + + [Fact] + 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); + var authData = o.AttestationObject.AuthData; + var acdBytes = authData.AttestedCredentialData.ToByteArray(); + var acd = AttestedCredentialData.Parse(acdBytes); + Assert.Equal(acd.ToByteArray(), acdBytes); + } + + [Fact] + public async Task TestInvalidU2FAttestationAsync() + { + // TODO: Figure out why this test fails on macOS and Linux + if (!OperatingSystem.IsWindows()) + return; + + 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); + var authData = o.AttestationObject.AuthData; + var acdBytes = authData.AttestedCredentialData.ToByteArray(); + var acd = AttestedCredentialData.Parse(acdBytes); + Assert.Equal(acd.ToByteArray(), acdBytes); + } + + [Fact] + public async Task TestMdsStatusReportsSuccessAsync() + { + var options = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationNoneOptions.json")); + var response = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationNoneResponse.json")); + + var mockMetadataService = new Mock(MockBehavior.Strict); + mockMetadataService.Setup(m => m.GetEntryAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new MetadataBLOBPayloadEntry() + { + StatusReports = + [ + new StatusReport { Status = AuthenticatorStatus.FIDO_CERTIFIED } + ] + }); + 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); + } + + [Fact] + public async Task TestMdsStatusReportsUndesiredAsync() + { + var options = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationNoneOptions.json")); + var response = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationNoneResponse.json")); + + var mockMetadataService = new Mock(MockBehavior.Strict); + mockMetadataService.Setup(m => m.GetEntryAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new MetadataBLOBPayloadEntry() + { + StatusReports = + [ + new StatusReport { Status = AuthenticatorStatus.FIDO_CERTIFIED }, + new StatusReport { Status = AuthenticatorStatus.REVOKED } + ] + }); + mockMetadataService.Setup(m => m.ConformanceTesting()).Returns(false); + + var o = AuthenticatorAttestationResponse.Parse(response); + await Assert.ThrowsAsync(() => + o.VerifyAsync(options, _config, (x, cancellationToken) => Task.FromResult(true), mockMetadataService.Object, CancellationToken.None)); + } + + [Fact] + public async Task TestMdsStatusReportsUndesiredFixedAsync() + { + var options = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationNoneOptions.json")); + var response = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationNoneResponse.json")); + + var mockMetadataService = new Mock(MockBehavior.Strict); + mockMetadataService.Setup(m => m.GetEntryAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new MetadataBLOBPayloadEntry() + { + StatusReports = new StatusReport[] + { + new StatusReport() { Status = AuthenticatorStatus.FIDO_CERTIFIED }, + new StatusReport() { Status = AuthenticatorStatus.REVOKED }, + new StatusReport() { Status = AuthenticatorStatus.UPDATE_AVAILABLE } + } + }); + 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); + } + + [Fact] + public async Task TestMdsStatusReportsNullAsync() + { + var options = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationNoneOptions.json")); + var response = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./attestationNoneResponse.json")); + + var mockMetadataService = new Mock(MockBehavior.Strict); + mockMetadataService.Setup(m => m.GetEntryAsync(It.IsAny(), It.IsAny())).ReturnsAsync((MetadataBLOBPayloadEntry)null); + 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); + } + + //public void TestHasCorrentAAguid() + //{ + // var expectedAaguid = new Uint8Array([ + // 0x42, 0x38, 0x32, 0x45, 0x44, 0x37, 0x33, 0x43, 0x38, 0x46, 0x42, 0x34, 0x45, 0x35, 0x41, 0x32 + //]).buffer; + //} + [Fact] + public void TestAttestedCredentialDataES256() + { + var aaguid = new Guid("F1D0F1D0-F1D0-F1D0-F1D0-F1D0F1D0F1D0"); + var credentialID = new byte[] { 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; + var ecdsa = MakeECDsa(COSE.Algorithm.ES256, COSE.EllipticCurve.P256); + var ecParams = ecdsa.ExportParameters(true); + var cpk = MakeCredentialPublicKey(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecParams.Q.X, ecParams.Q.Y); + + var acdFromConst = new AttestedCredentialData(aaguid, credentialID, cpk); + var acdBytes = acdFromConst.ToByteArray(); + var acdFromBytes = AttestedCredentialData.Parse(acdBytes); + Assert.Equal(acdFromBytes.ToByteArray(), acdFromConst.ToByteArray()); + } + + [Fact] + public void TestAttestedCredentialDataRSA() + { + var aaguid = new Guid("F1D0F1D0-F1D0-F1D0-F1D0-F1D0F1D0F1D0"); + var credentialID = new byte[] { 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; + var rsa = RSA.Create(); + var rsaParams = rsa.ExportParameters(true); + var cpk = MakeCredentialPublicKey(COSE.KeyType.RSA, COSE.Algorithm.RS256, rsaParams.Modulus, rsaParams.Exponent); + + var acdFromConst = new AttestedCredentialData(aaguid, credentialID, cpk); + var acdBytes = acdFromConst.ToByteArray(); + var acdFromBytes = AttestedCredentialData.Parse(acdBytes); + Assert.Equal(acdFromBytes.ToByteArray(), acdFromConst.ToByteArray()); + + var sig = SignData(COSE.KeyType.RSA, COSE.Algorithm.RS256, acdBytes, null, rsa, null); + + Assert.True(cpk.Verify(acdBytes, sig)); + sig[^1] ^= 0xff; + Assert.False(cpk.Verify(acdBytes, sig)); + } + + [Fact] + public void TestAttestedCredentialDataOKP() + { + var aaGuid = new Guid("F1D0F1D0-F1D0-F1D0-F1D0-F1D0F1D0F1D0"); + var credentialID = new byte[] { 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; + MakeEdDSA(out _, out var publicKey, out var privateKey); + var cpk = MakeCredentialPublicKey(COSE.KeyType.OKP, COSE.Algorithm.EdDSA, COSE.EllipticCurve.Ed25519, publicKey); + + var acdFromConst = new AttestedCredentialData(aaGuid, credentialID, cpk); + var acdBytes = acdFromConst.ToByteArray(); + var acdFromBytes = AttestedCredentialData.Parse(acdBytes); + Assert.Equal(acdFromBytes.ToByteArray(), acdFromConst.ToByteArray()); + + var sig = SignData(COSE.KeyType.OKP, COSE.Algorithm.EdDSA, acdBytes, null, null, privateKey); + + Assert.True(cpk.Verify(acdBytes, sig)); + sig[^1] ^= 0xff; + Assert.False(cpk.Verify(acdBytes, sig)); + } + + [Fact] + public void TestAuthenticatorData() + { + var rpId = "fido2.azurewebsites.net/"u8; + var rpIdHash = SHA256.HashData(rpId); + var flags = AuthenticatorFlags.AT | AuthenticatorFlags.ED | AuthenticatorFlags.UP | AuthenticatorFlags.UV; + const ushort signCount = 0xf1d0; + var aaGuid = new Guid("F1D0F1D0-F1D0-F1D0-F1D0-F1D0F1D0F1D0"); + var credentialID = new byte[] { 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; + var ecdsa = MakeECDsa(COSE.Algorithm.ES256, COSE.EllipticCurve.P256); + var ecParams = ecdsa.ExportParameters(true); + var cpk = MakeCredentialPublicKey(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecParams.Q.X, ecParams.Q.Y); + + var acd = new AttestedCredentialData(aaGuid, credentialID, cpk); + var extBytes = new CborMap { { "testing", true } }.Encode(); + var exts = new Extensions(extBytes); + + var ad = new AuthenticatorData(rpIdHash, flags, signCount, acd, exts); + Assert.Equal(rpIdHash, ad.RpIdHash); + Assert.True(ad.HasAttestedCredentialData | ad.UserPresent | ad.UserVerified | ad.HasExtensionsData); + Assert.Equal(signCount, ad.SignCount); + Assert.Equal(ad.AttestedCredentialData.ToByteArray(), acd.ToByteArray()); + Assert.Equal(extBytes, ad.Extensions.GetBytes()); + } + + [Fact] + public async Task TestAssertionResponse() + { + VerifyAssertionResult avr; + foreach (var (type, alg, curve) in _validCOSEParameters) + { + // No support for P256K on OSX + if (OperatingSystem.IsMacOS() && curve is COSE.EllipticCurve.P256K) + return; + + if (curve != default) + { + avr = await MakeAssertionResponseAsync(type, alg, curve); + } + else + { + avr = await MakeAssertionResponseAsync(type, alg); + } + + Assert.Equal([0xf1, 0xd0], avr.CredentialId); + Assert.Equal("1", avr.SignCount.ToString("X")); + } + } + + internal static async Task MakeAssertionResponseAsync( + COSE.KeyType kty, + COSE.Algorithm alg, + COSE.EllipticCurve crv = COSE.EllipticCurve.P256, + CredentialPublicKey cpk = null, + ushort signCount = 0, + ECDsa ecdsa = null, + RSA rsa = null, + byte[] expandedPrivateKey = null) + { + const string rp = "https://www.passwordless.dev"; + byte[] rpId = Encoding.UTF8.GetBytes(rp); + var rpIdHash = SHA256.HashData(rpId); + var flags = AuthenticatorFlags.AT | AuthenticatorFlags.ED | AuthenticatorFlags.UP | AuthenticatorFlags.UV; + var aaGuid = new Guid("F1D0F1D0-F1D0-F1D0-F1D0-F1D0F1D0F1D0"); + var credentialId = new byte[] { 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; + if (cpk == null) + { + switch (kty) + { + case COSE.KeyType.EC2: + { + ecdsa ??= MakeECDsa(alg, crv); + + var ecParams = ecdsa.ExportParameters(true); + cpk = MakeCredentialPublicKey(kty, alg, crv, ecParams.Q.X, ecParams.Q.Y); + break; + } + case COSE.KeyType.RSA: + { + rsa ??= RSA.Create(); + + var rsaParams = rsa.ExportParameters(true); + cpk = MakeCredentialPublicKey(kty, alg, rsaParams.Modulus, rsaParams.Exponent); + break; + } + case COSE.KeyType.OKP: + { + byte[] publicKey = null; + if (expandedPrivateKey == null) + { + MakeEdDSA(out var privateKeySeed, out publicKey, out expandedPrivateKey); + } + + cpk = MakeCredentialPublicKey(kty, alg, COSE.EllipticCurve.Ed25519, publicKey); + break; + } + default: + throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}"); + } + } + var acd = new AttestedCredentialData(aaGuid, credentialId, cpk); + var extBytes = new CborMap { { "testing", true } }.Encode(); + var exts = new Extensions(extBytes); + + var ad = new AuthenticatorData(rpIdHash, flags, (uint)(signCount + 1), acd, exts); + var authData = ad.ToByteArray(); + + var challenge = new byte[128]; + RandomNumberGenerator.Fill(challenge); + + var clientData = new + { + type = "webauthn.get", + challenge = challenge, + origin = rp, + }; + var clientDataJson = JsonSerializer.SerializeToUtf8Bytes(clientData); + + var hashedClientDataJson = SHA256.HashData(clientDataJson); + byte[] data = [.. authData, .. hashedClientDataJson]; + byte[] signature = SignData(kty, alg, data, ecdsa, rsa, expandedPrivateKey); + + var userHandle = new byte[16]; + RandomNumberGenerator.Fill(userHandle); + + var assertion = new AuthenticatorAssertionRawResponse.AssertionResponse() + { + AuthenticatorData = authData, + Signature = signature, + ClientDataJson = clientDataJson, + UserHandle = userHandle, + }; + + var lib = new Fido2(new Fido2Configuration + { + ServerDomain = rp, + ServerName = rp, + Origins = new HashSet { rp }, + }); + var existingCredentials = new List(); + var cred = new PublicKeyCredentialDescriptor([0xf1, 0xd0]); + existingCredentials.Add(cred); + + var options = lib.GetAssertionOptions(existingCredentials, null, null); + options.Challenge = challenge; + var response = new AuthenticatorAssertionRawResponse() + { + Response = assertion, + Type = PublicKeyCredentialType.PublicKey, + Id = [0xf1, 0xd0], + RawId = [0xf1, 0xd0], + }; + IsUserHandleOwnerOfCredentialIdAsync callback = (args, cancellationToken) => + { + return Task.FromResult(true); + }; + return await lib.MakeAssertionAsync(response, options, cpk.GetBytes(), null, signCount, callback); + } + + internal static void MakeEdDSA(out byte[] privateKeySeed, out byte[] publicKey, out byte[] expandedPrivateKey) + { + privateKeySeed = new byte[32]; + RandomNumberGenerator.Fill(privateKeySeed); + var key = Key.Create(SignatureAlgorithm.Ed25519, new KeyCreationParameters() { ExportPolicy = KeyExportPolicies.AllowPlaintextExport }); + expandedPrivateKey = key.Export(KeyBlobFormat.RawPrivateKey); + publicKey = key.Export(KeyBlobFormat.RawPublicKey); + } + + internal static ECDsa MakeECDsa(COSE.Algorithm alg, COSE.EllipticCurve crv) + { + ECCurve curve; + switch (alg) + { + case COSE.Algorithm.ES256K: + switch (crv) + { + case COSE.EllipticCurve.P256K: + if (OperatingSystem.IsMacOS()) + { + // see https://github.com/dotnet/runtime/issues/47770 + throw new PlatformNotSupportedException($"No support currently for secP256k1 on macOS"); + } + curve = ECCurve.CreateFromFriendlyName("secP256k1"); + break; + default: + throw new ArgumentOutOfRangeException(nameof(crv), $"Missing or unknown crv {crv}"); + } + break; + case COSE.Algorithm.ES256: + curve = crv switch + { + COSE.EllipticCurve.P256 => ECCurve.NamedCurves.nistP256, + _ => throw new ArgumentOutOfRangeException(nameof(crv), $"Missing or unknown crv {crv}"), + }; + break; + case COSE.Algorithm.ES384: + curve = crv switch // https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves + { + COSE.EllipticCurve.P384 => ECCurve.NamedCurves.nistP384, + _ => throw new ArgumentOutOfRangeException(nameof(crv), $"Missing or unknown crv {crv}"), + }; + break; + case COSE.Algorithm.ES512: + curve = crv switch // https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves + { + COSE.EllipticCurve.P521 => ECCurve.NamedCurves.nistP521, + _ => throw new ArgumentOutOfRangeException(nameof(crv), $"Missing or unknown crv {crv}"), + }; + break; + default: + throw new ArgumentOutOfRangeException(nameof(alg), $"Missing or unknown alg {alg}"); + } + return ECDsa.Create(curve); + } + + internal static CredentialPublicKey MakeCredentialPublicKey(COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve crv, byte[] x, byte[] y) + { + return MakeCredentialPublicKey(kty, alg, crv, x, y, null, null); + } + + internal static CredentialPublicKey MakeCredentialPublicKey(COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve crv, byte[] x) + { + return MakeCredentialPublicKey(kty, alg, crv, x, null, null, null); + } + + internal static CredentialPublicKey MakeCredentialPublicKey(COSE.KeyType kty, COSE.Algorithm alg, byte[] n, byte[] e) + { + return MakeCredentialPublicKey(kty, alg, null, null, null, n, e); + } + + internal static CredentialPublicKey MakeCredentialPublicKey(COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve? crv, byte[] x, byte[] y, byte[] n, byte[] e) + { + var cpk = new CborMap + { + { COSE.KeyCommonParameter.KeyType, kty }, + { COSE.KeyCommonParameter.Alg, alg } + }; + + switch (kty) + { + case COSE.KeyType.EC2: + cpk.Add(COSE.KeyTypeParameter.X, x); + cpk.Add(COSE.KeyTypeParameter.Y, y); + cpk.Add((int)COSE.KeyTypeParameter.Crv, (int)crv); + break; + case COSE.KeyType.RSA: + cpk.Add(COSE.KeyTypeParameter.N, n); + cpk.Add(COSE.KeyTypeParameter.E, e); + break; + case COSE.KeyType.OKP: + cpk.Add(COSE.KeyTypeParameter.X, x); + cpk.Add((int)COSE.KeyTypeParameter.Crv, (int)crv); + break; + default: + throw new ArgumentOutOfRangeException(nameof(kty), kty, "Invalid COSE key type"); + } + return new CredentialPublicKey(cpk); + } + + internal static CredentialPublicKey MakeCredentialPublicKey((COSE.KeyType, COSE.Algorithm, COSE.EllipticCurve) param) + { + var (kty, alg, crv) = param; + + CredentialPublicKey cpk; + switch (kty) + { + case COSE.KeyType.EC2: + { + var ecdsa = MakeECDsa(alg, crv); + var ecParams = ecdsa.ExportParameters(true); + cpk = MakeCredentialPublicKey(kty, alg, crv, ecParams.Q.X, ecParams.Q.Y); + break; + } + case COSE.KeyType.RSA: + { + var rsa = RSA.Create(); + var rsaParams = rsa.ExportParameters(true); + cpk = MakeCredentialPublicKey(kty, alg, rsaParams.Modulus, rsaParams.Exponent); + break; + } + case COSE.KeyType.OKP: + { + MakeEdDSA(out var privateKeySeed, out byte[] publicKey, out _); + cpk = MakeCredentialPublicKey(kty, alg, COSE.EllipticCurve.Ed25519, publicKey); + break; + } + default: + throw new ArgumentException(nameof(kty), $"Missing or unknown kty {kty}"); + } + return cpk; + } +} diff --git a/Test/MetadataServiceTests.cs b/Tests/Fido2.Tests/MetadataServiceTests.cs similarity index 96% rename from Test/MetadataServiceTests.cs rename to Tests/Fido2.Tests/MetadataServiceTests.cs index 3595b726..6fcf08a7 100644 --- a/Test/MetadataServiceTests.cs +++ b/Tests/Fido2.Tests/MetadataServiceTests.cs @@ -1,178 +1,178 @@ -using Fido2NetLib; - -using Microsoft.Extensions.Caching.Distributed; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Internal; -using Microsoft.Extensions.Logging; - -namespace Test; - -public class MetadataServiceTests -{ - [Fact] - public async Task ConformanceTestClient() - { - var client = new ConformanceMetadataRepository(null, "http://localhost:80"); - - var cancellationToken = CancellationToken.None; - - var blob = await client.GetBLOBAsync(cancellationToken); - - Assert.NotEmpty(blob.Entries); - - var entry_1 = await client.GetMetadataStatementAsync(blob, blob.Entries[^1], cancellationToken); - - Assert.NotNull(entry_1.Description); - } - - public class MockRepository : IMetadataRepository - { - public int GetBLOBAsyncCount { get; private set; } - - private string _nextUpdate; - private int _number; - - public string NextUpdate - { - set - { - _nextUpdate = value; - _number++; - } - get - { - return _nextUpdate; - } - } - - public MockRepository(string nextUpdate) - { - _nextUpdate = nextUpdate; - _number = 1; - } - - public Task GetBLOBAsync(CancellationToken cancellationToken = default) - { - GetBLOBAsyncCount++; - - var payload = new MetadataBLOBPayload - { - NextUpdate = NextUpdate, - Number = _number, - Entries = - [ - new MetadataBLOBPayloadEntry - { - AaGuid = Guid.Parse("6d44ba9b-f6ec-2e49-b930-0c8fe920cb73"), - MetadataStatement = new MetadataStatement - { - Description = "Security Key by Yubico with NFC" - } - } - ] - }; - - return Task.FromResult(payload); - - } - - public Task GetMetadataStatementAsync(MetadataBLOBPayload blob, MetadataBLOBPayloadEntry entry, CancellationToken cancellationToken = default) - { - return Task.FromResult(entry.MetadataStatement); - } - } - - public class MockClock(DateTimeOffset time) : ISystemClock - { - public DateTimeOffset UtcNow { get; set; } = time; - } - - [Fact] - public async Task DistributeCacheMetadataService_Cache_Rollover_Works() - { - var nextUpdateTime = DateTimeOffset.Parse("2021-12-01T00:00:00Z"); - var currentTime = DateTimeOffset.Parse("2021-11-30T00:00:00Z"); - - var services = new ServiceCollection(); - - var staticClient = new MockRepository(nextUpdateTime.ToString("yyyy-MM-dd")); - - var repositories = new List(); - - var currentTimeClock = new MockClock(currentTime); - - repositories.Add(staticClient); - - services.AddDistributedMemoryCache(options => - { - options.Clock = currentTimeClock; - }); - services.AddMemoryCache(options => - { - options.Clock = currentTimeClock; - }); - services.AddLogging(); - - var provider = services.BuildServiceProvider(); - - var distributedCache = provider.GetService(); - var memCache = provider.GetService(); - - var serviceInstance1 = new DistributedCacheMetadataService( - repositories, - distributedCache, - memCache, - provider.GetService>(), - currentTimeClock - ); - - var entryIdGuid = Guid.Parse("6d44ba9b-f6ec-2e49-b930-0c8fe920cb73"); - - var entry = await serviceInstance1.GetEntryAsync(entryIdGuid); - - for (int x = 0; x < 10; x++) - { - await serviceInstance1.GetEntryAsync(entryIdGuid); - } - - Assert.Equal(1, staticClient.GetBLOBAsyncCount); - - Assert.Equal("Security Key by Yubico with NFC", entry.MetadataStatement.Description); - - var blobEntry = await distributedCache.GetStringAsync("DistributedCacheMetadataService:V2:" + staticClient.GetType().Name + ":TOC"); - - var itemEntry = memCache.Get($"DistributedCacheMetadataService:V2:{entryIdGuid}"); - - Assert.NotNull(blobEntry); - - Assert.Equal(itemEntry.AaGuid, entryIdGuid); - - currentTimeClock.UtcNow = DateTimeOffset.Parse("2021-11-30 23:59:59.999Z"); //Before next update - - await serviceInstance1.GetEntryAsync(entryIdGuid); - - Assert.Equal(1, staticClient.GetBLOBAsyncCount); - - currentTimeClock.UtcNow = DateTimeOffset.Parse("2021-12-02 00:59:59.999Z"); //Before buffer period (25 hours) - - await serviceInstance1.GetEntryAsync(entryIdGuid); - await serviceInstance1.GetEntryAsync(entryIdGuid); - - Assert.Equal(1, staticClient.GetBLOBAsyncCount); - - currentTimeClock.UtcNow = DateTimeOffset.Parse("2021-12-02 01:00:00.001Z"); //After buffer period (25 hours) - - staticClient.NextUpdate = "2021-12-30"; - - await serviceInstance1.GetEntryAsync(entryIdGuid); - - Assert.Equal(2, staticClient.GetBLOBAsyncCount); - - currentTimeClock.UtcNow = DateTimeOffset.Parse("2021-12-29 01:00:00.001Z"); - - await serviceInstance1.GetEntryAsync(entryIdGuid); - - Assert.Equal(2, staticClient.GetBLOBAsyncCount); - } -} +using Fido2NetLib; + +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Internal; +using Microsoft.Extensions.Logging; + +namespace Test; + +public class MetadataServiceTests +{ + [Fact] + public async Task ConformanceTestClient() + { + var client = new ConformanceMetadataRepository(null, "http://localhost:80"); + + var cancellationToken = CancellationToken.None; + + var blob = await client.GetBLOBAsync(cancellationToken); + + Assert.NotEmpty(blob.Entries); + + var entry_1 = await client.GetMetadataStatementAsync(blob, blob.Entries[^1], cancellationToken); + + Assert.NotNull(entry_1.Description); + } + + public class MockRepository : IMetadataRepository + { + public int GetBLOBAsyncCount { get; private set; } + + private string _nextUpdate; + private int _number; + + public string NextUpdate + { + set + { + _nextUpdate = value; + _number++; + } + get + { + return _nextUpdate; + } + } + + public MockRepository(string nextUpdate) + { + _nextUpdate = nextUpdate; + _number = 1; + } + + public Task GetBLOBAsync(CancellationToken cancellationToken = default) + { + GetBLOBAsyncCount++; + + var payload = new MetadataBLOBPayload + { + NextUpdate = NextUpdate, + Number = _number, + Entries = + [ + new MetadataBLOBPayloadEntry + { + AaGuid = Guid.Parse("6d44ba9b-f6ec-2e49-b930-0c8fe920cb73"), + MetadataStatement = new MetadataStatement + { + Description = "Security Key by Yubico with NFC" + } + } + ] + }; + + return Task.FromResult(payload); + + } + + public Task GetMetadataStatementAsync(MetadataBLOBPayload blob, MetadataBLOBPayloadEntry entry, CancellationToken cancellationToken = default) + { + return Task.FromResult(entry.MetadataStatement); + } + } + + public class MockClock(DateTimeOffset time) : ISystemClock + { + public DateTimeOffset UtcNow { get; set; } = time; + } + + [Fact] + public async Task DistributeCacheMetadataService_Cache_Rollover_Works() + { + var nextUpdateTime = DateTimeOffset.Parse("2021-12-01T00:00:00Z"); + var currentTime = DateTimeOffset.Parse("2021-11-30T00:00:00Z"); + + var services = new ServiceCollection(); + + var staticClient = new MockRepository(nextUpdateTime.ToString("yyyy-MM-dd")); + + var repositories = new List(); + + var currentTimeClock = new MockClock(currentTime); + + repositories.Add(staticClient); + + services.AddDistributedMemoryCache(options => + { + options.Clock = currentTimeClock; + }); + services.AddMemoryCache(options => + { + options.Clock = currentTimeClock; + }); + services.AddLogging(); + + var provider = services.BuildServiceProvider(); + + var distributedCache = provider.GetService(); + var memCache = provider.GetService(); + + var serviceInstance1 = new DistributedCacheMetadataService( + repositories, + distributedCache, + memCache, + provider.GetService>(), + currentTimeClock + ); + + var entryIdGuid = Guid.Parse("6d44ba9b-f6ec-2e49-b930-0c8fe920cb73"); + + var entry = await serviceInstance1.GetEntryAsync(entryIdGuid); + + for (int x = 0; x < 10; x++) + { + await serviceInstance1.GetEntryAsync(entryIdGuid); + } + + Assert.Equal(1, staticClient.GetBLOBAsyncCount); + + Assert.Equal("Security Key by Yubico with NFC", entry.MetadataStatement.Description); + + var blobEntry = await distributedCache.GetStringAsync("DistributedCacheMetadataService:V2:" + staticClient.GetType().Name + ":TOC"); + + var itemEntry = memCache.Get($"DistributedCacheMetadataService:V2:{entryIdGuid}"); + + Assert.NotNull(blobEntry); + + Assert.Equal(itemEntry.AaGuid, entryIdGuid); + + currentTimeClock.UtcNow = DateTimeOffset.Parse("2021-11-30 23:59:59.999Z"); //Before next update + + await serviceInstance1.GetEntryAsync(entryIdGuid); + + Assert.Equal(1, staticClient.GetBLOBAsyncCount); + + currentTimeClock.UtcNow = DateTimeOffset.Parse("2021-12-02 00:59:59.999Z"); //Before buffer period (25 hours) + + await serviceInstance1.GetEntryAsync(entryIdGuid); + await serviceInstance1.GetEntryAsync(entryIdGuid); + + Assert.Equal(1, staticClient.GetBLOBAsyncCount); + + currentTimeClock.UtcNow = DateTimeOffset.Parse("2021-12-02 01:00:00.001Z"); //After buffer period (25 hours) + + staticClient.NextUpdate = "2021-12-30"; + + await serviceInstance1.GetEntryAsync(entryIdGuid); + + Assert.Equal(2, staticClient.GetBLOBAsyncCount); + + currentTimeClock.UtcNow = DateTimeOffset.Parse("2021-12-29 01:00:00.001Z"); + + await serviceInstance1.GetEntryAsync(entryIdGuid); + + Assert.Equal(2, staticClient.GetBLOBAsyncCount); + } +} diff --git a/Test/PubKeyCredParamTests.cs b/Tests/Fido2.Tests/PubKeyCredParamTests.cs similarity index 100% rename from Test/PubKeyCredParamTests.cs rename to Tests/Fido2.Tests/PubKeyCredParamTests.cs diff --git a/Test/TestFiles/assertionNoneOptions.json b/Tests/Fido2.Tests/TestFiles/assertionNoneOptions.json similarity index 100% rename from Test/TestFiles/assertionNoneOptions.json rename to Tests/Fido2.Tests/TestFiles/assertionNoneOptions.json diff --git a/Test/TestFiles/assertionNoneResponse.json b/Tests/Fido2.Tests/TestFiles/assertionNoneResponse.json similarity index 100% rename from Test/TestFiles/assertionNoneResponse.json rename to Tests/Fido2.Tests/TestFiles/assertionNoneResponse.json diff --git a/Test/TestFiles/attestationAndroidKeyOptions.json b/Tests/Fido2.Tests/TestFiles/attestationAndroidKeyOptions.json similarity index 100% rename from Test/TestFiles/attestationAndroidKeyOptions.json rename to Tests/Fido2.Tests/TestFiles/attestationAndroidKeyOptions.json diff --git a/Test/TestFiles/attestationAndroidKeyResponse.json b/Tests/Fido2.Tests/TestFiles/attestationAndroidKeyResponse.json similarity index 100% rename from Test/TestFiles/attestationAndroidKeyResponse.json rename to Tests/Fido2.Tests/TestFiles/attestationAndroidKeyResponse.json diff --git a/Test/TestFiles/attestationAppleOptions.json b/Tests/Fido2.Tests/TestFiles/attestationAppleOptions.json similarity index 100% rename from Test/TestFiles/attestationAppleOptions.json rename to Tests/Fido2.Tests/TestFiles/attestationAppleOptions.json diff --git a/Test/TestFiles/attestationAppleResponse.json b/Tests/Fido2.Tests/TestFiles/attestationAppleResponse.json similarity index 100% rename from Test/TestFiles/attestationAppleResponse.json rename to Tests/Fido2.Tests/TestFiles/attestationAppleResponse.json diff --git a/Test/TestFiles/attestationNoneOptions.json b/Tests/Fido2.Tests/TestFiles/attestationNoneOptions.json similarity index 100% rename from Test/TestFiles/attestationNoneOptions.json rename to Tests/Fido2.Tests/TestFiles/attestationNoneOptions.json diff --git a/Test/TestFiles/attestationNoneResponse.json b/Tests/Fido2.Tests/TestFiles/attestationNoneResponse.json similarity index 100% rename from Test/TestFiles/attestationNoneResponse.json rename to Tests/Fido2.Tests/TestFiles/attestationNoneResponse.json diff --git a/Test/TestFiles/attestationOptionsATKey.json b/Tests/Fido2.Tests/TestFiles/attestationOptionsATKey.json similarity index 100% rename from Test/TestFiles/attestationOptionsATKey.json rename to Tests/Fido2.Tests/TestFiles/attestationOptionsATKey.json diff --git a/Test/TestFiles/attestationOptionsNone.json b/Tests/Fido2.Tests/TestFiles/attestationOptionsNone.json similarity index 100% rename from Test/TestFiles/attestationOptionsNone.json rename to Tests/Fido2.Tests/TestFiles/attestationOptionsNone.json diff --git a/Test/TestFiles/attestationOptionsPacked.json b/Tests/Fido2.Tests/TestFiles/attestationOptionsPacked.json similarity index 100% rename from Test/TestFiles/attestationOptionsPacked.json rename to Tests/Fido2.Tests/TestFiles/attestationOptionsPacked.json diff --git a/Test/TestFiles/attestationOptionsPacked512.json b/Tests/Fido2.Tests/TestFiles/attestationOptionsPacked512.json similarity index 100% rename from Test/TestFiles/attestationOptionsPacked512.json rename to Tests/Fido2.Tests/TestFiles/attestationOptionsPacked512.json diff --git a/Test/TestFiles/attestationOptionsTrustKeyT110.json b/Tests/Fido2.Tests/TestFiles/attestationOptionsTrustKeyT110.json similarity index 100% rename from Test/TestFiles/attestationOptionsTrustKeyT110.json rename to Tests/Fido2.Tests/TestFiles/attestationOptionsTrustKeyT110.json diff --git a/Test/TestFiles/attestationOptionsU2F.json b/Tests/Fido2.Tests/TestFiles/attestationOptionsU2F.json similarity index 100% rename from Test/TestFiles/attestationOptionsU2F.json rename to Tests/Fido2.Tests/TestFiles/attestationOptionsU2F.json diff --git a/Test/TestFiles/attestationResultTrustKeyT110.json b/Tests/Fido2.Tests/TestFiles/attestationResultTrustKeyT110.json similarity index 100% rename from Test/TestFiles/attestationResultTrustKeyT110.json rename to Tests/Fido2.Tests/TestFiles/attestationResultTrustKeyT110.json diff --git a/Test/TestFiles/attestationResultsATKey.json b/Tests/Fido2.Tests/TestFiles/attestationResultsATKey.json similarity index 100% rename from Test/TestFiles/attestationResultsATKey.json rename to Tests/Fido2.Tests/TestFiles/attestationResultsATKey.json diff --git a/Test/TestFiles/attestationResultsNone.json b/Tests/Fido2.Tests/TestFiles/attestationResultsNone.json similarity index 100% rename from Test/TestFiles/attestationResultsNone.json rename to Tests/Fido2.Tests/TestFiles/attestationResultsNone.json diff --git a/Test/TestFiles/attestationResultsPacked.json b/Tests/Fido2.Tests/TestFiles/attestationResultsPacked.json similarity index 100% rename from Test/TestFiles/attestationResultsPacked.json rename to Tests/Fido2.Tests/TestFiles/attestationResultsPacked.json diff --git a/Test/TestFiles/attestationResultsPacked512.json b/Tests/Fido2.Tests/TestFiles/attestationResultsPacked512.json similarity index 100% rename from Test/TestFiles/attestationResultsPacked512.json rename to Tests/Fido2.Tests/TestFiles/attestationResultsPacked512.json diff --git a/Test/TestFiles/attestationResultsU2F.json b/Tests/Fido2.Tests/TestFiles/attestationResultsU2F.json similarity index 100% rename from Test/TestFiles/attestationResultsU2F.json rename to Tests/Fido2.Tests/TestFiles/attestationResultsU2F.json diff --git a/Test/TestFiles/attestationTPMSHA1Options.json b/Tests/Fido2.Tests/TestFiles/attestationTPMSHA1Options.json similarity index 100% rename from Test/TestFiles/attestationTPMSHA1Options.json rename to Tests/Fido2.Tests/TestFiles/attestationTPMSHA1Options.json diff --git a/Test/TestFiles/attestationTPMSHA1Response.json b/Tests/Fido2.Tests/TestFiles/attestationTPMSHA1Response.json similarity index 100% rename from Test/TestFiles/attestationTPMSHA1Response.json rename to Tests/Fido2.Tests/TestFiles/attestationTPMSHA1Response.json diff --git a/Test/TestFiles/attestationTPMSHA256Options.json b/Tests/Fido2.Tests/TestFiles/attestationTPMSHA256Options.json similarity index 100% rename from Test/TestFiles/attestationTPMSHA256Options.json rename to Tests/Fido2.Tests/TestFiles/attestationTPMSHA256Options.json diff --git a/Test/TestFiles/attestationTPMSHA256Response.json b/Tests/Fido2.Tests/TestFiles/attestationTPMSHA256Response.json similarity index 100% rename from Test/TestFiles/attestationTPMSHA256Response.json rename to Tests/Fido2.Tests/TestFiles/attestationTPMSHA256Response.json diff --git a/Test/TestFiles/json1.json b/Tests/Fido2.Tests/TestFiles/json1.json similarity index 100% rename from Test/TestFiles/json1.json rename to Tests/Fido2.Tests/TestFiles/json1.json diff --git a/Test/TestFiles/json2.json b/Tests/Fido2.Tests/TestFiles/json2.json similarity index 100% rename from Test/TestFiles/json2.json rename to Tests/Fido2.Tests/TestFiles/json2.json diff --git a/Test/TestFiles/mdsCA.cer b/Tests/Fido2.Tests/TestFiles/mdsCA.cer similarity index 100% rename from Test/TestFiles/mdsCA.cer rename to Tests/Fido2.Tests/TestFiles/mdsCA.cer diff --git a/Test/TestFiles/mdsCA.crl b/Tests/Fido2.Tests/TestFiles/mdsCA.crl similarity index 100% rename from Test/TestFiles/mdsCA.crl rename to Tests/Fido2.Tests/TestFiles/mdsCA.crl diff --git a/Test/TestFiles/mdsRoot.cer b/Tests/Fido2.Tests/TestFiles/mdsRoot.cer similarity index 100% rename from Test/TestFiles/mdsRoot.cer rename to Tests/Fido2.Tests/TestFiles/mdsRoot.cer diff --git a/Test/TestFiles/mdsRoot.crl b/Tests/Fido2.Tests/TestFiles/mdsRoot.crl similarity index 100% rename from Test/TestFiles/mdsRoot.crl rename to Tests/Fido2.Tests/TestFiles/mdsRoot.crl diff --git a/Test/TestFiles/mdsSigning.cer b/Tests/Fido2.Tests/TestFiles/mdsSigning.cer similarity index 100% rename from Test/TestFiles/mdsSigning.cer rename to Tests/Fido2.Tests/TestFiles/mdsSigning.cer diff --git a/Test/TestFiles/options1.json b/Tests/Fido2.Tests/TestFiles/options1.json similarity index 100% rename from Test/TestFiles/options1.json rename to Tests/Fido2.Tests/TestFiles/options1.json diff --git a/Test/TestFiles/options2.json b/Tests/Fido2.Tests/TestFiles/options2.json similarity index 100% rename from Test/TestFiles/options2.json rename to Tests/Fido2.Tests/TestFiles/options2.json diff --git a/Test/xunit.runner.json b/Tests/Fido2.Tests/xunit.runner.json similarity index 100% rename from Test/xunit.runner.json rename to Tests/Fido2.Tests/xunit.runner.json diff --git a/fido2-net-lib.sln b/fido2-net-lib.sln index 84a062c2..6798dd64 100644 --- a/fido2-net-lib.sln +++ b/fido2-net-lib.sln @@ -7,8 +7,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fido2", "Src\Fido2\Fido2.cs EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo", "Demo\Demo.csproj", "{1A7DD7B0-255E-44AD-8BE0-5F833846F444}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test", "Test\Test.csproj", "{06E82E3F-C626-4070-98BB-6D307DA86AC2}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{80EE6A6B-EA6A-4C01-9EEA-45BE37DB5A2D}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig @@ -30,7 +28,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorWasmDemo.Server", "Bl EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorWasmDemo.Client", "BlazorWasmDemo\Client\BlazorWasmDemo.Client.csproj", "{EAFF4332-145B-4719-B261-827CEB447A62}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fido2.Development", "Src\Fido2.Development\Fido2.Development.csproj", "{08089F2C-A0EE-4B2A-BFE6-B959DEAA4971}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fido2.Development", "Src\Fido2.Development\Fido2.Development.csproj", "{08089F2C-A0EE-4B2A-BFE6-B959DEAA4971}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fido2.Tests", "Tests\Fido2.Tests\Fido2.Tests.csproj", "{8DE1B773-5F5A-460C-9811-43506752E063}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -46,10 +46,6 @@ Global {1A7DD7B0-255E-44AD-8BE0-5F833846F444}.Debug|Any CPU.Build.0 = Debug|Any CPU {1A7DD7B0-255E-44AD-8BE0-5F833846F444}.Release|Any CPU.ActiveCfg = Release|Any CPU {1A7DD7B0-255E-44AD-8BE0-5F833846F444}.Release|Any CPU.Build.0 = Release|Any CPU - {06E82E3F-C626-4070-98BB-6D307DA86AC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {06E82E3F-C626-4070-98BB-6D307DA86AC2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {06E82E3F-C626-4070-98BB-6D307DA86AC2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {06E82E3F-C626-4070-98BB-6D307DA86AC2}.Release|Any CPU.Build.0 = Release|Any CPU {08AA0431-3BBF-49EF-8DEF-5433B3068D92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {08AA0431-3BBF-49EF-8DEF-5433B3068D92}.Debug|Any CPU.Build.0 = Debug|Any CPU {08AA0431-3BBF-49EF-8DEF-5433B3068D92}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -82,6 +78,10 @@ Global {08089F2C-A0EE-4B2A-BFE6-B959DEAA4971}.Debug|Any CPU.Build.0 = Debug|Any CPU {08089F2C-A0EE-4B2A-BFE6-B959DEAA4971}.Release|Any CPU.ActiveCfg = Release|Any CPU {08089F2C-A0EE-4B2A-BFE6-B959DEAA4971}.Release|Any CPU.Build.0 = Release|Any CPU + {8DE1B773-5F5A-460C-9811-43506752E063}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8DE1B773-5F5A-460C-9811-43506752E063}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8DE1B773-5F5A-460C-9811-43506752E063}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8DE1B773-5F5A-460C-9811-43506752E063}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE