From 464197aea31198c8e9d72606aef3165bc9cd5632 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Sun, 26 Jan 2025 12:02:31 -0800 Subject: [PATCH 1/6] Update Google hardware attestation root certs --- .../services/defaultRootCerts/android-key.ts | 88 +++++++++++++++++++ .../server/src/services/settingsService.ts | 6 +- 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/packages/server/src/services/defaultRootCerts/android-key.ts b/packages/server/src/services/defaultRootCerts/android-key.ts index ec38a744..ba984b79 100644 --- a/packages/server/src/services/defaultRootCerts/android-key.ts +++ b/packages/server/src/services/defaultRootCerts/android-key.ts @@ -84,3 +84,91 @@ ZutL8VuFkERQGt6vQ2OCw0sV47VMkuYbacK/xyZFiRcrPJPb41zgbQj9XAEyLKCH ex0SdDrx+tWUDqG8At2JHA== -----END CERTIFICATE----- `; + +/** + * Google Hardware Attestation Root 3 + * + * Downloaded from https://developer.android.com/training/articles/security-key-attestation#root_certificate + * (third entry) + * + * Valid until 2036-11-13 @ 15:10 PST + * + * SHA256 Fingerprint + * AB:66:41:17:8A:36:E1:79:AA:0C:1C:DD:DF:9A:16:EB:45:FA:20:94:3E:2B:8C:D7:C7:C0:5C:26:CF:8B:48:7A + */ +export const Google_Hardware_Attestation_Root_3 = ` +-----BEGIN CERTIFICATE----- +MIIFHDCCAwSgAwIBAgIJAMNrfES5rhgxMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV +BAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMjExMTE3MjMxMDQyWhcNMzYxMTEzMjMx +MDQyWjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdS +Sxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7 +tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggj +nar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGq +C4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQ +oVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+O +JtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/Eg +sTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRi +igHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+M +RPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9E +aDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5Um +AGMCAwEAAaNjMGEwHQYDVR0OBBYEFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMB8GA1Ud +IwQYMBaAFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMA8GA1UdEwEB/wQFMAMBAf8wDgYD +VR0PAQH/BAQDAgIEMA0GCSqGSIb3DQEBCwUAA4ICAQBTNNZe5cuf8oiq+jV0itTG +zWVhSTjOBEk2FQvh11J3o3lna0o7rd8RFHnN00q4hi6TapFhh4qaw/iG6Xg+xOan +63niLWIC5GOPFgPeYXM9+nBb3zZzC8ABypYuCusWCmt6Tn3+Pjbz3MTVhRGXuT/T +QH4KGFY4PhvzAyXwdjTOCXID+aHud4RLcSySr0Fq/L+R8TWalvM1wJJPhyRjqRCJ +erGtfBagiALzvhnmY7U1qFcS0NCnKjoO7oFedKdWlZz0YAfu3aGCJd4KHT0MsGiL +Zez9WP81xYSrKMNEsDK+zK5fVzw6jA7cxmpXcARTnmAuGUeI7VVDhDzKeVOctf3a +0qQLwC+d0+xrETZ4r2fRGNw2YEs2W8Qj6oDcfPvq9JySe7pJ6wcHnl5EZ0lwc4xH +7Y4Dx9RA1JlfooLMw3tOdJZH0enxPXaydfAD3YifeZpFaUzicHeLzVJLt9dvGB0b +HQLE4+EqKFgOZv2EoP686DQqbVS1u+9k0p2xbMA105TBIk7npraa8VM0fnrRKi7w +lZKwdH+aNAyhbXRW9xsnODJ+g8eF452zvbiKKngEKirK5LGieoXBX7tZ9D1GNBH2 +Ob3bKOwwIWdEFle/YF/h6zWgdeoaNGDqVBrLr2+0DtWoiB1aDEjLWl9FmyIUyUm7 +mD/vFDkzF+wm7cyWpQpCVQ== +-----END CERTIFICATE----- +`; + +/** + * Google Hardware Attestation Root 4 + * + * Downloaded from https://developer.android.com/training/articles/security-key-attestation#root_certificate + * (fourth entry) + * + * Valid until 2042-03-15 @ 11:07 PDT + * + * SHA256 Fingerprint + * CE:DB:1C:B6:DC:89:6A:E5:EC:79:73:48:BC:E9:28:67:53:C2:B3:8E:E7:1C:E0:FB:E3:4A:9A:12:48:80:0D:FC + */ +export const Google_Hardware_Attestation_Root_4 = ` +-----BEGIN CERTIFICATE----- +MIIFHDCCAwSgAwIBAgIJAPHBcqaZ6vUdMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV +BAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMjIwMzIwMTgwNzQ4WhcNNDIwMzE1MTgw +NzQ4WjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdS +Sxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7 +tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggj +nar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGq +C4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQ +oVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+O +JtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/Eg +sTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRi +igHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+M +RPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9E +aDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5Um +AGMCAwEAAaNjMGEwHQYDVR0OBBYEFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMB8GA1Ud +IwQYMBaAFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMA8GA1UdEwEB/wQFMAMBAf8wDgYD +VR0PAQH/BAQDAgIEMA0GCSqGSIb3DQEBCwUAA4ICAQB8cMqTllHc8U+qCrOlg3H7 +174lmaCsbo/bJ0C17JEgMLb4kvrqsXZs01U3mB/qABg/1t5Pd5AORHARs1hhqGIC +W/nKMav574f9rZN4PC2ZlufGXb7sIdJpGiO9ctRhiLuYuly10JccUZGEHpHSYM2G +tkgYbZba6lsCPYAAP83cyDV+1aOkTf1RCp/lM0PKvmxYN10RYsK631jrleGdcdkx +oSK//mSQbgcWnmAEZrzHoF1/0gso1HZgIn0YLzVhLSA/iXCX4QT2h3J5z3znluKG +1nv8NQdxei2DIIhASWfu804CA96cQKTTlaae2fweqXjdN1/v2nqOhngNyz1361mF +mr4XmaKH/ItTwOe72NI9ZcwS1lVaCvsIkTDCEXdm9rCNPAY10iTunIHFXRh+7KPz +lHGewCq/8TOohBRn0/NNfh7uRslOSZ/xKbN9tMBtw37Z8d2vvnXq/YWdsm1+JLVw +n6yYD/yacNJBlwpddla8eaVMjsF6nBnIgQOf9zKSe06nSTqvgwUHosgOECZJZ1Eu +zbH4yswbt02tKtKEFhx+v+OTge/06V+jGsqTWLsfrOCNLuA8H++z+pUENmpqnnHo +vaI47gC+TNpkgYGkkBT6B/m/U01BuOBBTzhIlMEZq9qkDWuM2cA5kW5V3FJUcfHn +w1IdYIg2Wxg7yHcQZemFQg== +-----END CERTIFICATE----- +`; diff --git a/packages/server/src/services/settingsService.ts b/packages/server/src/services/settingsService.ts index 6f10ea05..3d3bc0d8 100644 --- a/packages/server/src/services/settingsService.ts +++ b/packages/server/src/services/settingsService.ts @@ -1,10 +1,12 @@ -import { AttestationFormat } from '../helpers/decodeAttestationObject.ts'; +import type { AttestationFormat } from '../helpers/decodeAttestationObject.ts'; import { convertCertBufferToPEM } from '../helpers/convertCertBufferToPEM.ts'; import { GlobalSign_Root_CA } from './defaultRootCerts/android-safetynet.ts'; import { Google_Hardware_Attestation_Root_1, Google_Hardware_Attestation_Root_2, + Google_Hardware_Attestation_Root_3, + Google_Hardware_Attestation_Root_4, } from './defaultRootCerts/android-key.ts'; import { Apple_WebAuthn_Root_CA } from './defaultRootCerts/apple.ts'; import { GlobalSign_Root_CA_R3 } from './defaultRootCerts/mds.ts'; @@ -84,6 +86,8 @@ SettingsService.setRootCertificates({ certificates: [ Google_Hardware_Attestation_Root_1, Google_Hardware_Attestation_Root_2, + Google_Hardware_Attestation_Root_3, + Google_Hardware_Attestation_Root_4, ], }); From ddea5048d136f4787c30264a343632987d33cb74 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Sun, 26 Jan 2025 12:03:45 -0800 Subject: [PATCH 2/6] Call out specific verification steps via comments --- .../verifyAttestationAndroidKey.ts | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts b/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts index e51ddb3b..b560afa8 100644 --- a/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts +++ b/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts @@ -52,7 +52,10 @@ export async function verifyAttestationAndroidKey( ); } - // Check that credentialPublicKey matches the public key in the attestation certificate + /** + * Verify that the public key in the first certificate in x5c matches the credentialPublicKey in + * the attestedCredentialData in authenticatorData. + */ // Find the public cert in the certificate as PKCS const parsedCert = AsnParser.parse(x5c[0], Certificate); const parsedCertPubKey = new Uint8Array( @@ -68,6 +71,10 @@ export async function verifyAttestationAndroidKey( ); } + /** + * Verify that the attestationChallenge field in the attestation certificate extension data is + * identical to clientDataHash. + */ // Find Android KeyStore Extension in certificate extensions const extKeyStore = parsedCert.tbsCertificate.extensions?.find( (ext) => ext.extnID === id_ce_keyDescription, @@ -96,8 +103,12 @@ export async function verifyAttestationAndroidKey( ); } - // Ensure that the key is strictly bound to the caller app identifier (shouldn't contain the - // [600] tag) + /** + * The AuthorizationList.allApplications field is not present on either authorization list + * (softwareEnforced nor teeEnforced), since PublicKeyCredential MUST be scoped to the RP ID. + * + * (i.e. These shouldn't contain the [600] tag) + */ if (teeEnforced.allApplications !== undefined) { throw new Error( 'teeEnforced contained "allApplications [600]" tag (AndroidKey)', @@ -136,6 +147,11 @@ export async function verifyAttestationAndroidKey( } } + /** + * Verify that sig is a valid signature over the concatenation of authenticatorData and + * clientDataHash using the public key in the first certificate in x5c with the algorithm + * specified in alg. + */ const signatureBase = isoUint8Array.concat([authData, clientDataHash]); return verifySignature({ From e291b8c6fee6b009d150bb342d787765cdeb994c Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Sun, 26 Jan 2025 12:04:15 -0800 Subject: [PATCH 3/6] Tweak error strings --- .../verifyAttestationAndroidKey.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts b/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts index b560afa8..90b57149 100644 --- a/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts +++ b/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts @@ -32,23 +32,23 @@ export async function verifyAttestationAndroidKey( if (!x5c) { throw new Error( - 'No attestation certificate provided in attestation statement (AndroidKey)', + 'No attestation certificate provided in attestation statement (Android Key)', ); } if (!sig) { throw new Error( - 'No attestation signature provided in attestation statement (AndroidKey)', + 'No attestation signature provided in attestation statement (Android Key)', ); } if (!alg) { - throw new Error(`Attestation statement did not contain alg (AndroidKey)`); + throw new Error(`Attestation statement did not contain alg (Android Key)`); } if (!isCOSEAlg(alg)) { throw new Error( - `Attestation statement contained invalid alg ${alg} (AndroidKey)`, + `Attestation statement contained invalid alg ${alg} (Android Key)`, ); } @@ -67,7 +67,7 @@ export async function verifyAttestationAndroidKey( if (!isoUint8Array.areEqual(credPubKeyPKCS, parsedCertPubKey)) { throw new Error( - 'Credential public key does not equal leaf cert public key (AndroidKey)', + 'Credential public key does not equal leaf cert public key (Android Key)', ); } @@ -81,7 +81,7 @@ export async function verifyAttestationAndroidKey( ); if (!extKeyStore) { - throw new Error('Certificate did not contain extKeyStore (AndroidKey)'); + throw new Error('Certificate did not contain extKeyStore (Android Key)'); } const parsedExtKeyStore = AsnParser.parse( @@ -99,7 +99,7 @@ export async function verifyAttestationAndroidKey( ) ) { throw new Error( - 'Attestation challenge was not equal to client data hash (AndroidKey)', + 'Attestation challenge was not equal to client data hash (Android Key)', ); } @@ -111,13 +111,13 @@ export async function verifyAttestationAndroidKey( */ if (teeEnforced.allApplications !== undefined) { throw new Error( - 'teeEnforced contained "allApplications [600]" tag (AndroidKey)', + 'teeEnforced contained "allApplications [600]" tag (Android Key)', ); } if (softwareEnforced.allApplications !== undefined) { throw new Error( - 'teeEnforced contained "allApplications [600]" tag (AndroidKey)', + 'teeEnforced contained "allApplications [600]" tag (Android Key)', ); } @@ -132,7 +132,7 @@ export async function verifyAttestationAndroidKey( }); } catch (err) { const _err = err as Error; - throw new Error(`${_err.message} (AndroidKey)`); + throw new Error(`${_err.message} (Android Key)`); } } else { try { From 4f9590c3a7de9e0aff998c3755cb108c3d234e47 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Sun, 26 Jan 2025 12:04:52 -0800 Subject: [PATCH 4/6] Update non-MDS x5c cert chain verification --- .../verifications/verifyAttestationAndroidKey.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts b/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts index 90b57149..ebb570f3 100644 --- a/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts +++ b/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts @@ -135,15 +135,19 @@ export async function verifyAttestationAndroidKey( throw new Error(`${_err.message} (Android Key)`); } } else { + /** + * Verify that x5c contains a full certificate path. + */ + const x5cNoRootPEM = x5c.slice(0, -1).map(convertCertBufferToPEM); + const x5cRootPEM = x5c.slice(-1).map(convertCertBufferToPEM); + try { - // Try validating the certificate path using the root certificates set via SettingsService - await validateCertificatePath( - x5c.map(convertCertBufferToPEM), - rootCertificates, - ); + await validateCertificatePath(x5cNoRootPEM, x5cRootPEM); } catch (err) { const _err = err as Error; - throw new Error(`${_err.message} (AndroidKey)`); + throw new Error(`${_err.message} (Android Key)`); + } + } } From 468a9e26e0509fe0ea2dcc333e7ee8c24761ec9b Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Sun, 26 Jan 2025 12:05:03 -0800 Subject: [PATCH 5/6] Check that root cert is a known one --- .../verifications/verifyAttestationAndroidKey.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts b/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts index ebb570f3..01b6e0c6 100644 --- a/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts +++ b/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts @@ -148,6 +148,13 @@ export async function verifyAttestationAndroidKey( throw new Error(`${_err.message} (Android Key)`); } + /** + * Make sure the root certificate is one of the Google Hardware Attestation Root certificates + * + * https://developer.android.com/privacy-and-security/security-key-attestation#root_certificate + */ + if (rootCertificates.length > 0 && rootCertificates.indexOf(x5cRootPEM[0]) < 0) { + throw new Error('x5c root certificate was not a known root certificate (Android Key)'); } } From 71b38799f4543386560e6a3bb3dcef2455220517 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Sun, 26 Jan 2025 12:05:08 -0800 Subject: [PATCH 6/6] Update tests --- .../verifyAttestationAndroidKey.test.ts | 89 +++++++++++++++++-- 1 file changed, 84 insertions(+), 5 deletions(-) diff --git a/packages/server/src/registration/verifications/verifyAttestationAndroidKey.test.ts b/packages/server/src/registration/verifications/verifyAttestationAndroidKey.test.ts index bd10add6..1eca7de5 100644 --- a/packages/server/src/registration/verifications/verifyAttestationAndroidKey.test.ts +++ b/packages/server/src/registration/verifications/verifyAttestationAndroidKey.test.ts @@ -1,18 +1,35 @@ import { assertEquals } from '@std/assert'; +import { FakeTime } from '@std/testing/time'; import { SettingsService } from '../../services/settingsService.ts'; import { verifyRegistrationResponse } from '../verifyRegistrationResponse.ts'; +import { Google_Hardware_Attestation_Root_2 } from '../../services/defaultRootCerts/android-key.ts'; /** * Clear out root certs for android-key since responses were captured from FIDO Conformance testing * and have cert paths that can't be validated with known root certs from Google */ -SettingsService.setRootCertificates({ - identifier: 'android-key', - certificates: [], -}); -Deno.test('should verify Android KeyStore response', async () => { +Deno.test('should verify Android Keystore response from FIDO Conformance in 2020', async () => { + SettingsService.setRootCertificates({ + identifier: 'android-key', + certificates: [], + }); + + /** + * Faking time to something that'll satisfy all of these ranges: + * + * { + * notBefore: 1970-02-01T00:00:00.000Z, + * notAfter: 2099-01-31T23:59:59.000Z + * } + * { + * notBefore: 2019-04-25T05:49:32.000Z, + * notAfter: 2046-09-10T05:49:32.000Z + * } + */ + const mockDate = new FakeTime(new Date('2025-01-26T19:00:00.000Z')); + const verification = await verifyRegistrationResponse({ response: { id: 'V51GE29tGbhby7sbg1cZ_qL8V8njqEsXpAnwQBobvgw', @@ -34,4 +51,66 @@ Deno.test('should verify Android KeyStore response', async () => { }); assertEquals(verification.verified, true); + + mockDate.restore(); +}); + +Deno.test('should verify Android Keystore response from a Pixel 8a in January 2025', async () => { + SettingsService.setRootCertificates({ + identifier: 'android-key', + certificates: [Google_Hardware_Attestation_Root_2], + }); + + /** + * Faking time to something that'll satisfy all of these ranges: + * + * { + * notBefore: 1970-01-01T00:00:00.000Z, + * notAfter: 2048-01-01T00:00:00.000Z + * } + * { + * notBefore: 2025-01-07T17:08:43.000Z, + * notAfter: 2025-02-02T10:35:27.000Z + * } + * { + * notBefore: 2024-12-09T06:28:53.000Z, + * notAfter: 2025-02-17T06:28:52.000Z + * } + * { + * notBefore: 2022-01-26T22:49:45.000Z, + * notAfter: 2037-01-22T22:49:45.000Z + * } + * { + * notBefore: 2019-11-22T20:37:58.000Z, + * notAfter: 2034-11-18T20:37:58.000Z + * } + */ + const mockDate = new FakeTime(new Date('2025-02-02T10:00:00.000Z')); + + const verification = await verifyRegistrationResponse({ + response: { + id: 'AYNe4CBKc8H30FuAb8uaht6JbEQfbSBnS0SX7B6MFg8ofI92oR5lheRDJCgwY-JqB_QSJtezdhMbf8Wzt_La5N0', + rawId: + 'AYNe4CBKc8H30FuAb8uaht6JbEQfbSBnS0SX7B6MFg8ofI92oR5lheRDJCgwY-JqB_QSJtezdhMbf8Wzt_La5N0', + response: { + attestationObject: + 'o2NmbXRrYW5kcm9pZC1rZXlnYXR0U3RtdKNjYWxnJmNzaWdYSDBGAiEAs9Aufj5f5HyLKEFsgfmqyaXfAih-hGuTJqgmxZGijzYCIQDAMddAq1gwH3MtesYR6WE6IAockRz8ilR7CFw_kgdmv2N4NWOFWQLQMIICzDCCAnKgAwIBAgIBATAKBggqhkjOPQQDAjA5MSkwJwYDVQQDEyBkNjAyYTAzYTY3MmQ4NjViYTVhNDg1ZTMzYTIwN2M3MzEMMAoGA1UEChMDVEVFMB4XDTcwMDEwMTAwMDAwMFoXDTQ4MDEwMTAwMDAwMFowHzEdMBsGA1UEAxMUQW5kcm9pZCBLZXlzdG9yZSBLZXkwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATXVi3-n-rBsrP3A4Pj9P8e6PNh3eNdC38PaFiCZyMWdUVA6PbE6985PSUDDcnk3Knnpyc66J_HFOu_geuqiWtAo4IBgzCCAX8wDgYDVR0PAQH_BAQDAgeAMIIBawYKKwYBBAHWeQIBEQSCAVswggFXAgIBLAoBAQICASwKAQEEIFZS4txFVJqW-Wr6IlUC-H-twIpgvAITksC-jFBi_V9eBAAwd7-FPQgCBgGUcHc4or-FRWcEZTBjMT0wGwQWY29tLmdvb2dsZS5hbmRyb2lkLmdzZgIBIzAeBBZjb20uZ29vZ2xlLmFuZHJvaWQuZ21zAgQO6jzjMSIEIPD9bFtBDyXLJcO1M0bIly-uMPjudBHfkQSArWstYNuDMIGpoQUxAwIBAqIDAgEDowQCAgEApQUxAwIBBKoDAgEBv4N4AwIBA7-DeQMCAQq_hT4DAgEAv4VATDBKBCCd4l-wK7VTDUQUnRSEN8guJn5VcyJTCqbwOwrC6Skx2gEB_woBAAQg6y0px0ZXc5v2bsVb45w-6IiMbXzp3gyHIWKS1mbz6gu_hUEFAgMCSfC_hUIFAgMDFwW_hU4GAgQBNP35v4VPBgIEATT9-TAKBggqhkjOPQQDAgNIADBFAiEAzNz6wyTo4t5ixo9G4zXPwh4zSB9F854sU_KDGTf0dxYCICaQVSWzWgTZLQYv13MXJJee8S8_luQB3W5lPPzP0exsWQHjMIIB3zCCAYWgAwIBAgIRANYCoDpnLYZbpaSF4zogfHMwCgYIKoZIzj0EAwIwKTETMBEGA1UEChMKR29vZ2xlIExMQzESMBAGA1UEAxMJRHJvaWQgQ0EzMB4XDTI1MDEwNzE3MDg0M1oXDTI1MDIwMjEwMzUyN1owOTEpMCcGA1UEAxMgZDYwMmEwM2E2NzJkODY1YmE1YTQ4NWUzM2EyMDdjNzMxDDAKBgNVBAoTA1RFRTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABFPbPYqm91rYvZVCBdFaHRMg0tw7U07JA1EcD9ZP4d0lK2NFM4A0wGKS4jbTR_bu7NTt_YyF388S0PWAJTluqnOjfjB8MB0GA1UdDgQWBBSXyrsZ_A1NnJGRq0sm2G9nm-NC5zAfBgNVHSMEGDAWgBTFUX4F2MtjWykYrAIa8sh9bBL-kjAPBgNVHRMBAf8EBTADAQH_MA4GA1UdDwEB_wQEAwICBDAZBgorBgEEAdZ5AgEeBAuiAQgDZkdvb2dsZTAKBggqhkjOPQQDAgNIADBFAiEAysd6JDoI8X4NEdrRwUwtIAy-hLxSEKUVS2XVWS2CP04CIFNQQzM4TkA_xaZj8KyiS61nb-aOBP35tlA34JCOlv9nWQHcMIIB2DCCAV2gAwIBAgIUAIUK9vrO5iIEbQx0izdwqlWwtk0wCgYIKoZIzj0EAwMwKTETMBEGA1UEChMKR29vZ2xlIExMQzESMBAGA1UEAxMJRHJvaWQgQ0EyMB4XDTI0MTIwOTA2Mjg1M1oXDTI1MDIxNzA2Mjg1MlowKTETMBEGA1UEChMKR29vZ2xlIExMQzESMBAGA1UEAxMJRHJvaWQgQ0EzMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPjbr-yt9xhgcbKLXoN3RK-1FcCjwIpeMPJZjayW0dqNtFflHp2smO0DxN_6x7M7NAGbcC9lM1_E-N6z51ODv-6NjMGEwDgYDVR0PAQH_BAQDAgIEMA8GA1UdEwEB_wQFMAMBAf8wHQYDVR0OBBYEFMVRfgXYy2NbKRisAhryyH1sEv6SMB8GA1UdIwQYMBaAFKYLhqTwyH8ztWE5Ys0956c6QoNIMAoGCCqGSM49BAMDA2kAMGYCMQCuzU0wV_NkOQzgqzyqP66SJN6lilrU-NDVU6qNCnbFsUoZQOm4wBwUw7LqfoUhx7YCMQDFEvqHfc2hwN2J4I9Z4rTHiLlsy6gA33WvECzIZmVMpKcyEiHlm4c9XR0nVkAjQ_5ZA4QwggOAMIIBaKADAgECAgoDiCZnYGWJloYOMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNVBAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMjIwMTI2MjI0OTQ1WhcNMzcwMTIyMjI0OTQ1WjApMRMwEQYDVQQKEwpHb29nbGUgTExDMRIwEAYDVQQDEwlEcm9pZCBDQTIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT72ZtYJ0I2etFhouvtVs0sBzvYsx8thNCZV1wsDPvsMDSTPij-M1wBFD00OUn2bfU5b7K2_t2NkXc2-_V9g--mdb6SoRGmJ_AG9ScY60LKSA7iPT7gZ_5-q0tnEPPZJCqjZjBkMB0GA1UdDgQWBBSmC4ak8Mh_M7VhOWLNPeenOkKDSDAfBgNVHSMEGDAWgBQ2YeEAfIgFCVGLRGxH_xpMyepPEjASBgNVHRMBAf8ECDAGAQH_AgECMA4GA1UdDwEB_wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEArpB2eLbKHNcS6Q3Td3N7ZCgVLN0qA7CboM-Ftu4YYAcHxh-e_sk7T7XOg5S4d9a_DD7mIXgENSBPB_fVqCnBaSDKNJ3nUuC1_9gcT95p4kKJo0tqcsWw8WgKVJhNuZCN7d_ziHLiRRcrKtaj944THzsy7vB-pSai7gTah_RJrDQI91bDUJgld8_p_QAbVnYA8o-msO0sRKxgF1V5QuBwBTfpdkqshqL3nwBm0sofqI_rM-JOQava3-IurHvfkzioiOJ0uFJnBGVjpZFwGwsmyKwzl-3qRKlkHggAOKt3lQQ4GiJnOCm10JrxPa2Za0K6_kyk6YyvvRcFNai5ej3nMKJPg-eeG2nST6N6ePFuaeoNQnD4XkagGFEQYzcqvsdFsmsbUFMghFl7zEVYdscuSgCG939wxW1JgKyG5ce7CI40328w9IuOf8mUS_W3i4jSfxqCJbegyo_SKDpDILnhJUBy0T3fN8mv9AyO0uoJBlvnogIVv2SdpYUt92vyOiGMy3Jx_ZRWjIRa7iIV3VnjLI__pgCrXQLMinZWEWsxVxg25nrk8u32nZd67DJN3k2FufRbsmHZly9CLo0P79lkIEC3rifLqqJeDyHQNaBMUC6BSDZ5RJCtMjSZw2xL5z0X9_zBsKVPkMW61hMhKzVmYNLe1DJQANRP-enru5i1oXlZBSAwggUcMIIDBKADAgECAgkA1Q_yW6Py1rMwDQYJKoZIhvcNAQELBQAwGzEZMBcGA1UEBRMQZjkyMDA5ZTg1M2I2YjA0NTAeFw0xOTExMjIyMDM3NThaFw0zNDExMTgyMDM3NThaMBsxGTAXBgNVBAUTEGY5MjAwOWU4NTNiNmIwNDUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCvtseCK7GnAewrtC6LzFQWY6vvmC8yx391MQMMl1JLG1_oCfvHKqlFH3Q8vZpvEzV0SqVed_a2rDU17hfCXmOVF92ckuY3SlPL_iWPj_u2_RKTeKIqTKmcRS1HpZ8yAfRBl8oczX52L7L1MVG2_rL__Stv5P5bxr2ew0v-CCOdqvzrjrWo7Ss6zZxeOneQ4bUUQnkxWYWYEa2esqlrvdelfJOpHEH8zSfWf9b2caoLgVJhrThPo3lEhkYE3bPYxPkgoZsWVsLxStbQPFbsBgiZBBwe0aX-bTRAtVa60dChUlicU-VdNwdi8BIu75GGGxsObEyAknSZwOm-wLg-O8H5PHLASWBLvS8TReYsP44m2-wGyUdm88EoI51PQxL62BI4h-Br7PVnWDv4NVqB_uq6-ZqDyN8-KjIq_Gcr8SCxNRWLaCHOrzCbbu53-YgzsBjaoQ5FHwajdNUHgfNZCClmu3eLkwiUJpjnTgvNJGKKAcLMA-UfCz5bSsHk356vn_akkqd8FIOIKIUBW0Is5nuAuIybSOE7YHq1Rccj_4xE-PLTaLn2Ug0xFF6_noYq1x32o7_SRQlZ1lN0DZehLzaLE-9m1dClSm4vXZpv70RoMrxnhEclhh8JPdDm80BdqJZD7w9NabZCAFH9uTBJZz42lQWA0830-9CLxYSDlSYAYwIDAQABo2MwYTAdBgNVHQ4EFgQUNmHhAHyIBQlRi0RsR_8aTMnqTxIwHwYDVR0jBBgwFoAUNmHhAHyIBQlRi0RsR_8aTMnqTxIwDwYDVR0TAQH_BAUwAwEB_zAOBgNVHQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADggIBAE4xoFzyi6Zdva-hztcJae5cqEEErd7YowbPf23uUDdddF7ZkssCQsznLcnu1RGR_lrVK61907JcCZ4TpJGjzdSHpazOh2YyTErkYzgkaue3ikGKy7mKBcTJ1pbuqrYJ0LoM4aMb6YSQ3z9MDqndyegv-w_LPp692MuVJ4nysUEfrFbIhkJutylgQnNdpQ4RrHFfGBjPn9xOJUo3YzUbaiRAFQhhJjpuMQvhpQ3lx-juiA_dS-WISjcSjRiDC7NHa_QpHoLVxmpklJOeCEgL-8APfYp01D5zc36-XY5OxRUwLUaJaSeA3HU47X6Rdb5hOedNQ604izBQ_9Wp3lJiAAiYwB9jxT3-IiCRCPpPZboWxJzL3gg318WETVS3OYugEi5QWxVckxPP4m5y2H4iqhYW5r2_VH3f-T3ynjWmO0Vf4fwOyVWB8_T3u-O7goOWo3rjFXWCvDdkuXgKI578D3Wh4ubZQc6rrCfd6wHivYQhApvqNNUa7mxgJx1alevQBRWpwAE92Av4fuomC4HDT2iObrE0ivDY6hysMqy52T-iSv8DCoTI8rD1acyVCAsgrDWs4MbY29T2hHcZUZ0yRQFm60vxW4WQRFAa3q9DY4LDSxXjtUyS5htpwr_HJkWJFys8k9vjXOBtCP1cATIsoId7HRJ0OvH61ZQOobwC3YkcaGF1dGhEYXRhWMVJlg3liA6MaHQ0Fw9kdmBbj-SuuaKGMseZXPO6gx2XY0UAAAAAuT_ZYfLmRi-xIoIAIkfeeABBAYNe4CBKc8H30FuAb8uaht6JbEQfbSBnS0SX7B6MFg8ofI92oR5lheRDJCgwY-JqB_QSJtezdhMbf8Wzt_La5N2lAQIDJiABIVgg11Yt_p_qwbKz9wOD4_T_HujzYd3jXQt_D2hYgmcjFnUiWCBFQOj2xOvfOT0lAw3J5Nyp56cnOuifxxTrv4HrqolrQA', + clientDataJSON: + 'eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoidDRMV0kwaVlKU1RXUGw5V1hVZE5oZEhBbnJQRExGOWVXQVA5bEhnbUhQOCIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODAwMCIsImNyb3NzT3JpZ2luIjpmYWxzZX0', + }, + type: 'public-key', + clientExtensionResults: { + credProps: { rk: false }, + }, + authenticatorAttachment: 'platform', + }, + expectedChallenge: 't4LWI0iYJSTWPl9WXUdNhdHAnrPDLF9eWAP9lHgmHP8', + expectedOrigin: 'http://localhost:8000', + expectedRPID: 'localhost', + requireUserVerification: false, + }); + + assertEquals(verification.verified, true); + + mockDate.restore(); });