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