using System.Buffers.Binary;
using System.Collections.Generic;
using System.Formats.Cbor;
using System.Security.Cryptography;
using Microsoft.IdentityModel.Tokens;
public static int ReadStartMapCheckLength(CborReader reader, int expectedLength)
int? length = reader.ReadStartMap();
throw new Exception("Indefinite length maps are not supported");
if (length != expectedLength)
throw new Exception($"Expecting map with length {expectedLength} but length {length}");
public static string ReadMapStringEntry(CborReader reader, string expectedKey)
ReadMapKey(reader, expectedKey);
return reader.ReadTextString();
public static byte[] ReadMapByteStringEntry(CborReader reader, string expectedKey)
ReadMapKey(reader, expectedKey);
return reader.ReadByteString();
public static byte[] ReadMapByteStringEntry(CborReader reader, int expectedKey)
ReadMapKey(reader, expectedKey);
return reader.ReadByteString();
public static int ReadMapIntEntry(CborReader reader, string expectedKey)
ReadMapKey(reader, expectedKey);
return reader.ReadInt32();
public static int ReadMapIntEntry(CborReader reader, int expectedKey)
ReadMapKey(reader, expectedKey);
return reader.ReadInt32();
public static T ReadMapObjectEntry<T>(CborReader reader, string expectedKey, Func<CborReader, T> readObject)
ReadMapKey(reader, expectedKey);
return readObject(reader);
public static T[] ReadMapArrayEntry<T>(CborReader reader, string expectedKey, Func<CborReader, T> readItem, bool allowEmptyArray = false)
ReadMapKey(reader, expectedKey);
int? arrayLength = reader.ReadStartArray();
if (arrayLength == null) throw new Exception("Indefinite length arrays are not supported.");
throw new Exception($"Expected non-empty array but found an empty array");
T[] array = new T[(int)arrayLength];
for (int i = 0; i < arrayLength; i++)
array[i] = readItem(reader);
private static void ReadMapKey(CborReader reader, string expectedKey)
string key = reader.ReadTextString();
if (key != expectedKey) throw new Exception($"Expecting map key with name {expectedKey} but found {key}");
private static void ReadMapKey(CborReader reader, int expectedKey)
int key = reader.ReadInt32();
if (key != expectedKey) throw new Exception($"Expecting map key with name {expectedKey} but found {key}");
public class AuthenticatorAttestationResponse
public string AttestationObject { get; set; }
public string ClientDataJSON { get; set; }
public byte[] AttestationObjectBytes => Base64UrlEncoder.DecodeBytes(this.AttestationObject);
public byte[] ClientDataJSONBytes => Base64UrlEncoder.DecodeBytes(this.ClientDataJSON);
public byte[] ClientDataHash => SHA256.HashData(this.ClientDataJSONBytes);
public class PublicKeyCredential
public string AuthenticatorAttachment { get; set; }
public string Id { get; set; }
public string RawId { get; set; }
public AuthenticatorAttestationResponse Response { get; set; }
public string Type { get; set; }
public byte[] IdBytes => Base64UrlEncoder.DecodeBytes(this.Id);
public byte[] RawIdBytes => Base64UrlEncoder.DecodeBytes(this.RawId);
public abstract class AttestationStatementBase
private static Dictionary<string, Func<CborReader, AttestationStatementBase>> AttestationParsers = new ()
{ "none", NoneAttestationStatement.ReadNoneAttestationStatement },
{ "tpm", TpmAttestationStatement.ReadTpmAttestationStatement },
public static AttestationStatementBase ReadAttestationStatement(CborReader reader, string format)
if (!AttestationParsers.ContainsKey(format)) throw new Exception($"Unknown attestation format {format}");
return AttestationParsers[format](reader);
public abstract void Verify(CredentialPublicKeyBase attestedKey, byte[] clientDataHash);
public class NoneAttestationStatement : AttestationStatementBase
public static AttestationStatementBase ReadNoneAttestationStatement(CborReader reader)
CborHelper.ReadStartMapCheckLength(reader, 0);
return new NoneAttestationStatement();
public override void Verify(CredentialPublicKeyBase attestedKey, byte[] clientDataHash)
public class TpmAttestationPubArea
public ushort Type { get; set; }
public ushort NameAlg { get; set; }
public uint ObjectAttributes { get; set; }
public byte[] AuthPolicy { get; set; }
public ushort Curve { get; set; }
public byte[] X { get; set; }
public byte[] Y { get; set; }
public bool IsRestricted => (this.ObjectAttributes & 0x10000) != 0;
public bool CanSignEncrypt => (this.ObjectAttributes & 0x40000) != 0;
public static TpmAttestationPubArea FromBytes(byte[] data)
var span = new Span<byte>(data);
TpmAttestationPubArea pubArea = new ()
Type = BinaryPrimitives.ReadUInt16BigEndian(span.Slice(0, 2)),
NameAlg = BinaryPrimitives.ReadUInt16BigEndian(span.Slice(2, 2)),
ObjectAttributes = BinaryPrimitives.ReadUInt32BigEndian(span.Slice(4, 4)),
AuthPolicy = data[10 .. (10 + BinaryPrimitives.ReadUInt16BigEndian(span.Slice(8, 2)))],
span = span.Slice(10 + pubArea.AuthPolicy.Length);
if (pubArea.IsRestricted) throw new Exception("Expected unrestricted key but found restricted key");
if (!pubArea.CanSignEncrypt) throw new Exception("Expected key that can be used for signing or encrypting");
if (BinaryPrimitives.ReadUInt16BigEndian(span.Slice(0, 2)) != 0x10) throw new Exception("Expected parameter symmetric to be TPM_ALG_NULL");
if (BinaryPrimitives.ReadUInt16BigEndian(span.Slice(2, 2)) != 0x10) throw new Exception("Expected parameter scheme to be TPM_ALG_NULL");
if (BinaryPrimitives.ReadUInt16BigEndian(span.Slice(6, 2)) != 0x10) throw new Exception("Expected parameter kdf to be TPM_ALG_NULL");
pubArea.Curve = BinaryPrimitives.ReadUInt16BigEndian(span.Slice(4, 2));
pubArea.X = span.Slice(10, BinaryPrimitives.ReadUInt16BigEndian(span.Slice(8, 2))).ToArray();
span = span.Slice(10 + pubArea.X.Length);
pubArea.Y = span.Slice(2, BinaryPrimitives.ReadUInt16BigEndian(span.Slice(0, 2))).ToArray();
public class TpmAttestationStatement : AttestationStatementBase
public string Version { get; set; }
public int Algorithm { get; set; }
public byte[][] X5C { get; set; }
public byte[] Signature { get; set; }
public byte[] CertInfo { get; set; }
public TpmAttestationPubArea PubArea { get; set; }
public static AttestationStatementBase ReadTpmAttestationStatement(CborReader reader)
CborHelper.ReadStartMapCheckLength(reader, 6);
TpmAttestationStatement attestationStatement = new ()
Algorithm = CborHelper.ReadMapIntEntry(reader, "alg"),
Signature = CborHelper.ReadMapByteStringEntry(reader, "sig"),
Version = CborHelper.ReadMapStringEntry(reader, "ver"),
X5C = CborHelper.ReadMapArrayEntry(reader, "x5c", (reader) => reader.ReadByteString()),
PubArea = TpmAttestationPubArea.FromBytes(CborHelper.ReadMapByteStringEntry(reader, "pubArea")),
CertInfo = CborHelper.ReadMapByteStringEntry(reader, "certInfo")
return attestationStatement;
public override void Verify(CredentialPublicKeyBase attestedKey, byte[] clientDataHash)
if (attestedKey == null) throw new Exception("Attested key is null");
EC2CredentialPublicKey ec2AttestedKey = attestedKey as EC2CredentialPublicKey;
if (ec2AttestedKey == null) throw new Exception($"Expected attested key to be of type EC2CredentialPublicKey but found {attestedKey.GetType()}");
ushort expectedAlg = ec2AttestedKey.Algorithm switch
_ => throw new Exception($"Unknown algorithm {ec2AttestedKey.Algorithm} in attested key")
ushort expectedCurve = ec2AttestedKey.Curve switch
_ => throw new Exception($"Unknown curve {ec2AttestedKey.Curve} in attested key")
if (this.PubArea.NameAlg != expectedAlg) throw new Exception($"Expected algorithm {expectedAlg} but found {this.PubArea.NameAlg}");
if (this.PubArea.Curve != expectedCurve) throw new Exception($"Expected curve {expectedCurve} but found {this.PubArea.Curve}");
if (!ec2AttestedKey.X.AsSpan().SequenceEqual(this.PubArea.X)) throw new Exception($"Expected X to be equal");
if (!ec2AttestedKey.Y.AsSpan().SequenceEqual(this.PubArea.Y)) throw new Exception($"Expected Y to be equal");
public class CredentialPublicKeyBase
private static Dictionary<int, Func<CborReader, CredentialPublicKeyBase>> CredentialPublicKeyParsers = new ()
{ 2, EC2CredentialPublicKey.ReadEC2CredentialPublicKey },
public static CredentialPublicKeyBase ReadCredentialPublicKey(CborReader reader)
int? mapLength = reader.ReadStartMap();
int keyType = CborHelper.ReadMapIntEntry(reader, 1);
if (!CredentialPublicKeyParsers.ContainsKey(keyType)) throw new Exception($"Unknown public key type {keyType}");
CredentialPublicKeyBase credentialPublicKey = CredentialPublicKeyParsers[keyType](reader);
return credentialPublicKey;
public class EC2CredentialPublicKey : CredentialPublicKeyBase
public int Algorithm { get; set; }
public int Curve { get; set; }
public byte[] X { get; set; }
public byte[] Y { get; set; }
public static CredentialPublicKeyBase ReadEC2CredentialPublicKey(CborReader reader)
EC2CredentialPublicKey ec2CredentialPublicKey = new ()
Algorithm = CborHelper.ReadMapIntEntry(reader, 3),
Curve = CborHelper.ReadMapIntEntry(reader, -1),
X = CborHelper.ReadMapByteStringEntry(reader, -2),
Y = CborHelper.ReadMapByteStringEntry(reader, -3)
return ec2CredentialPublicKey;
public class AttestedCredentialData
public Guid AAGuid { get; set; }
public ushort CredentialIdLength { get; set; }
public byte[] CredentialId { get; set; }
public CredentialPublicKeyBase CredentialPublicKey { get; set; }
public static AttestedCredentialData FromBytes(byte[] data, out int bytesRemaining)
byte[] aaGuidBytes = new byte[16];
Array.Copy(data, 3, aaGuidBytes, 0, 1);
Array.Copy(data, 2, aaGuidBytes, 1, 1);
Array.Copy(data, 1, aaGuidBytes, 2, 1);
Array.Copy(data, 0, aaGuidBytes, 3, 1);
Array.Copy(data, 5, aaGuidBytes, 4, 1);
Array.Copy(data, 4, aaGuidBytes, 5, 1);
Array.Copy(data, 7, aaGuidBytes, 6, 1);
Array.Copy(data, 6, aaGuidBytes, 7, 1);
Array.Copy(data, 8, aaGuidBytes, 8, 8);
byte[] credentialIdLengthBytes = new byte[2];
Array.Copy(data, 17, credentialIdLengthBytes, 0, 1);
Array.Copy(data, 16, credentialIdLengthBytes, 1, 1);
AttestedCredentialData attestedCredentialData = new ()
AAGuid = new Guid(aaGuidBytes),
CredentialIdLength = BitConverter.ToUInt16(credentialIdLengthBytes)
attestedCredentialData.CredentialId = new byte[attestedCredentialData.CredentialIdLength];
Array.Copy(data, 18, attestedCredentialData.CredentialId, 0, attestedCredentialData.CredentialIdLength);
CborReader reader = new CborReader(data[(18 + attestedCredentialData.CredentialIdLength) .. data.Length]);
attestedCredentialData.CredentialPublicKey = CredentialPublicKeyBase.ReadCredentialPublicKey(reader);
bytesRemaining = reader.BytesRemaining;
return attestedCredentialData;
public class AuthenticatorData
public byte[] RPIdHash { get; set; }
public byte Flags { get; set; }
public uint SignCount { get; set; }
public AttestedCredentialData AttestedCredentialData { get; set; }
public bool HasAttestedCredentialData => (this.Flags & 0x40) != 0;
public bool HasExtensionData => (this.Flags & 0x80) != 0;
public static AuthenticatorData FromBytes(byte[] authenticatorDataBytes)
AuthenticatorData authenticatorData = new ()
RPIdHash = authenticatorDataBytes[0 .. 32],
Flags = authenticatorDataBytes[32],
SignCount = BitConverter.ToUInt32(authenticatorDataBytes, 33)
int bytesRemaining = authenticatorDataBytes.Length - 36;
if (authenticatorData.HasAttestedCredentialData)
if (bytesRemaining == 0) throw new Exception("Expected attested credential data but no more bytes remain");
authenticatorData.AttestedCredentialData = AttestedCredentialData.FromBytes(authenticatorDataBytes[37 .. authenticatorDataBytes.Length], out bytesRemaining);
if (authenticatorData.HasExtensionData)
if (bytesRemaining == 0) throw new Exception("Expected extension data but no more bytes remain");
return authenticatorData;
public class AttestationObject
public string Format { get; set; }
public AttestationStatementBase AttestationStatement { get; set; }
public byte[] AuthenticatorDataBytes { get; set; }
public AuthenticatorData AuthenticatorData => AuthenticatorData.FromBytes(this.AuthenticatorDataBytes);
public static AttestationObject ReadAttestationObject(CborReader reader)
CborHelper.ReadStartMapCheckLength(reader, 3);
AttestationObject attestationObject = new AttestationObject()
Format = CborHelper.ReadMapStringEntry(reader, "fmt")
attestationObject.AttestationStatement = CborHelper.ReadMapObjectEntry(reader, "attStmt", (reader) => AttestationStatementBase.ReadAttestationStatement(reader, attestationObject.Format));
attestationObject.AuthenticatorDataBytes = CborHelper.ReadMapByteStringEntry(reader, "authData");
return attestationObject;
public void Verify(byte[] clientDataHash)
byte[] attToBeSigned = new byte[this.AuthenticatorDataBytes.Length + clientDataHash.Length];
Array.Copy(this.AuthenticatorDataBytes, 0, attToBeSigned, 0, this.AuthenticatorDataBytes.Length);
Array.Copy(clientDataHash, 0, attToBeSigned, this.AuthenticatorDataBytes.Length, clientDataHash.Length);
this.AttestationStatement.Verify(this.AuthenticatorData.AttestedCredentialData.CredentialPublicKey, attToBeSigned);
public static void Main()
"authenticatorAttachment": "platform",
"id": "mP2h3oDqSgoCeLaZYrNtoyjeNJjX3drrphzHMIFx2cQ",
"rawId": "mP2h3oDqSgoCeLaZYrNtoyjeNJjX3drrphzHMIFx2cQ",
"attestationObject": "o2NmbXRjdHBtZ2F0dFN0bXSmY2FsZzn__mNzaWdZAQBLZKfqq8FdsFiTLOuFjYonSw2JadAWhppUTMTFsNC6WwKJd8fGsPYMpj2dJPOSIpzgH6_JaVEKl4ZUQ10ZhUoklDFMg0Bn4hJYr8XHSi_5hqvteGadgK9EdtpZUDIMuC4kdSIm1nMfSDAX-v-3COm2bngYGDpepe0TZb8hX7aYBe6wHK2DOe59B1z_P74ZugEyTi6nLbdbt-DkvwadpHf-qrd_vyX1PyQfonNbmPwyzAm8w7wMZQHznhJxi4C7t-RxqUg7AjzocVL3zhqh0XuXLKlxEgOCpCToI38qGJ7llrZUfB-wbzI64qK42g6bboLOqiu3q4lOKhICbv2ZZIhmY3ZlcmMyLjBjeDVjglkFwDCCBbwwggOkoAMCAQICEFL-G59yW09gkBGJFja7FvYwDQYJKoZIhvcNAQELBQAwQTE_MD0GA1UEAxM2TkNVLUlGWC1LRVlJRC02OTE0NkFDM0NGQjM2NjVDN0FFNzgxMDFDQ0E1QzE0MjU1Q0EyQkM4MB4XDTIzMTAyNTIwNDUyOVoXDTI3MDYwMzE3NTUxOVowADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKVn-RvBmQ3eMdxDPtnLyqxgwmwJU8Bbf8K2IBosYrZNxY7_WEo5ARP3vw7CAkJk_ArHdzGClqM-5lC2CeYVFM6Y-9nvj-krlXvw0iO1G1JNXoEEMLc1Dk65xNsHQAA0m6OiS-A6OFG4z0q7O8vCHrzChcY4MjASZrnOCazLElaggXx04wCUHwj5WrZB-RjP5HxCvJEJJXLzIb6yG3kQsGXNSL0J6u2Rger29T6GgoNhQViezhUEyt_vvEI6YnK6YEYZpItDWm3kp_wIEERwL6C6YQs_HsJAP5jjM7Z1vjm4y8aEK4T9_mj12ETHSAGbhMGYHPqMtiJcZa6xjxLcMHUCAwEAAaOCAe8wggHrMA4GA1UdDwEB_wQEAwIHgDAMBgNVHRMBAf8EAjAAMG0GA1UdIAEB_wRjMGEwXwYJKwYBBAGCNxUfMFIwUAYIKwYBBQUHAgIwRB5CAFQAQwBQAEEAIAAgAFQAcgB1AHMAdABlAGQAIAAgAFAAbABhAHQAZgBvAHIAbQAgACAASQBkAGUAbgB0AGkAdAB5MBAGA1UdJQQJMAcGBWeBBQgDMFUGA1UdEQEB_wRLMEmkRzBFMRYwFAYFZ4EFAgEMC2lkOjQ5NDY1ODAwMRcwFQYFZ4EFAgIMDFNMQjk2NjV4eDIuMDESMBAGBWeBBQIDDAdpZDowNXVwMB8GA1UdIwQYMBaAFJNYb-KNQyhlTYiMZvZRMDcSs-c_MB0GA1UdDgQWBBT5cXzOa6KE5JOo-6p--bOSCPh74zCBsgYIKwYBBQUHAQEEgaUwgaIwgZ8GCCsGAQUFBzAChoGSaHR0cDovL2F6Y3Nwcm9kbmN1YWlrcHVibGlzaC5ibG9iLmNvcmUud2luZG93cy5uZXQvbmN1LWlmeC1rZXlpZC02OTE0NmFjM2NmYjM2NjVjN2FlNzgxMDFjY2E1YzE0MjU1Y2EyYmM4L2U2MWRlNjMxLWIyOGYtNDM1My04ZWVhLTVlOTAxYjRkNTUxOC5jZXIwDQYJKoZIhvcNAQELBQADggIBAEC3_DQ_sI8jYhPJKy_oOf3jNOKUtSRCUapcPLrSn007gQkHESQCbwxk5O6BSQsi8Mz5QCQKX_Y0hKemZN16gaTsc0PTAU4EobMCfklgFV5Uesp_4ZVHYaw2DPCx42ElJXLGF76QcVe6_7jb9L-6O9Lzzmx31ODsDc4m8tdPc3g6bKlRcJDSjRi-yOCsfpvg2dwQMq6fzKHjMi3yo7lw-pSDOHdIGDwsWzvblsrhEoMYvqg1C5-ZOAHN6rxm2GVRQeincSwg9wBLqEVXLpjqu9dpzbenstTQwxRv53YMKLEHvs0lC5S2Kkx33aX6eYikmEIshTnmdqqhIknlDQboDXUZKv3kCaxboBLcACKVDCQ6YjRIpxLnEDbWTIoOev78i5IO1PPjwJXGQDXD9EOmAy29J2ygjT8ZEUzX0kYcx02Kw2lxy7AetmrWuk7VZah9SAvG_lIOjhr0BQm77vyM4OuVth2ArMV2UzudvBIykQmkMMjFPZGoIx1jAwm6D-cMDpKWa1cWGcOYnc4nzKY2EHIavl3_Raa4dymF-vT9T3J5nMSCnE3m33hKUWNtZ0bYVKhq0hE5LbAl1fbrgJa2r0yhWv0g_xIg3P0tCWrV9UeaVyQdTrRg4PuUPlVNDdjWui5lBUg-fb2mc3n_uF-xK1ZC56CmbSEmnbqu7WHRMmLwWQbvMIIG6zCCBNOgAwIBAgITMwAABRXdrTKA8n0s3wAAAAAFFTANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjE2MDQGA1UEAxMtTWljcm9zb2Z0IFRQTSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDE0MB4XDTIxMDYwMzE3NTUxOVoXDTI3MDYwMzE3NTUxOVowQTE_MD0GA1UEAxM2TkNVLUlGWC1LRVlJRC02OTE0NkFDM0NGQjM2NjVDN0FFNzgxMDFDQ0E1QzE0MjU1Q0EyQkM4MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr-6Oau2VnxK8n-9Xp6sygP1CLhq_APic-LZmq0EtgXfVoITDeXL8hwsx9iWwN-XNKhUkpK_M-0UJadVM3OFeDCCAQYHd4sZ2ZTtcVn9dr5lZTVJ9qZdoSura0_DAJ8DYFKOk7K7uWsEKZ7NMYyVqWpOZddAaV8MR-CGUEaLgYx9CgbZ-nFytmr2WC7k5qXX496qZNWrdilGy6_XcdW7sbR0hFyORbLzSK8OEjU5O2ka6N4b8jDNbqr4qxu1gt7wLzQ8-6MN-uRrV7_mmPVhM6pyvJDUkhEk97DrdXwFCrlC15pO6jduJuzxl4oN1VBGVuz5fIkFaN7pc1mTpf0Kj3N0oyDsX1rQWf7Zrpwu7uRf8MByc1JJ2BWUZ2SkyBdtk6BpZuliRu0ONMWAXVPkUMC5j3ov6AuR8lD3euvLhjPvVt2S3rUvRAAj3lXkwlmnhXVZ3RXyNvYDbN6LuRNd4YLAyLAS2V1iB_TzG2qVoEnBjRVaT8bnppQ3dfi7e0OBzPgSYAVnuaYt1pVdtJ__3d2_JjAtlm5X_tRBXai1U_OcQ11ZkVkQv5q8wPVPZn6JwPUy56eDTcXG5vkcdFP1CNwyIxO-txFZ3unxyyOHphDd38aYgfbn1PvXE3OYqIWRXmBR1qO1E-mXreWE8GM0-AXhQZtO_eUX9qJNWW9RG9YECAwEAAaOCAY4wggGKMA4GA1UdDwEB_wQEAwIChDAbBgNVHSUEFDASBgkrBgEEAYI3FSQGBWeBBQgDMBYGA1UdIAQPMA0wCwYJKwYBBAGCNxUfMBIGA1UdEwEB_wQIMAYBAf8CAQAwHQYDVR0OBBYEFJNYb-KNQyhlTYiMZvZRMDcSs-c_MB8GA1UdIwQYMBaAFHqMCs4vSGIX4pTRrlXBUuxxdKRWMHAGA1UdHwRpMGcwZaBjoGGGX2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUyMFRQTSUyMFJvb3QlMjBDZXJ0aWZpY2F0ZSUyMEF1dGhvcml0eSUyMDIwMTQuY3JsMH0GCCsGAQUFBwEBBHEwbzBtBggrBgEFBQcwAoZhaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUUE0lMjBSb290JTIwQ2VydGlmaWNhdGUlMjBBdXRob3JpdHklMjAyMDE0LmNydDANBgkqhkiG9w0BAQsFAAOCAgEAPlq8ibgbCHT6kYKGIAEHgo5vm77YkWFcUpQiJdltITx_BHweTm0zqRJ-Qr_7_FJ2JMpIaljtojDUu0Fa8qpGy9c0Hjr6rGZbFfAmmpGr9vgBc3a3ZPJVUq05j5MSnzk9hRqURRCS0pBVPpk-EDKUM9CQMFE4HDLk9J9LZ04dKImabXZIEW1F0uNPV2QIID550llzw8sGMWDHSDEO51KB6v2ZVCtbhlUsHFOHypbT9OB6_kNHIY76tChuTQb06m9oDOon-JNfDLsIfDKlzdIRPelloybnA_fb3oEa9q3uXAmfcR4OhamfTQJITgBvLFCZiamXXyPPa1cH3fXOIfj7LMw18G4CXtk90MQESqHfMx-Rtr--QRuchIab0ADjRh_ZJ7-oC-CIxm1qKygpwEoWm-S6twF5cIb0GOZlVz67lnCPRzpi9vz1z2K4mUc80TuGrc5QtWfSjU28GcSBjiNwjlQ3-Sk-JAWa-nZ1F-c06-RrPN3PDQWXHovkmhi94AvAj1apXhR64E0FmkwU6TcfJiSxpPlFsPY9n-ovuwmd18u0G-twpgbFyiYeX7m2GWz9RiKfI6cHWfX3snWt0Ufh-c0Sn2dUS1ao7ODsFFsKpeE8vQNnfFyU2rTN3-ZrPn3AkP4uPY7eL0pGr3hmivTp-kORZbBeJG0F7pc-73M7lPZncHViQXJlYVh2ACMACwAEAHIAIJ3_y_NsODrmmfuYaNxty4nXFTiEvigDkiwSQVi_rSKuABAAEAADABAAIA0sMRgWgyNTv9sa1dLpbmDgjrndkVPMzmKR7GC_JAdSACDh1qa55r5G_hPP0WyCGtPGD61whKZyGdVe6eQpL7JnhGhjZXJ0SW5mb1ih_1RDR4AXACIACxXmegy25mt7heAlodG7nl25G4Z_7jFSBBCDSZnwELt0ABRBDXCpUeDG90APqEfJXFWmPFc7IwAAAAqJNNdp7hVRz8jG-qUB9OUeeLaf4DMAIgALi1JIbzxsli3zF3PJB_feW2qPI2PS2NrtOFdLygGBXwUAIgAL1xzZ-8yFHutQZDlbneNccfd4UvEMWBOSJvvaLowQxAdoYXV0aERhdGFYpHSm6pITyZwvdLIkkrMgz0AmKpTBqVCgOX8pJQtghB7wRQAAAAAImHBYytxLgbbhMN5Q3L6WACCY_aHegOpKCgJ4tplis22jKN40mNfd2uumHMcwgXHZxKUBAgMmIAEhWCANLDEYFoMjU7_bGtXS6W5g4I653ZFTzM5ikexgvyQHUiJYIOHWprnmvkb-E8_RbIIa08YPrXCEpnIZ1V7p5CkvsmeE",
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiQW44YXVqbFN2bzczOUhfT1lkMjh2a1pUcE83cDhSSmROWnlaejByZjBRWll5aWhmSE1ZSkIzVGhVdEE0VHVsazNFTDZtdzJfaE5laGxURzJ1OS1uZUEiLCJvcmlnaW4iOiJodHRwczovL3dlYmF1dGhuLmlvIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ"
PublicKeyCredential pubKey = JsonSerializer.Deserialize<PublicKeyCredential>(pubKeyJson, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
CborReader reader = new CborReader(pubKey.Response.AttestationObjectBytes);
AttestationObject attestationObject = AttestationObject.ReadAttestationObject(reader);
attestationObject.Verify(pubKey.Response.ClientDataHash);
Console.WriteLine("Success!");