[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @package Joomla.Plugin 5 * @subpackage Multifactorauth.webauthn 6 * 7 * @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org> 8 * @license GNU General Public License version 2 or later; see LICENSE.txt 9 * @copyright (C) 2014-2019 Spomky-Labs 10 * @license This software may be modified and distributed under the terms 11 * of the MIT license. 12 * See libraries/vendor/web-auth/webauthn-lib/LICENSE 13 */ 14 15 namespace Joomla\Plugin\Multifactorauth\Webauthn\Hotfix; 16 17 use Assert\Assertion; 18 use Cose\Algorithm\Algorithm; 19 use Cose\Algorithm\ManagerFactory; 20 use Cose\Algorithm\Signature\ECDSA; 21 use Cose\Algorithm\Signature\EdDSA; 22 use Cose\Algorithm\Signature\RSA; 23 use Psr\Http\Client\ClientInterface; 24 use Psr\Http\Message\RequestFactoryInterface; 25 use Psr\Http\Message\ServerRequestInterface; 26 use Webauthn\AttestationStatement\AndroidSafetyNetAttestationStatementSupport; 27 use Webauthn\AttestationStatement\AttestationObjectLoader; 28 use Webauthn\AttestationStatement\AttestationStatementSupportManager; 29 use Webauthn\AttestationStatement\NoneAttestationStatementSupport; 30 use Webauthn\AttestationStatement\PackedAttestationStatementSupport; 31 use Webauthn\AttestationStatement\TPMAttestationStatementSupport; 32 use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs; 33 use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler; 34 use Webauthn\AuthenticatorAssertionResponse; 35 use Webauthn\AuthenticatorAssertionResponseValidator; 36 use Webauthn\AuthenticatorAttestationResponse; 37 use Webauthn\AuthenticatorAttestationResponseValidator; 38 use Webauthn\AuthenticatorSelectionCriteria; 39 use Webauthn\MetadataService\MetadataStatementRepository; 40 use Webauthn\PublicKeyCredentialCreationOptions; 41 use Webauthn\PublicKeyCredentialDescriptor; 42 use Webauthn\PublicKeyCredentialLoader; 43 use Webauthn\PublicKeyCredentialParameters; 44 use Webauthn\PublicKeyCredentialRequestOptions; 45 use Webauthn\PublicKeyCredentialRpEntity; 46 use Webauthn\PublicKeyCredentialSource; 47 use Webauthn\PublicKeyCredentialSourceRepository; 48 use Webauthn\PublicKeyCredentialUserEntity; 49 use Webauthn\TokenBinding\TokenBindingNotSupportedHandler; 50 51 // phpcs:disable PSR1.Files.SideEffects 52 \defined('_JEXEC') or die; 53 // phpcs:enable PSR1.Files.SideEffects 54 55 /** 56 * Customised WebAuthn server object. 57 * 58 * We had to fork the server object from the WebAuthn server package to address an issue with PHP 8. 59 * 60 * We are currently using an older version of the WebAuthn library (2.x) which was written before 61 * PHP 8 was developed. We cannot upgrade the WebAuthn library to a newer major version because of 62 * Joomla's Semantic Versioning promise. 63 * 64 * The FidoU2FAttestationStatementSupport and AndroidKeyAttestationStatementSupport classes force 65 * an assertion on the result of the openssl_pkey_get_public() function, assuming it will return a 66 * resource. However, starting with PHP 8.0 this function returns an OpenSSLAsymmetricKey object 67 * and the assertion fails. As a result, you cannot use Android or FIDO U2F keys with WebAuthn. 68 * 69 * The assertion check is in a private method, therefore we have to fork both attestation support 70 * classes to change the assertion. The assertion takes place through a third party library we 71 * cannot (and should not!) modify. 72 * 73 * The assertions objects, however, are injected to the attestation support manager in a private 74 * method of the Server object. Because literally everything in this class is private we have no 75 * option than to fork the entire class to apply our two forked attestation support classes. 76 * 77 * This is marked as deprecated because we'll be able to upgrade the WebAuthn library on Joomla 5. 78 * 79 * @since 4.2.0 80 * 81 * @deprecated 5.0 We will upgrade the WebAuthn library to version 3 or later and this will go away. 82 */ 83 class Server extends \Webauthn\Server 84 { 85 /** 86 * @var integer 87 * @since 4.2.0 88 */ 89 public $timeout = 60000; 90 91 /** 92 * @var integer 93 * @since 4.2.0 94 */ 95 public $challengeSize = 32; 96 97 /** 98 * @var PublicKeyCredentialRpEntity 99 * @since 4.2.0 100 */ 101 private $rpEntity; 102 103 /** 104 * @var ManagerFactory 105 * @since 4.2.0 106 */ 107 private $coseAlgorithmManagerFactory; 108 109 /** 110 * @var PublicKeyCredentialSourceRepository 111 * @since 4.2.0 112 */ 113 private $publicKeyCredentialSourceRepository; 114 115 /** 116 * @var TokenBindingNotSupportedHandler 117 * @since 4.2.0 118 */ 119 private $tokenBindingHandler; 120 121 /** 122 * @var ExtensionOutputCheckerHandler 123 * @since 4.2.0 124 */ 125 private $extensionOutputCheckerHandler; 126 127 /** 128 * @var string[] 129 * @since 4.2.0 130 */ 131 private $selectedAlgorithms; 132 133 /** 134 * @var MetadataStatementRepository|null 135 * @since 4.2.0 136 */ 137 private $metadataStatementRepository; 138 139 /** 140 * @var ClientInterface 141 * @since 4.2.0 142 */ 143 private $httpClient; 144 145 /** 146 * @var string 147 * @since 4.2.0 148 */ 149 private $googleApiKey; 150 151 /** 152 * @var RequestFactoryInterface 153 * @since 4.2.0 154 */ 155 private $requestFactory; 156 157 /** 158 * Overridden constructor. 159 * 160 * @param PublicKeyCredentialRpEntity $relayingParty Obvious 161 * @param PublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository Obvious 162 * @param MetadataStatementRepository|null $metadataStatementRepository Obvious 163 * 164 * @since 4.2.0 165 */ 166 public function __construct( 167 PublicKeyCredentialRpEntity $relayingParty, 168 PublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository, 169 ?MetadataStatementRepository $metadataStatementRepository 170 ) { 171 $this->rpEntity = $relayingParty; 172 173 $this->coseAlgorithmManagerFactory = new ManagerFactory(); 174 $this->coseAlgorithmManagerFactory->add('RS1', new RSA\RS1()); 175 $this->coseAlgorithmManagerFactory->add('RS256', new RSA\RS256()); 176 $this->coseAlgorithmManagerFactory->add('RS384', new RSA\RS384()); 177 $this->coseAlgorithmManagerFactory->add('RS512', new RSA\RS512()); 178 $this->coseAlgorithmManagerFactory->add('PS256', new RSA\PS256()); 179 $this->coseAlgorithmManagerFactory->add('PS384', new RSA\PS384()); 180 $this->coseAlgorithmManagerFactory->add('PS512', new RSA\PS512()); 181 $this->coseAlgorithmManagerFactory->add('ES256', new ECDSA\ES256()); 182 $this->coseAlgorithmManagerFactory->add('ES256K', new ECDSA\ES256K()); 183 $this->coseAlgorithmManagerFactory->add('ES384', new ECDSA\ES384()); 184 $this->coseAlgorithmManagerFactory->add('ES512', new ECDSA\ES512()); 185 $this->coseAlgorithmManagerFactory->add('Ed25519', new EdDSA\Ed25519()); 186 187 $this->selectedAlgorithms = ['RS256', 'RS512', 'PS256', 'PS512', 'ES256', 'ES512', 'Ed25519']; 188 $this->publicKeyCredentialSourceRepository = $publicKeyCredentialSourceRepository; 189 $this->tokenBindingHandler = new TokenBindingNotSupportedHandler(); 190 $this->extensionOutputCheckerHandler = new ExtensionOutputCheckerHandler(); 191 $this->metadataStatementRepository = $metadataStatementRepository; 192 } 193 194 /** 195 * @param string[] $selectedAlgorithms Obvious 196 * 197 * @return void 198 * @since 4.2.0 199 */ 200 public function setSelectedAlgorithms(array $selectedAlgorithms): void 201 { 202 $this->selectedAlgorithms = $selectedAlgorithms; 203 } 204 205 /** 206 * @param TokenBindingNotSupportedHandler $tokenBindingHandler Obvious 207 * 208 * @return void 209 * @since 4.2.0 210 */ 211 public function setTokenBindingHandler(TokenBindingNotSupportedHandler $tokenBindingHandler): void 212 { 213 $this->tokenBindingHandler = $tokenBindingHandler; 214 } 215 216 /** 217 * @param string $alias Obvious 218 * @param Algorithm $algorithm Obvious 219 * 220 * @return void 221 * @since 4.2.0 222 */ 223 public function addAlgorithm(string $alias, Algorithm $algorithm): void 224 { 225 $this->coseAlgorithmManagerFactory->add($alias, $algorithm); 226 $this->selectedAlgorithms[] = $alias; 227 $this->selectedAlgorithms = array_unique($this->selectedAlgorithms); 228 } 229 230 /** 231 * @param ExtensionOutputCheckerHandler $extensionOutputCheckerHandler Obvious 232 * 233 * @return void 234 * @since 4.2.0 235 */ 236 public function setExtensionOutputCheckerHandler(ExtensionOutputCheckerHandler $extensionOutputCheckerHandler): void 237 { 238 $this->extensionOutputCheckerHandler = $extensionOutputCheckerHandler; 239 } 240 241 /** 242 * @param string|null $userVerification Obvious 243 * @param PublicKeyCredentialDescriptor[] $allowedPublicKeyDescriptors Obvious 244 * @param AuthenticationExtensionsClientInputs|null $extensions Obvious 245 * 246 * @return PublicKeyCredentialRequestOptions 247 * @throws \Exception 248 * @since 4.2.0 249 */ 250 public function generatePublicKeyCredentialRequestOptions( 251 ?string $userVerification = PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_PREFERRED, 252 array $allowedPublicKeyDescriptors = [], 253 ?AuthenticationExtensionsClientInputs $extensions = null 254 ): PublicKeyCredentialRequestOptions { 255 return new PublicKeyCredentialRequestOptions( 256 random_bytes($this->challengeSize), 257 $this->timeout, 258 $this->rpEntity->getId(), 259 $allowedPublicKeyDescriptors, 260 $userVerification, 261 $extensions ?? new AuthenticationExtensionsClientInputs() 262 ); 263 } 264 265 /** 266 * @param PublicKeyCredentialUserEntity $userEntity Obvious 267 * @param string|null $attestationMode Obvious 268 * @param PublicKeyCredentialDescriptor[] $excludedPublicKeyDescriptors Obvious 269 * @param AuthenticatorSelectionCriteria|null $criteria Obvious 270 * @param AuthenticationExtensionsClientInputs|null $extensions Obvious 271 * 272 * @return PublicKeyCredentialCreationOptions 273 * @throws \Exception 274 * @since 4.2.0 275 */ 276 public function generatePublicKeyCredentialCreationOptions( 277 PublicKeyCredentialUserEntity $userEntity, 278 ?string $attestationMode = PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE, 279 array $excludedPublicKeyDescriptors = [], 280 ?AuthenticatorSelectionCriteria $criteria = null, 281 ?AuthenticationExtensionsClientInputs $extensions = null 282 ): PublicKeyCredentialCreationOptions { 283 $coseAlgorithmManager = $this->coseAlgorithmManagerFactory->create($this->selectedAlgorithms); 284 $publicKeyCredentialParametersList = []; 285 286 foreach ($coseAlgorithmManager->all() as $algorithm) { 287 $publicKeyCredentialParametersList[] = new PublicKeyCredentialParameters( 288 PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, 289 $algorithm::identifier() 290 ); 291 } 292 293 $criteria = $criteria ?? new AuthenticatorSelectionCriteria(); 294 $extensions = $extensions ?? new AuthenticationExtensionsClientInputs(); 295 $challenge = random_bytes($this->challengeSize); 296 297 return new PublicKeyCredentialCreationOptions( 298 $this->rpEntity, 299 $userEntity, 300 $challenge, 301 $publicKeyCredentialParametersList, 302 $this->timeout, 303 $excludedPublicKeyDescriptors, 304 $criteria, 305 $attestationMode, 306 $extensions 307 ); 308 } 309 310 /** 311 * @param string $data Obvious 312 * @param PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions Obvious 313 * @param ServerRequestInterface $serverRequest Obvious 314 * 315 * @return PublicKeyCredentialSource 316 * @throws \Assert\AssertionFailedException 317 * @since 4.2.0 318 */ 319 public function loadAndCheckAttestationResponse( 320 string $data, 321 PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, 322 ServerRequestInterface $serverRequest 323 ): PublicKeyCredentialSource { 324 $attestationStatementSupportManager = $this->getAttestationStatementSupportManager(); 325 $attestationObjectLoader = new AttestationObjectLoader($attestationStatementSupportManager); 326 $publicKeyCredentialLoader = new PublicKeyCredentialLoader($attestationObjectLoader); 327 328 $publicKeyCredential = $publicKeyCredentialLoader->load($data); 329 $authenticatorResponse = $publicKeyCredential->getResponse(); 330 Assertion::isInstanceOf($authenticatorResponse, AuthenticatorAttestationResponse::class, 'Not an authenticator attestation response'); 331 332 $authenticatorAttestationResponseValidator = new AuthenticatorAttestationResponseValidator( 333 $attestationStatementSupportManager, 334 $this->publicKeyCredentialSourceRepository, 335 $this->tokenBindingHandler, 336 $this->extensionOutputCheckerHandler 337 ); 338 339 return $authenticatorAttestationResponseValidator->check($authenticatorResponse, $publicKeyCredentialCreationOptions, $serverRequest); 340 } 341 342 /** 343 * @param string $data Obvious 344 * @param PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions Obvious 345 * @param PublicKeyCredentialUserEntity|null $userEntity Obvious 346 * @param ServerRequestInterface $serverRequest Obvious 347 * 348 * @return PublicKeyCredentialSource 349 * @throws \Assert\AssertionFailedException 350 * @since 4.2.0 351 */ 352 public function loadAndCheckAssertionResponse( 353 string $data, 354 PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions, 355 ?PublicKeyCredentialUserEntity $userEntity, 356 ServerRequestInterface $serverRequest 357 ): PublicKeyCredentialSource { 358 $attestationStatementSupportManager = $this->getAttestationStatementSupportManager(); 359 $attestationObjectLoader = new AttestationObjectLoader($attestationStatementSupportManager); 360 $publicKeyCredentialLoader = new PublicKeyCredentialLoader($attestationObjectLoader); 361 362 $publicKeyCredential = $publicKeyCredentialLoader->load($data); 363 $authenticatorResponse = $publicKeyCredential->getResponse(); 364 Assertion::isInstanceOf($authenticatorResponse, AuthenticatorAssertionResponse::class, 'Not an authenticator assertion response'); 365 366 $authenticatorAssertionResponseValidator = new AuthenticatorAssertionResponseValidator( 367 $this->publicKeyCredentialSourceRepository, 368 null, 369 $this->tokenBindingHandler, 370 $this->extensionOutputCheckerHandler, 371 $this->coseAlgorithmManagerFactory->create($this->selectedAlgorithms) 372 ); 373 374 return $authenticatorAssertionResponseValidator->check( 375 $publicKeyCredential->getRawId(), 376 $authenticatorResponse, 377 $publicKeyCredentialRequestOptions, 378 $serverRequest, 379 null !== $userEntity ? $userEntity->getId() : null 380 ); 381 } 382 383 /** 384 * @param ClientInterface $client Obvious 385 * @param string $apiKey Obvious 386 * @param RequestFactoryInterface $requestFactory Obvious 387 * 388 * @return void 389 * @since 4.2.0 390 */ 391 public function enforceAndroidSafetyNetVerification( 392 ClientInterface $client, 393 string $apiKey, 394 RequestFactoryInterface $requestFactory 395 ): void { 396 $this->httpClient = $client; 397 $this->googleApiKey = $apiKey; 398 $this->requestFactory = $requestFactory; 399 } 400 401 /** 402 * @return AttestationStatementSupportManager 403 * @since 4.2.0 404 */ 405 private function getAttestationStatementSupportManager(): AttestationStatementSupportManager 406 { 407 $attestationStatementSupportManager = new AttestationStatementSupportManager(); 408 $attestationStatementSupportManager->add(new NoneAttestationStatementSupport()); 409 410 if ($this->metadataStatementRepository !== null) { 411 $coseAlgorithmManager = $this->coseAlgorithmManagerFactory->create($this->selectedAlgorithms); 412 $attestationStatementSupportManager->add(new FidoU2FAttestationStatementSupport(null, $this->metadataStatementRepository)); 413 414 /** 415 * Work around a third party library (web-token/jwt-signature-algorithm-eddsa) bug. 416 * 417 * On PHP 8 libsodium is compiled into PHP, it is not an extension. However, the third party library does 418 * not check if the libsodium function are available; it checks if the "sodium" extension is loaded. This of 419 * course causes an immediate failure with a Runtime exception EVEN IF the attested data isn't attested by 420 * Android Safety Net. Therefore we have to not even load the AndroidSafetyNetAttestationStatementSupport 421 * class in this case... 422 */ 423 if (function_exists('sodium_crypto_sign_seed_keypair') && function_exists('extension_loaded') && extension_loaded('sodium')) { 424 $attestationStatementSupportManager->add( 425 new AndroidSafetyNetAttestationStatementSupport( 426 $this->httpClient, 427 $this->googleApiKey, 428 $this->requestFactory, 429 2000, 430 60000, 431 $this->metadataStatementRepository 432 ) 433 ); 434 } 435 436 $attestationStatementSupportManager->add(new AndroidKeyAttestationStatementSupport(null, $this->metadataStatementRepository)); 437 $attestationStatementSupportManager->add(new TPMAttestationStatementSupport($this->metadataStatementRepository)); 438 $attestationStatementSupportManager->add( 439 new PackedAttestationStatementSupport( 440 null, 441 $coseAlgorithmManager, 442 $this->metadataStatementRepository 443 ) 444 ); 445 } 446 447 return $attestationStatementSupportManager; 448 } 449 }
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 |