diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 3c10d4ff5..a49f2e44b 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1501,11 +1501,6 @@ parameters: count: 1 path: src/webauthn/src/AttestationStatement/AppleAttestationStatementSupport.php - - - message: "#^Cannot access offset 1 on array\\|false\\.$#" - count: 3 - path: src/webauthn/src/AttestationStatement/AttestationObjectLoader.php - - message: "#^Parameter \\#1 \\$data of static method Webauthn\\\\TrustPath\\\\TrustPathLoader\\:\\:loadTrustPath\\(\\) expects array, mixed given\\.$#" count: 1 @@ -1836,6 +1831,31 @@ parameters: count: 1 path: src/webauthn/src/AuthenticatorAttestationResponseValidator.php + - + message: "#^Cannot access offset 1 on array\\|false\\.$#" + count: 2 + path: src/webauthn/src/AuthenticatorDataLoader.php + + - + message: "#^Parameter \\#1 \\$search of function str_replace expects array\\|string, string\\|false given\\.$#" + count: 1 + path: src/webauthn/src/AuthenticatorDataLoader.php + + - + message: "#^Parameter \\#2 \\$callback of function array_reduce expects callable\\(string, mixed\\)\\: string, Closure\\(string, string\\)\\: non\\-empty\\-string given\\.$#" + count: 1 + path: src/webauthn/src/AuthenticatorDataLoader.php + + - + message: "#^Parameter \\#2 \\$needle of function mb_strpos expects string, string\\|false given\\.$#" + count: 1 + path: src/webauthn/src/AuthenticatorDataLoader.php + + - + message: "#^Parameter \\#2 \\$replace of function str_replace expects array\\|string, string\\|false given\\.$#" + count: 1 + path: src/webauthn/src/AuthenticatorDataLoader.php + - message: """ #^Access to deprecated property \\$requireResidentKey of class Webauthn\\\\AuthenticatorSelectionCriteria\\: @@ -1890,11 +1910,6 @@ parameters: count: 1 path: src/webauthn/src/PublicKeyCredentialDescriptorCollection.php - - - message: "#^Cannot access offset 1 on array\\|false\\.$#" - count: 2 - path: src/webauthn/src/PublicKeyCredentialLoader.php - - message: "#^Parameter \\#1 \\$json of method Webauthn\\\\PublicKeyCredentialLoader\\:\\:loadArray\\(\\) expects array, mixed given\\.$#" count: 1 diff --git a/src/webauthn/src/AttestationStatement/AttestationObjectLoader.php b/src/webauthn/src/AttestationStatement/AttestationObjectLoader.php index 2b85a99fb..fecfe9855 100644 --- a/src/webauthn/src/AttestationStatement/AttestationObjectLoader.php +++ b/src/webauthn/src/AttestationStatement/AttestationObjectLoader.php @@ -6,19 +6,13 @@ use function array_key_exists; use CBOR\Decoder; -use CBOR\MapObject; use CBOR\Normalizable; use function is_array; -use function ord; use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; -use Symfony\Component\Uid\Uuid; use Throwable; -use function unpack; -use Webauthn\AttestedCredentialData; -use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputsLoader; -use Webauthn\AuthenticatorData; +use Webauthn\AuthenticatorDataLoader; use Webauthn\Event\AttestationObjectLoaded; use Webauthn\Exception\InvalidDataException; use Webauthn\MetadataService\CanLogData; @@ -29,12 +23,6 @@ class AttestationObjectLoader implements CanDispatchEvents, CanLogData { - private const FLAG_AT = 0b01000000; - - private const FLAG_ED = 0b10000000; - - private readonly Decoder $decoder; - private LoggerInterface $logger; private EventDispatcherInterface $dispatcher; @@ -42,7 +30,6 @@ class AttestationObjectLoader implements CanDispatchEvents, CanLogData public function __construct( private readonly AttestationStatementSupportManager $attestationStatementSupportManager ) { - $this->decoder = Decoder::create(); $this->logger = new NullLogger(); $this->dispatcher = new NullEventDispatcher(); } @@ -65,7 +52,7 @@ public function load(string $data): AttestationObject ]); $decodedData = Base64::decode($data); $stream = new StringStream($decodedData); - $parsed = $this->decoder->decode($stream); + $parsed = Decoder::create()->decode($stream); $this->logger->info('Loading the Attestation Statement'); $parsed instanceof Normalizable || throw InvalidDataException::create( @@ -94,7 +81,6 @@ public function load(string $data): AttestationObject $attestationObject, 'Invalid attestation object' ); - $authData = $attestationObject['authData']; $attestationStatementSupport = $this->attestationStatementSupportManager->get($attestationObject['fmt']); $attestationStatement = $attestationStatementSupport->load($attestationObject); @@ -102,61 +88,10 @@ public function load(string $data): AttestationObject $this->logger->debug('Attestation Statement loaded', [ 'attestationStatement' => $attestationStatement, ]); + $authData = $attestationObject['authData']; + $authDataLoader = AuthenticatorDataLoader::create(); + $authenticatorData = $authDataLoader->load($authData); - $authDataStream = new StringStream($authData); - $rp_id_hash = $authDataStream->read(32); - $flags = $authDataStream->read(1); - $signCount = $authDataStream->read(4); - $signCount = unpack('N', $signCount); - $this->logger->debug(sprintf('Signature counter: %d', $signCount[1])); - - $attestedCredentialData = null; - if (0 !== (ord($flags) & self::FLAG_AT)) { - $this->logger->info('Attested Credential Data is present'); - $aaguid = Uuid::fromBinary($authDataStream->read(16)); - $credentialLength = $authDataStream->read(2); - $credentialLength = unpack('n', $credentialLength); - $credentialId = $authDataStream->read($credentialLength[1]); - $credentialPublicKey = $this->decoder->decode($authDataStream); - $credentialPublicKey instanceof MapObject || throw InvalidDataException::create( - $credentialPublicKey, - 'The data does not contain a valid credential public key.' - ); - $attestedCredentialData = new AttestedCredentialData( - $aaguid, - $credentialId, - (string) $credentialPublicKey - ); - $this->logger->info('Attested Credential Data loaded'); - $this->logger->debug('Attested Credential Data loaded', [ - 'at' => $attestedCredentialData, - ]); - } - - $extension = null; - if (0 !== (ord($flags) & self::FLAG_ED)) { - $this->logger->info('Extension Data loaded'); - $extension = $this->decoder->decode($authDataStream); - $extension = AuthenticationExtensionsClientOutputsLoader::load($extension); - $this->logger->info('Extension Data loaded'); - $this->logger->debug('Extension Data loaded', [ - 'ed' => $extension, - ]); - } - $authDataStream->isEOF() || throw InvalidDataException::create( - null, - 'Invalid authentication data. Presence of extra bytes.' - ); - $authDataStream->close(); - - $authenticatorData = new AuthenticatorData( - $authData, - $rp_id_hash, - $flags, - $signCount[1], - $attestedCredentialData, - $extension - ); $attestationObject = new AttestationObject($data, $attestationStatement, $authenticatorData); $this->logger->info('Attestation Object loaded'); $this->logger->debug('Attestation Object', [ diff --git a/src/webauthn/src/AuthenticatorData.php b/src/webauthn/src/AuthenticatorData.php index 89e241343..9b0f9fc53 100644 --- a/src/webauthn/src/AuthenticatorData.php +++ b/src/webauthn/src/AuthenticatorData.php @@ -12,17 +12,17 @@ */ class AuthenticatorData { - private const FLAG_UP = 0b00000001; + final public const FLAG_UP = 0b00000001; - private const FLAG_RFU1 = 0b00000010; + final public const FLAG_RFU1 = 0b00000010; - private const FLAG_UV = 0b00000100; + final public const FLAG_UV = 0b00000100; - private const FLAG_RFU2 = 0b00111000; + final public const FLAG_RFU2 = 0b00111000; - private const FLAG_AT = 0b01000000; + final public const FLAG_AT = 0b01000000; - private const FLAG_ED = 0b10000000; + final public const FLAG_ED = 0b10000000; public function __construct( protected string $authData, diff --git a/src/webauthn/src/AuthenticatorDataLoader.php b/src/webauthn/src/AuthenticatorDataLoader.php new file mode 100644 index 000000000..43dd809c1 --- /dev/null +++ b/src/webauthn/src/AuthenticatorDataLoader.php @@ -0,0 +1,117 @@ +decoder = Decoder::create(); + } + + public static function create(): self + { + return new self(); + } + + public function load(string $authData): AuthenticatorData + { + $authData = $this->fixIncorrectEdDSAKey($authData); + $authDataStream = new StringStream($authData); + $rp_id_hash = $authDataStream->read(32); + $flags = $authDataStream->read(1); + $signCount = $authDataStream->read(4); + $signCount = unpack('N', $signCount); + + $attestedCredentialData = null; + if (0 !== (ord($flags) & AuthenticatorData::FLAG_AT)) { + $aaguid = Uuid::fromBinary($authDataStream->read(16)); + $credentialLength = $authDataStream->read(2); + $credentialLength = unpack('n', $credentialLength); + $credentialId = $authDataStream->read($credentialLength[1]); + $credentialPublicKey = $this->decoder->decode($authDataStream); + $credentialPublicKey instanceof MapObject || throw InvalidDataException::create( + $authData, + 'The data does not contain a valid credential public key.' + ); + $attestedCredentialData = new AttestedCredentialData( + $aaguid, + $credentialId, + (string) $credentialPublicKey + ); + } + + $extension = null; + if (0 !== (ord($flags) & AuthenticatorData::FLAG_ED)) { + $extension = $this->decoder->decode($authDataStream); + $extension = AuthenticationExtensionsClientOutputsLoader::load($extension); + } + $authDataStream->isEOF() || throw InvalidDataException::create( + $authData, + 'Invalid authentication data. Presence of extra bytes.' + ); + $authDataStream->close(); + + return new AuthenticatorData( + $authData, + $rp_id_hash, + $flags, + $signCount[1], + $attestedCredentialData, + $extension + ); + } + + private function fixIncorrectEdDSAKey(string $data): string + { + $needle = hex2bin('a301634f4b500327206745643235353139'); + $correct = hex2bin('a401634f4b500327206745643235353139'); + $position = mb_strpos($data, $needle, 0, '8bit'); + if ($position === false) { + return $data; + } + + $begin = mb_substr($data, 0, $position, '8bit'); + $end = mb_substr($data, $position, null, '8bit'); + $end = str_replace($needle, $correct, $end); + $cbor = new StringStream($end); + $badKey = $this->decoder->decode($cbor); + + ($badKey instanceof MapObject && $cbor->isEOF()) || throw InvalidDataException::create( + $end, + 'Invalid authentication data. Presence of extra bytes.' + ); + $badX = $badKey->get(-2); + $badX instanceof ListObject || throw InvalidDataException::create($end, 'Invalid authentication data.'); + $keyBytes = array_reduce( + $badX->normalize(), + static fn (string $carry, string $item): string => $carry . chr((int) $item), + '' + ); + $correctX = ByteStringObject::create($keyBytes); + $correctKey = MapObject::create() + ->add(UnsignedIntegerObject::create(1), ByteStringObject::create('OKP')) + ->add(UnsignedIntegerObject::create(3), NegativeIntegerObject::create(-8)) + ->add(NegativeIntegerObject::create(-1), TextStringObject::create('Ed25519')) + ->add(NegativeIntegerObject::create(-2), $correctX); + + return $begin . $correctKey; + } +} diff --git a/src/webauthn/src/PublicKeyCredentialLoader.php b/src/webauthn/src/PublicKeyCredentialLoader.php index f745e26bc..6f73696d2 100644 --- a/src/webauthn/src/PublicKeyCredentialLoader.php +++ b/src/webauthn/src/PublicKeyCredentialLoader.php @@ -5,38 +5,25 @@ namespace Webauthn; use function array_key_exists; -use CBOR\Decoder; -use CBOR\MapObject; use function is_array; use function is_string; use const JSON_THROW_ON_ERROR; -use function ord; use ParagonIE\ConstantTime\Base64UrlSafe; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; -use Symfony\Component\Uid\Uuid; use Throwable; -use function unpack; use Webauthn\AttestationStatement\AttestationObjectLoader; -use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputsLoader; use Webauthn\Exception\InvalidDataException; use Webauthn\MetadataService\CanLogData; use Webauthn\Util\Base64; class PublicKeyCredentialLoader implements CanLogData { - private const FLAG_AT = 0b01000000; - - private const FLAG_ED = 0b10000000; - - private readonly Decoder $decoder; - private LoggerInterface $logger; public function __construct( private readonly AttestationObjectLoader $attestationObjectLoader ) { - $this->decoder = Decoder::create(); $this->logger = new NullLogger(); } @@ -145,7 +132,7 @@ private function createResponse(array $response): AuthenticatorResponse case array_key_exists('attestationObject', $response): is_string($response['attestationObject']) || throw InvalidDataException::create( $response, - 'Invalid data. The parameter "attestationObject " is invalid' + 'Invalid data. The parameter "attestationObject" is invalid' ); $attestationObject = $this->attestationObjectLoader->load($response['attestationObject']); @@ -153,50 +140,9 @@ private function createResponse(array $response): AuthenticatorResponse $response['clientDataJSON'] ), $attestationObject); case array_key_exists('authenticatorData', $response) && array_key_exists('signature', $response): + $authDataLoader = AuthenticatorDataLoader::create(); $authData = Base64UrlSafe::decodeNoPadding($response['authenticatorData']); - - $authDataStream = new StringStream($authData); - $rp_id_hash = $authDataStream->read(32); - $flags = $authDataStream->read(1); - $signCount = $authDataStream->read(4); - $signCount = unpack('N', $signCount); - - $attestedCredentialData = null; - if (0 !== (ord($flags) & self::FLAG_AT)) { - $aaguid = Uuid::fromBinary($authDataStream->read(16)); - $credentialLength = $authDataStream->read(2); - $credentialLength = unpack('n', $credentialLength); - $credentialId = $authDataStream->read($credentialLength[1]); - $credentialPublicKey = $this->decoder->decode($authDataStream); - $credentialPublicKey instanceof MapObject || throw InvalidDataException::create( - $authData, - 'The data does not contain a valid credential public key.' - ); - $attestedCredentialData = new AttestedCredentialData( - $aaguid, - $credentialId, - (string) $credentialPublicKey - ); - } - - $extension = null; - if (0 !== (ord($flags) & self::FLAG_ED)) { - $extension = $this->decoder->decode($authDataStream); - $extension = AuthenticationExtensionsClientOutputsLoader::load($extension); - } - $authDataStream->isEOF() || throw InvalidDataException::create( - $authData, - 'Invalid authentication data. Presence of extra bytes.' - ); - $authDataStream->close(); - $authenticatorData = new AuthenticatorData( - $authData, - $rp_id_hash, - $flags, - $signCount[1], - $attestedCredentialData, - $extension - ); + $authenticatorData = $authDataLoader->load($authData); try { $signature = Base64::decode($response['signature']); diff --git a/tests/library/Functional/PublicKeyCreationCeremonyTest.php b/tests/library/Functional/PublicKeyCreationCeremonyTest.php new file mode 100644 index 000000000..14343ca96 --- /dev/null +++ b/tests/library/Functional/PublicKeyCreationCeremonyTest.php @@ -0,0 +1,47 @@ +getPublicKeyCredentialLoader() + ->load($response); + static::assertInstanceOf(AuthenticatorAttestationResponse::class, $publicKeyCredential->getResponse()); + $source = $this->getAuthenticatorAttestationResponseValidator() + ->check($publicKeyCredential->getResponse(), $publicKeyCredentialCreationOptions, 'localhost'); + + static::assertSame(hex2bin($keyId), $source->getPublicKeyCredentialId()); + static::assertSame($type, $source->getAttestationType()); + } + + public static function getPublicKeyCredentialCreationOptions(): iterable + { + yield 'anAuthenticatorAttestationResponseWithSubdomainCanBeVerified' => [ + '{"rp":{"name":"Webauthn Demo","id":"spomky-labs.com"},"pubKeyCredParams":[{"type":"public-key","alg":-7},{"type":"public-key","alg":-257}],"challenge":"xGQ2h2bK2tbn96eNutboG53FvUT30IV\/2ThoeKPu778=","attestation":"indirect","user":{"name":"fff","id":"MTY3YzljMjUtZThiYy00MzVmLTlhYmMtNDYxMWY5OTg3ODU4","displayName":"FFF"},"authenticatorSelection":{"requireResidentKey":false,"userVerification":"preferred"},"timeout":60000}', + '{"id":"-cGSjQwC4UBTsh2Mw6guep2uTdLXOExla3QJrVpByOkEWJaOljo54PWOazmHtxBuV5DeysX7qjohoGYK2YibdA","type":"public-key","rawId":"+cGSjQwC4UBTsh2Mw6guep2uTdLXOExla3QJrVpByOkEWJaOljo54PWOazmHtxBuV5DeysX7qjohoGYK2YibdA==","response":{"clientDataJSON":"eyJjaGFsbGVuZ2UiOiJ4R1EyaDJiSzJ0Ym45NmVOdXRib0c1M0Z2VVQzMElWXzJUaG9lS1B1Nzc4Iiwib3JpZ2luIjoiaHR0cHM6Ly93ZWJhdXRobi5zcG9ta3ktbGFicy5jb20iLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0","attestationObject":"o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEcwRQIhALotmA9bjE8DC5afT4C6QJHwB2TDCgh+/DSpIuxt1Z2dAiBzVRmktx9Ur1sjxZJvjhAnzZCRDicD/h2dyd8a+MkVGWN4NWOBWQLCMIICvjCCAaagAwIBAgIEdIb9wjANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowbzELMAkGA1UEBhMCU0UxEjAQBgNVBAoMCVl1YmljbyBBQjEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjEoMCYGA1UEAwwfWXViaWNvIFUyRiBFRSBTZXJpYWwgMTk1NTAwMzg0MjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJVd8633JH0xde/9nMTzGk6HjrrhgQlWYVD7OIsuX2Unv1dAmqWBpQ0KxS8YRFwKE1SKE1PIpOWacE5SO8BN6+2jbDBqMCIGCSsGAQQBgsQKAgQVMS4zLjYuMS40LjEuNDE0ODIuMS4xMBMGCysGAQQBguUcAgEBBAQDAgUgMCEGCysGAQQBguUcAQEEBBIEEPigEfOMCk0VgAYXER+e3H0wDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAMVxIgOaaUn44Zom9af0KqG9J655OhUVBVW+q0As6AIod3AH5bHb2aDYakeIyyBCnnGMHTJtuekbrHbXYXERIn4aKdkPSKlyGLsA/A+WEi+OAfXrNVfjhrh7iE6xzq0sg4/vVJoywe4eAJx0fS+Dl3axzTTpYl71Nc7p/NX6iCMmdik0pAuYJegBcTckE3AoYEg4K99AM/JaaKIblsbFh8+3LxnemeNf7UwOczaGGvjS6UzGVI0Odf9lKcPIwYhuTxM5CaNMXTZQ7xq4/yTfC3kPWtE4hFT34UJJflZBiLrxG4OsYxkHw/n5vKgmpspB3GfYuYTWhkDKiE8CYtyg87mhhdXRoRGF0YVjEzJVnqxWDxWv1dKGpGXUmkUO/MGMFSRae4FA9zhbiR3VBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQPnBko0MAuFAU7IdjMOoLnqdrk3S1zhMZWt0Ca1aQcjpBFiWjpY6OeD1jms5h7cQbleQ3srF+6o6IaBmCtmIm3SlAQIDJiABIVggZ9kAaP2QIzTF401zK9+GnJ9t5P5nZMd+7Uq2dj9zrDciWCAYPJnCkmc15U8txqQB+CdSKUhpVrhITkmBPycz6nzp8g=="}}', + 'f9c1928d0c02e14053b21d8cc3a82e7a9dae4dd2d7384c656b7409ad5a41c8e90458968e963a39e0f58e6b3987b7106e5790decac5fbaa3a21a0660ad9889b74', + 'basic', + ]; + yield 'wrongEdDSAlgorithmIsFixed' => [ + '{"rp": {"name": "Tuleap", "id": "tuleap-web.tuleap-aio-dev.docker"}, "user": {"name": "admin", "id": "MTAx", "displayName": "Site Administrator"}, "challenge": "oq1vpg74u-TmqW3Dv2LwU_jH00NQf65OqpMhrvr7yPY", "pubKeyCredParams": [{"type": "public-key", "alg": -8}, {"type": "public-key", "alg": -7}, {"type": "public-key", "alg": -257}], "attestation": "none"}', + '{"clientExtensionResults": {}, "id": "ma2Y7hbtrzJtoDR4N2PkazhnrO6_58gZ8mO8epx-6aCnR9Jtio8Ge1w0_msV7HniYmLIH9yxOW8Yu_9ze_y8oj-MehAozj1jFTsjlQUEc_dxdzG5uFJTn6_RnzhulEWCcZZwcvlNTYne99MpWAD31c-4IuEr-eRRV1DWSANcax0", "rawId": "ma2Y7hbtrzJtoDR4N2PkazhnrO6_58gZ8mO8epx-6aCnR9Jtio8Ge1w0_msV7HniYmLIH9yxOW8Yu_9ze_y8oj-MehAozj1jFTsjlQUEc_dxdzG5uFJTn6_RnzhulEWCcZZwcvlNTYne99MpWAD31c-4IuEr-eRRV1DWSANcax0", "response": {"attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVkBCRawLfvD1MyjfrwvZRZlmxIhDbnhAYq58TqWkGOOpv2oRQAAAAIvwFefgRNH6rEWu1qNuSAqAICZrZjuFu2vMm2gNHg3Y-RrOGes7r_nyBnyY7x6nH7poKdH0m2KjwZ7XDT-axXseeJiYsgf3LE5bxi7_3N7_LyiP4x6ECjOPWMVOyOVBQRz93F3Mbm4UlOfr9GfOG6URYJxlnBy-U1Nid730ylYAPfVz7gi4Sv55FFXUNZIA1xrHaMBY09LUAMnIGdFZDI1NTE5IZggCBjXGDcYzBgpGFwYlBgcGJYYTxjdGOYY8BjyGL4YPxg7GEgYfBh_GCIYKxhgChgmGIQYkhhQGH0Y1hjoGIk", "clientDataJSON": "eyJjaGFsbGVuZ2UiOiJvcTF2cGc3NHUtVG1xVzNEdjJMd1VfakgwME5RZjY1T3FwTWhydnI3eVBZIiwib3JpZ2luIjoiaHR0cHM6Ly90dWxlYXAtd2ViLnR1bGVhcC1haW8tZGV2LmRvY2tlciIsInR5cGUiOiJ3ZWJhdXRobi5jcmVhdGUifQ"}, "type": "public-key"}', + '99ad98ee16edaf326da034783763e46b3867aceebfe7c819f263bc7a9c7ee9a0a747d26d8a8f067b5c34fe6b15ec79e26262c81fdcb1396f18bbff737bfcbca23f8c7a1028ce3d63153b2395050473f7717731b9b852539fafd19f386e94458271967072f94d4d89def7d3295800f7d5cfb822e12bf9e4515750d648035c6b1d', + 'none', + ]; + } +} diff --git a/tests/library/Functional/SubDomainRelyingPartyTest.php b/tests/library/Functional/SubDomainRelyingPartyTest.php deleted file mode 100644 index 372a34366..000000000 --- a/tests/library/Functional/SubDomainRelyingPartyTest.php +++ /dev/null @@ -1,30 +0,0 @@ -getPublicKeyCredentialLoader() - ->load( - '{"id":"-cGSjQwC4UBTsh2Mw6guep2uTdLXOExla3QJrVpByOkEWJaOljo54PWOazmHtxBuV5DeysX7qjohoGYK2YibdA","type":"public-key","rawId":"+cGSjQwC4UBTsh2Mw6guep2uTdLXOExla3QJrVpByOkEWJaOljo54PWOazmHtxBuV5DeysX7qjohoGYK2YibdA==","response":{"clientDataJSON":"eyJjaGFsbGVuZ2UiOiJ4R1EyaDJiSzJ0Ym45NmVOdXRib0c1M0Z2VVQzMElWXzJUaG9lS1B1Nzc4Iiwib3JpZ2luIjoiaHR0cHM6Ly93ZWJhdXRobi5zcG9ta3ktbGFicy5jb20iLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0","attestationObject":"o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEcwRQIhALotmA9bjE8DC5afT4C6QJHwB2TDCgh+/DSpIuxt1Z2dAiBzVRmktx9Ur1sjxZJvjhAnzZCRDicD/h2dyd8a+MkVGWN4NWOBWQLCMIICvjCCAaagAwIBAgIEdIb9wjANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowbzELMAkGA1UEBhMCU0UxEjAQBgNVBAoMCVl1YmljbyBBQjEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjEoMCYGA1UEAwwfWXViaWNvIFUyRiBFRSBTZXJpYWwgMTk1NTAwMzg0MjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJVd8633JH0xde/9nMTzGk6HjrrhgQlWYVD7OIsuX2Unv1dAmqWBpQ0KxS8YRFwKE1SKE1PIpOWacE5SO8BN6+2jbDBqMCIGCSsGAQQBgsQKAgQVMS4zLjYuMS40LjEuNDE0ODIuMS4xMBMGCysGAQQBguUcAgEBBAQDAgUgMCEGCysGAQQBguUcAQEEBBIEEPigEfOMCk0VgAYXER+e3H0wDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAMVxIgOaaUn44Zom9af0KqG9J655OhUVBVW+q0As6AIod3AH5bHb2aDYakeIyyBCnnGMHTJtuekbrHbXYXERIn4aKdkPSKlyGLsA/A+WEi+OAfXrNVfjhrh7iE6xzq0sg4/vVJoywe4eAJx0fS+Dl3axzTTpYl71Nc7p/NX6iCMmdik0pAuYJegBcTckE3AoYEg4K99AM/JaaKIblsbFh8+3LxnemeNf7UwOczaGGvjS6UzGVI0Odf9lKcPIwYhuTxM5CaNMXTZQ7xq4/yTfC3kPWtE4hFT34UJJflZBiLrxG4OsYxkHw/n5vKgmpspB3GfYuYTWhkDKiE8CYtyg87mhhdXRoRGF0YVjEzJVnqxWDxWv1dKGpGXUmkUO/MGMFSRae4FA9zhbiR3VBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQPnBko0MAuFAU7IdjMOoLnqdrk3S1zhMZWt0Ca1aQcjpBFiWjpY6OeD1jms5h7cQbleQ3srF+6o6IaBmCtmIm3SlAQIDJiABIVggZ9kAaP2QIzTF401zK9+GnJ9t5P5nZMd+7Uq2dj9zrDciWCAYPJnCkmc15U8txqQB+CdSKUhpVrhITkmBPycz6nzp8g=="}}' - ); - static::assertInstanceOf(AuthenticatorAttestationResponse::class, $publicKeyCredential->getResponse()); - $this->getAuthenticatorAttestationResponseValidator() - ->check($publicKeyCredential->getResponse(), $publicKeyCredentialCreationOptions, 'localhost'); - } -}