[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 3 declare(strict_types=1); 4 5 /* 6 * The MIT License (MIT) 7 * 8 * Copyright (c) 2014-2019 Spomky-Labs 9 * 10 * This software may be modified and distributed under the terms 11 * of the MIT license. See the LICENSE file for details. 12 */ 13 14 namespace Webauthn; 15 16 use Assert\Assertion; 17 use CBOR\Decoder; 18 use CBOR\OtherObject\OtherObjectManager; 19 use CBOR\Tag\TagObjectManager; 20 use Cose\Algorithm\Manager; 21 use Cose\Algorithm\Signature\Signature; 22 use Cose\Key\Key; 23 use Psr\Http\Message\ServerRequestInterface; 24 use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs; 25 use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputs; 26 use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler; 27 use Webauthn\TokenBinding\TokenBindingHandler; 28 use Webauthn\Util\CoseSignatureFixer; 29 30 class AuthenticatorAssertionResponseValidator 31 { 32 /** 33 * @var PublicKeyCredentialSourceRepository 34 */ 35 private $publicKeyCredentialSourceRepository; 36 37 /** 38 * @var Decoder 39 */ 40 private $decoder; 41 42 /** 43 * @var TokenBindingHandler 44 */ 45 private $tokenBindingHandler; 46 47 /** 48 * @var ExtensionOutputCheckerHandler 49 */ 50 private $extensionOutputCheckerHandler; 51 52 /** 53 * @var Manager|null 54 */ 55 private $algorithmManager; 56 57 public function __construct(PublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository, ?Decoder $decoder, TokenBindingHandler $tokenBindingHandler, ExtensionOutputCheckerHandler $extensionOutputCheckerHandler, Manager $algorithmManager) 58 { 59 if (null !== $decoder) { 60 @trigger_error('The argument "$decoder" is deprecated since 2.1 and will be removed in v3.0. Set null instead', E_USER_DEPRECATED); 61 } 62 $this->publicKeyCredentialSourceRepository = $publicKeyCredentialSourceRepository; 63 $this->decoder = $decoder ?? new Decoder(new TagObjectManager(), new OtherObjectManager()); 64 $this->tokenBindingHandler = $tokenBindingHandler; 65 $this->extensionOutputCheckerHandler = $extensionOutputCheckerHandler; 66 $this->algorithmManager = $algorithmManager; 67 } 68 69 /** 70 * @see https://www.w3.org/TR/webauthn/#verifying-assertion 71 */ 72 public function check(string $credentialId, AuthenticatorAssertionResponse $authenticatorAssertionResponse, PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions, ServerRequestInterface $request, ?string $userHandle): PublicKeyCredentialSource 73 { 74 /* @see 7.2.1 */ 75 if (0 !== \count($publicKeyCredentialRequestOptions->getAllowCredentials())) { 76 Assertion::true($this->isCredentialIdAllowed($credentialId, $publicKeyCredentialRequestOptions->getAllowCredentials()), 'The credential ID is not allowed.'); 77 } 78 79 /* @see 7.2.2 */ 80 $publicKeyCredentialSource = $this->publicKeyCredentialSourceRepository->findOneByCredentialId($credentialId); 81 Assertion::notNull($publicKeyCredentialSource, 'The credential ID is invalid.'); 82 83 /* @see 7.2.3 */ 84 $attestedCredentialData = $publicKeyCredentialSource->getAttestedCredentialData(); 85 $credentialUserHandle = $publicKeyCredentialSource->getUserHandle(); 86 $responseUserHandle = $authenticatorAssertionResponse->getUserHandle(); 87 88 /* @see 7.2.2 User Handle*/ 89 if (null !== $userHandle) { //If the user was identified before the authentication ceremony was initiated, 90 Assertion::eq($credentialUserHandle, $userHandle, 'Invalid user handle'); 91 if (null !== $responseUserHandle && '' !== $responseUserHandle) { 92 Assertion::eq($credentialUserHandle, $responseUserHandle, 'Invalid user handle'); 93 } 94 } else { 95 Assertion::notEmpty($responseUserHandle, 'User handle is mandatory'); 96 Assertion::eq($credentialUserHandle, $responseUserHandle, 'Invalid user handle'); 97 } 98 99 $credentialPublicKey = $attestedCredentialData->getCredentialPublicKey(); 100 Assertion::notNull($credentialPublicKey, 'No public key available.'); 101 $stream = new StringStream($credentialPublicKey); 102 $credentialPublicKeyStream = $this->decoder->decode($stream); 103 Assertion::true($stream->isEOF(), 'Invalid key. Presence of extra bytes.'); 104 $stream->close(); 105 106 /** @see 7.2.4 */ 107 /** @see 7.2.5 */ 108 //Nothing to do. Use of objects directly 109 110 /** @see 7.2.6 */ 111 $C = $authenticatorAssertionResponse->getClientDataJSON(); 112 113 /* @see 7.2.7 */ 114 Assertion::eq('webauthn.get', $C->getType(), 'The client data type is not "webauthn.get".'); 115 116 /* @see 7.2.8 */ 117 Assertion::true(hash_equals($publicKeyCredentialRequestOptions->getChallenge(), $C->getChallenge()), 'Invalid challenge.'); 118 119 /** @see 7.2.9 */ 120 $rpId = $publicKeyCredentialRequestOptions->getRpId() ?? $request->getUri()->getHost(); 121 $rpIdLength = mb_strlen($rpId); 122 $parsedRelyingPartyId = parse_url($C->getOrigin()); 123 Assertion::isArray($parsedRelyingPartyId, 'Invalid origin'); 124 $scheme = $parsedRelyingPartyId['scheme'] ?? ''; 125 Assertion::eq('https', $scheme, 'Invalid scheme. HTTPS required.'); 126 $clientDataRpId = $parsedRelyingPartyId['host'] ?? ''; 127 Assertion::notEmpty($clientDataRpId, 'Invalid origin rpId.'); 128 Assertion::eq(mb_substr($clientDataRpId, -$rpIdLength), $rpId, 'rpId mismatch.'); 129 130 /* @see 7.2.10 */ 131 if (null !== $C->getTokenBinding()) { 132 $this->tokenBindingHandler->check($C->getTokenBinding(), $request); 133 } 134 135 /** @see 7.2.11 */ 136 $facetId = $this->getFacetId($rpId, $publicKeyCredentialRequestOptions->getExtensions(), $authenticatorAssertionResponse->getAuthenticatorData()->getExtensions()); 137 $rpIdHash = hash('sha256', $rpId, true); 138 Assertion::true(hash_equals($rpIdHash, $authenticatorAssertionResponse->getAuthenticatorData()->getRpIdHash()), 'rpId hash mismatch.'); 139 140 /* @see 7.2.12 */ 141 /* @see 7.2.13 */ 142 if (AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED === $publicKeyCredentialRequestOptions->getUserVerification()) { 143 Assertion::true($authenticatorAssertionResponse->getAuthenticatorData()->isUserPresent(), 'User was not present'); 144 Assertion::true($authenticatorAssertionResponse->getAuthenticatorData()->isUserVerified(), 'User authentication required.'); 145 } 146 147 /* @see 7.2.14 */ 148 $extensions = $authenticatorAssertionResponse->getAuthenticatorData()->getExtensions(); 149 if (null !== $extensions) { 150 $this->extensionOutputCheckerHandler->check($extensions); 151 } 152 153 /** @see 7.2.15 */ 154 $getClientDataJSONHash = hash('sha256', $authenticatorAssertionResponse->getClientDataJSON()->getRawData(), true); 155 156 /* @see 7.2.16 */ 157 $dataToVerify = $authenticatorAssertionResponse->getAuthenticatorData()->getAuthData().$getClientDataJSONHash; 158 $signature = $authenticatorAssertionResponse->getSignature(); 159 $coseKey = new Key($credentialPublicKeyStream->getNormalizedData()); 160 $algorithm = $this->algorithmManager->get($coseKey->alg()); 161 Assertion::isInstanceOf($algorithm, Signature::class, 'Invalid algorithm identifier. Should refer to a signature algorithm'); 162 $signature = CoseSignatureFixer::fix($signature, $algorithm); 163 Assertion::true($algorithm->verify($dataToVerify, $coseKey, $signature), 'Invalid signature.'); 164 165 /* @see 7.2.17 */ 166 $storedCounter = $publicKeyCredentialSource->getCounter(); 167 $currentCounter = $authenticatorAssertionResponse->getAuthenticatorData()->getSignCount(); 168 if (0 !== $currentCounter || 0 !== $storedCounter) { 169 Assertion::greaterThan($currentCounter, $storedCounter, 'Invalid counter.'); 170 } 171 $publicKeyCredentialSource->setCounter($currentCounter); 172 $this->publicKeyCredentialSourceRepository->saveCredentialSource($publicKeyCredentialSource); 173 174 /* @see 7.2.18 */ 175 //All good. We can continue. 176 return $publicKeyCredentialSource; 177 } 178 179 private function isCredentialIdAllowed(string $credentialId, array $allowedCredentials): bool 180 { 181 foreach ($allowedCredentials as $allowedCredential) { 182 if (hash_equals($allowedCredential->getId(), $credentialId)) { 183 return true; 184 } 185 } 186 187 return false; 188 } 189 190 private function getFacetId(string $rpId, AuthenticationExtensionsClientInputs $authenticationExtensionsClientInputs, ?AuthenticationExtensionsClientOutputs $authenticationExtensionsClientOutputs): string 191 { 192 switch (true) { 193 case !$authenticationExtensionsClientInputs->has('appid'): 194 return $rpId; 195 case null === $authenticationExtensionsClientOutputs: 196 return $rpId; 197 case !$authenticationExtensionsClientOutputs->has('appid'): 198 return $rpId; 199 case true !== $authenticationExtensionsClientOutputs->get('appid'): 200 return $rpId; 201 default: 202 return $authenticationExtensionsClientInputs->get('appid'); 203 } 204 } 205 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |