Submit
Path:
~
/
/
opt
/
psa
/
phpMyAdmin
/
vendor
/
web-auth
/
webauthn-lib
/
src
/
File Content:
AuthenticatorAttestationResponseValidator.php
<?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2021 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn; use Assert\Assertion; use function count; use function in_array; use InvalidArgumentException; use function is_string; use LogicException; use Psr\Http\Message\ServerRequestInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use Ramsey\Uuid\Uuid; use function Safe\parse_url; use function Safe\sprintf; use Throwable; use Webauthn\AttestationStatement\AttestationObject; use Webauthn\AttestationStatement\AttestationStatement; use Webauthn\AttestationStatement\AttestationStatementSupportManager; use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs; use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputs; use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler; use Webauthn\CertificateChainChecker\CertificateChainChecker; use Webauthn\MetadataService\MetadataStatement; use Webauthn\MetadataService\MetadataStatementRepository; use Webauthn\MetadataService\StatusReport; use Webauthn\TokenBinding\TokenBindingHandler; use Webauthn\TrustPath\CertificateTrustPath; use Webauthn\TrustPath\EmptyTrustPath; class AuthenticatorAttestationResponseValidator { /** * @var AttestationStatementSupportManager */ private $attestationStatementSupportManager; /** * @var PublicKeyCredentialSourceRepository */ private $publicKeyCredentialSource; /** * @var TokenBindingHandler */ private $tokenBindingHandler; /** * @var ExtensionOutputCheckerHandler */ private $extensionOutputCheckerHandler; /** * @var LoggerInterface */ private $logger; /** * @var MetadataStatementRepository|null */ private $metadataStatementRepository; /** * @var CertificateChainChecker|null */ private $certificateChainChecker; public function __construct(AttestationStatementSupportManager $attestationStatementSupportManager, PublicKeyCredentialSourceRepository $publicKeyCredentialSource, TokenBindingHandler $tokenBindingHandler, ExtensionOutputCheckerHandler $extensionOutputCheckerHandler, ?MetadataStatementRepository $metadataStatementRepository = null, ?LoggerInterface $logger = null) { if (null !== $logger) { @trigger_error('The argument "logger" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setLogger".', E_USER_DEPRECATED); } if (null !== $metadataStatementRepository) { @trigger_error('The argument "metadataStatementRepository" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setMetadataStatementRepository".', E_USER_DEPRECATED); } $this->attestationStatementSupportManager = $attestationStatementSupportManager; $this->publicKeyCredentialSource = $publicKeyCredentialSource; $this->tokenBindingHandler = $tokenBindingHandler; $this->extensionOutputCheckerHandler = $extensionOutputCheckerHandler; $this->metadataStatementRepository = $metadataStatementRepository; $this->logger = $logger ?? new NullLogger(); } public function setLogger(LoggerInterface $logger): self { $this->logger = $logger; return $this; } public function setCertificateChainChecker(CertificateChainChecker $certificateChainChecker): self { $this->certificateChainChecker = $certificateChainChecker; return $this; } public function setMetadataStatementRepository(MetadataStatementRepository $metadataStatementRepository): self { $this->metadataStatementRepository = $metadataStatementRepository; return $this; } /** * @see https://www.w3.org/TR/webauthn/#registering-a-new-credential */ public function check(AuthenticatorAttestationResponse $authenticatorAttestationResponse, PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, ServerRequestInterface $request, array $securedRelyingPartyId = []): PublicKeyCredentialSource { try { $this->logger->info('Checking the authenticator attestation response', [ 'authenticatorAttestationResponse' => $authenticatorAttestationResponse, 'publicKeyCredentialCreationOptions' => $publicKeyCredentialCreationOptions, 'host' => $request->getUri()->getHost(), ]); /** @see 7.1.1 */ //Nothing to do /** @see 7.1.2 */ $C = $authenticatorAttestationResponse->getClientDataJSON(); /** @see 7.1.3 */ Assertion::eq('webauthn.create', $C->getType(), 'The client data type is not "webauthn.create".'); /** @see 7.1.4 */ Assertion::true(hash_equals($publicKeyCredentialCreationOptions->getChallenge(), $C->getChallenge()), 'Invalid challenge.'); /** @see 7.1.5 */ $rpId = $publicKeyCredentialCreationOptions->getRp()->getId() ?? $request->getUri()->getHost(); $facetId = $this->getFacetId($rpId, $publicKeyCredentialCreationOptions->getExtensions(), $authenticatorAttestationResponse->getAttestationObject()->getAuthData()->getExtensions()); $parsedRelyingPartyId = parse_url($C->getOrigin()); Assertion::isArray($parsedRelyingPartyId, sprintf('The origin URI "%s" is not valid', $C->getOrigin())); Assertion::keyExists($parsedRelyingPartyId, 'scheme', 'Invalid origin rpId.'); $clientDataRpId = $parsedRelyingPartyId['host'] ?? ''; Assertion::notEmpty($clientDataRpId, 'Invalid origin rpId.'); $rpIdLength = mb_strlen($facetId); Assertion::eq(mb_substr('.'.$clientDataRpId, -($rpIdLength + 1)), '.'.$facetId, 'rpId mismatch.'); if (!in_array($facetId, $securedRelyingPartyId, true)) { $scheme = $parsedRelyingPartyId['scheme'] ?? ''; Assertion::eq('https', $scheme, 'Invalid scheme. HTTPS required.'); } /** @see 7.1.6 */ if (null !== $C->getTokenBinding()) { $this->tokenBindingHandler->check($C->getTokenBinding(), $request); } /** @see 7.1.7 */ $clientDataJSONHash = hash('sha256', $authenticatorAttestationResponse->getClientDataJSON()->getRawData(), true); /** @see 7.1.8 */ $attestationObject = $authenticatorAttestationResponse->getAttestationObject(); /** @see 7.1.9 */ $rpIdHash = hash('sha256', $facetId, true); Assertion::true(hash_equals($rpIdHash, $attestationObject->getAuthData()->getRpIdHash()), 'rpId hash mismatch.'); /** @see 7.1.10 */ Assertion::true($attestationObject->getAuthData()->isUserPresent(), 'User was not present'); /** @see 7.1.11 */ if (AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED === $publicKeyCredentialCreationOptions->getAuthenticatorSelection()->getUserVerification()) { Assertion::true($attestationObject->getAuthData()->isUserVerified(), 'User authentication required.'); } /** @see 7.1.12 */ $extensionsClientOutputs = $attestationObject->getAuthData()->getExtensions(); if (null !== $extensionsClientOutputs) { $this->extensionOutputCheckerHandler->check( $publicKeyCredentialCreationOptions->getExtensions(), $extensionsClientOutputs ); } /** @see 7.1.13 */ $this->checkMetadataStatement($publicKeyCredentialCreationOptions, $attestationObject); $fmt = $attestationObject->getAttStmt()->getFmt(); Assertion::true($this->attestationStatementSupportManager->has($fmt), 'Unsupported attestation statement format.'); /** @see 7.1.14 */ $attestationStatementSupport = $this->attestationStatementSupportManager->get($fmt); Assertion::true($attestationStatementSupport->isValid($clientDataJSONHash, $attestationObject->getAttStmt(), $attestationObject->getAuthData()), 'Invalid attestation statement.'); /** @see 7.1.15 */ /** @see 7.1.16 */ /** @see 7.1.17 */ Assertion::true($attestationObject->getAuthData()->hasAttestedCredentialData(), 'There is no attested credential data.'); $attestedCredentialData = $attestationObject->getAuthData()->getAttestedCredentialData(); Assertion::notNull($attestedCredentialData, 'There is no attested credential data.'); $credentialId = $attestedCredentialData->getCredentialId(); Assertion::null($this->publicKeyCredentialSource->findOneByCredentialId($credentialId), 'The credential ID already exists.'); /** @see 7.1.18 */ /** @see 7.1.19 */ $publicKeyCredentialSource = $this->createPublicKeyCredentialSource( $credentialId, $attestedCredentialData, $attestationObject, $publicKeyCredentialCreationOptions->getUser()->getId() ); $this->logger->info('The attestation is valid'); $this->logger->debug('Public Key Credential Source', ['publicKeyCredentialSource' => $publicKeyCredentialSource]); return $publicKeyCredentialSource; } catch (Throwable $throwable) { $this->logger->error('An error occurred', [ 'exception' => $throwable, ]); throw $throwable; } } private function checkCertificateChain(AttestationStatement $attestationStatement, ?MetadataStatement $metadataStatement): void { $trustPath = $attestationStatement->getTrustPath(); if (!$trustPath instanceof CertificateTrustPath) { return; } $authenticatorCertificates = $trustPath->getCertificates(); if (null === $metadataStatement) { // @phpstan-ignore-next-line null === $this->certificateChainChecker ? CertificateToolbox::checkChain($authenticatorCertificates) : $this->certificateChainChecker->check($authenticatorCertificates, [], null); return; } $metadataStatementCertificates = $metadataStatement->getAttestationRootCertificates(); $rootStatementCertificates = $metadataStatement->getRootCertificates(); foreach ($metadataStatementCertificates as $key => $metadataStatementCertificate) { $metadataStatementCertificates[$key] = CertificateToolbox::fixPEMStructure($metadataStatementCertificate); } $trustedCertificates = array_merge( $metadataStatementCertificates, $rootStatementCertificates ); // @phpstan-ignore-next-line null === $this->certificateChainChecker ? CertificateToolbox::checkChain($authenticatorCertificates, $trustedCertificates) : $this->certificateChainChecker->check($authenticatorCertificates, $trustedCertificates); } private function checkMetadataStatement(PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, AttestationObject $attestationObject): void { $attestationStatement = $attestationObject->getAttStmt(); $attestedCredentialData = $attestationObject->getAuthData()->getAttestedCredentialData(); Assertion::notNull($attestedCredentialData, 'No attested credential data found'); $aaguid = $attestedCredentialData->getAaguid()->toString(); if (PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE === $publicKeyCredentialCreationOptions->getAttestation()) { $this->logger->debug('No attestation is asked.'); //No attestation is asked. We shall ensure that the data is anonymous. if ( '00000000-0000-0000-0000-000000000000' === $aaguid && (AttestationStatement::TYPE_NONE === $attestationStatement->getType() || AttestationStatement::TYPE_SELF === $attestationStatement->getType())) { $this->logger->debug('The Attestation Statement is anonymous.'); $this->checkCertificateChain($attestationStatement, null); return; } $this->logger->debug('Anonymization required. AAGUID and Attestation Statement changed.', [ 'aaguid' => $aaguid, 'AttestationStatement' => $attestationStatement, ]); $attestedCredentialData->setAaguid( Uuid::fromString('00000000-0000-0000-0000-000000000000') ); $attestationObject->setAttStmt(AttestationStatement::createNone('none', [], new EmptyTrustPath())); return; } if (AttestationStatement::TYPE_NONE === $attestationStatement->getType()) { $this->logger->debug('No attestation returned.'); //No attestation is returned. We shall ensure that the AAGUID is a null one. if ('00000000-0000-0000-0000-000000000000' !== $aaguid) { $this->logger->debug('Anonymization required. AAGUID and Attestation Statement changed.', [ 'aaguid' => $aaguid, 'AttestationStatement' => $attestationStatement, ]); $attestedCredentialData->setAaguid( Uuid::fromString('00000000-0000-0000-0000-000000000000') ); return; } return; } //The MDS Repository is mandatory here Assertion::notNull($this->metadataStatementRepository, 'The Metadata Statement Repository is mandatory when requesting attestation objects.'); $metadataStatement = $this->metadataStatementRepository->findOneByAAGUID($aaguid); // We check the last status report $this->checkStatusReport(null === $metadataStatement ? [] : $metadataStatement->getStatusReports()); // We check the certificate chain (if any) $this->checkCertificateChain($attestationStatement, $metadataStatement); // If no Attestation Statement has been returned or if null AAGUID (=00000000-0000-0000-0000-000000000000) // => nothing to check if ('00000000-0000-0000-0000-000000000000' === $aaguid || AttestationStatement::TYPE_NONE === $attestationStatement->getType()) { return; } // At this point, the Metadata Statement is mandatory Assertion::notNull($metadataStatement, sprintf('The Metadata Statement for the AAGUID "%s" is missing', $aaguid)); // Check Attestation Type is allowed if (0 !== count($metadataStatement->getAttestationTypes())) { $type = $this->getAttestationType($attestationStatement); Assertion::inArray($type, $metadataStatement->getAttestationTypes(), 'Invalid attestation statement. The attestation type is not allowed for this authenticator'); } } /** * @param StatusReport[] $statusReports */ private function checkStatusReport(array $statusReports): void { if (0 !== count($statusReports)) { $lastStatusReport = end($statusReports); if ($lastStatusReport->isCompromised()) { throw new LogicException('The authenticator is compromised and cannot be used'); } } } private function createPublicKeyCredentialSource(string $credentialId, AttestedCredentialData $attestedCredentialData, AttestationObject $attestationObject, string $userHandle): PublicKeyCredentialSource { return new PublicKeyCredentialSource( $credentialId, PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, [], $attestationObject->getAttStmt()->getType(), $attestationObject->getAttStmt()->getTrustPath(), $attestedCredentialData->getAaguid(), $attestedCredentialData->getCredentialPublicKey(), $userHandle, $attestationObject->getAuthData()->getSignCount() ); } private function getAttestationType(AttestationStatement $attestationStatement): int { switch ($attestationStatement->getType()) { case AttestationStatement::TYPE_BASIC: return MetadataStatement::ATTESTATION_BASIC_FULL; case AttestationStatement::TYPE_SELF: return MetadataStatement::ATTESTATION_BASIC_SURROGATE; case AttestationStatement::TYPE_ATTCA: return MetadataStatement::ATTESTATION_ATTCA; case AttestationStatement::TYPE_ECDAA: return MetadataStatement::ATTESTATION_ECDAA; default: throw new InvalidArgumentException('Invalid attestation type'); } } private function getFacetId(string $rpId, AuthenticationExtensionsClientInputs $authenticationExtensionsClientInputs, ?AuthenticationExtensionsClientOutputs $authenticationExtensionsClientOutputs): string { if (null === $authenticationExtensionsClientOutputs || !$authenticationExtensionsClientInputs->has('appid') || !$authenticationExtensionsClientOutputs->has('appid')) { return $rpId; } $appId = $authenticationExtensionsClientInputs->get('appid')->value(); $wasUsed = $authenticationExtensionsClientOutputs->get('appid')->value(); if (!is_string($appId) || true !== $wasUsed) { return $rpId; } return $appId; } }
Submit
FILE
FOLDER
INFO
Name
Size
Permission
Action
AttestationStatement
---
0755
AuthenticationExtensions
---
0755
CertificateChainChecker
---
0755
Counter
---
0755
TokenBinding
---
0755
TrustPath
---
0755
Util
---
0755
AttestedCredentialData.php
2826 bytes
0644
AuthenticatorAssertionResponse.php
1421 bytes
0644
AuthenticatorAssertionResponseValidator.php
12200 bytes
0644
AuthenticatorAttestationResponse.php
878 bytes
0644
AuthenticatorAttestationResponseValidator.php
17793 bytes
0644
AuthenticatorData.php
2926 bytes
0644
AuthenticatorResponse.php
686 bytes
0644
AuthenticatorSelectionCriteria.php
5246 bytes
0644
CertificateToolbox.php
7660 bytes
0644
CollectedClientData.php
3045 bytes
0644
Credential.php
757 bytes
0644
PublicKeyCredential.php
1297 bytes
0644
PublicKeyCredentialCreationOptions.php
9165 bytes
0644
PublicKeyCredentialDescriptor.php
2415 bytes
0644
PublicKeyCredentialDescriptorCollection.php
2365 bytes
0644
PublicKeyCredentialEntity.php
1029 bytes
0644
PublicKeyCredentialLoader.php
7124 bytes
0644
PublicKeyCredentialOptions.php
2659 bytes
0644
PublicKeyCredentialParameters.php
1728 bytes
0644
PublicKeyCredentialRequestOptions.php
5936 bytes
0644
PublicKeyCredentialRpEntity.php
1226 bytes
0644
PublicKeyCredentialSource.php
5908 bytes
0644
PublicKeyCredentialSourceRepository.php
681 bytes
0644
PublicKeyCredentialUserEntity.php
2060 bytes
0644
Server.php
14123 bytes
0644
StringStream.php
1499 bytes
0644
U2FPublicKey.php
1240 bytes
0644
N4ST4R_ID | Naxtarrr