File manager - Edit - /home/opticamezl/www/newok/Hotfix.tar
Back
FidoU2FAttestationStatementSupport.php 0000644 00000021332 15174372665 0014176 0 ustar 00 <?php /** * @package Joomla.Plugin * @subpackage System.Webauthn * * @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org> * @license MIT; see libraries/vendor/web-auth/webauthn-lib/LICENSE */ namespace Joomla\Plugin\System\Webauthn\Hotfix; use Assert\Assertion; use CBOR\Decoder; use CBOR\MapObject; use CBOR\OtherObject\OtherObjectManager; use CBOR\Tag\TagObjectManager; use Cose\Key\Ec2Key; use Webauthn\AttestationStatement\AttestationStatement; use Webauthn\AttestationStatement\AttestationStatementSupport; use Webauthn\AuthenticatorData; use Webauthn\CertificateToolbox; use Webauthn\MetadataService\MetadataStatementRepository; use Webauthn\StringStream; use Webauthn\TrustPath\CertificateTrustPath; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * We had to fork the key attestation support object from the WebAuthn server package to address an * issue with PHP 8. * * We are currently using an older version of the WebAuthn library (2.x) which was written before * PHP 8 was developed. We cannot upgrade the WebAuthn library to a newer major version because of * Joomla's Semantic Versioning promise. * * The FidoU2FAttestationStatementSupport class forces an assertion on the result of the * openssl_pkey_get_public() function, assuming it will return a resource. However, starting with * PHP 8.0 this function returns an OpenSSLAsymmetricKey object and the assertion fails. As a * result, you cannot use Android or FIDO U2F keys with WebAuthn. * * The assertion check is in a private method, therefore we have to fork both attestation support * class to change the assertion. The assertion takes place through a third party library we cannot * (and should not!) modify. * * @since 4.2.0 * * @deprecated 4.2 will be removed in 6.0 * Will be removed without replacement * We will upgrade the WebAuthn library to version 3 or later and this will go away. */ final class FidoU2FAttestationStatementSupport implements AttestationStatementSupport { /** * @var Decoder * @since 4.2.0 */ private $decoder; /** * @var MetadataStatementRepository|null * @since 4.2.0 */ private $metadataStatementRepository; /** * @param Decoder|null $decoder Obvious * @param MetadataStatementRepository|null $metadataStatementRepository Obvious * * @since 4.2.0 */ public function __construct( ?Decoder $decoder = null, ?MetadataStatementRepository $metadataStatementRepository = null ) { if ($decoder !== null) { @trigger_error('The argument "$decoder" is deprecated since 2.1 and will be removed in v3.0. Set null instead', E_USER_DEPRECATED); } if ($metadataStatementRepository === null) { @trigger_error( 'Setting "null" for argument "$metadataStatementRepository" is deprecated since 2.1 and will be mandatory in v3.0.', E_USER_DEPRECATED ); } $this->decoder = $decoder ?? new Decoder(new TagObjectManager(), new OtherObjectManager()); $this->metadataStatementRepository = $metadataStatementRepository; } /** * @return string * @since 4.2.0 */ public function name(): string { return 'fido-u2f'; } /** * @param array $attestation Obvious * * @return AttestationStatement * @throws \Assert\AssertionFailedException * * @since 4.2.0 */ public function load(array $attestation): AttestationStatement { Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object'); foreach (['sig', 'x5c'] as $key) { Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key)); } $certificates = $attestation['attStmt']['x5c']; Assertion::isArray($certificates, 'The attestation statement value "x5c" must be a list with one certificate.'); Assertion::count($certificates, 1, 'The attestation statement value "x5c" must be a list with one certificate.'); Assertion::allString($certificates, 'The attestation statement value "x5c" must be a list with one certificate.'); reset($certificates); $certificates = CertificateToolbox::convertAllDERToPEM($certificates); $this->checkCertificate($certificates[0]); return AttestationStatement::createBasic($attestation['fmt'], $attestation['attStmt'], new CertificateTrustPath($certificates)); } /** * @param string $clientDataJSONHash Obvious * @param AttestationStatement $attestationStatement Obvious * @param AuthenticatorData $authenticatorData Obvious * * @return boolean * @throws \Assert\AssertionFailedException * @since 4.2.0 */ public function isValid( string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData ): bool { Assertion::eq( $authenticatorData->getAttestedCredentialData()->getAaguid()->toString(), '00000000-0000-0000-0000-000000000000', 'Invalid AAGUID for fido-u2f attestation statement. Shall be "00000000-0000-0000-0000-000000000000"' ); if ($this->metadataStatementRepository !== null) { CertificateToolbox::checkAttestationMedata( $attestationStatement, $authenticatorData->getAttestedCredentialData()->getAaguid()->toString(), [], $this->metadataStatementRepository ); } $trustPath = $attestationStatement->getTrustPath(); Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path'); $dataToVerify = "\0"; $dataToVerify .= $authenticatorData->getRpIdHash(); $dataToVerify .= $clientDataJSONHash; $dataToVerify .= $authenticatorData->getAttestedCredentialData()->getCredentialId(); $dataToVerify .= $this->extractPublicKey($authenticatorData->getAttestedCredentialData()->getCredentialPublicKey()); return openssl_verify($dataToVerify, $attestationStatement->get('sig'), $trustPath->getCertificates()[0], OPENSSL_ALGO_SHA256) === 1; } /** * @param string|null $publicKey Obvious * * @return string * @throws \Assert\AssertionFailedException * @since 4.2.0 */ private function extractPublicKey(?string $publicKey): string { Assertion::notNull($publicKey, 'The attested credential data does not contain a valid public key.'); $publicKeyStream = new StringStream($publicKey); $coseKey = $this->decoder->decode($publicKeyStream); Assertion::true($publicKeyStream->isEOF(), 'Invalid public key. Presence of extra bytes.'); $publicKeyStream->close(); Assertion::isInstanceOf($coseKey, MapObject::class, 'The attested credential data does not contain a valid public key.'); $coseKey = $coseKey->getNormalizedData(); $ec2Key = new Ec2Key($coseKey + [Ec2Key::TYPE => 2, Ec2Key::DATA_CURVE => Ec2Key::CURVE_P256]); return "\x04" . $ec2Key->x() . $ec2Key->y(); } /** * @param string $publicKey Obvious * * @return void * @throws \Assert\AssertionFailedException * @since 4.2.0 */ private function checkCertificate(string $publicKey): void { try { $resource = openssl_pkey_get_public($publicKey); if (version_compare(PHP_VERSION, '8.0', 'lt')) { Assertion::isResource($resource, 'Unable to read the certificate'); } else { /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */ Assertion::isInstanceOf($resource, \OpenSSLAsymmetricKey::class, 'Unable to read the certificate'); } } catch (\Throwable $throwable) { throw new \InvalidArgumentException('Invalid certificate or certificate chain', 0, $throwable); } $details = openssl_pkey_get_details($resource); Assertion::keyExists($details, 'ec', 'Invalid certificate or certificate chain'); Assertion::keyExists($details['ec'], 'curve_name', 'Invalid certificate or certificate chain'); Assertion::eq($details['ec']['curve_name'], 'prime256v1', 'Invalid certificate or certificate chain'); Assertion::keyExists($details['ec'], 'curve_oid', 'Invalid certificate or certificate chain'); Assertion::eq($details['ec']['curve_oid'], '1.2.840.10045.3.1.7', 'Invalid certificate or certificate chain'); } } Server.php 0000644 00000044066 15174372665 0006555 0 ustar 00 <?php /** * @package Joomla.Plugin * @subpackage System.Webauthn * * @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org> * @license MIT; see libraries/vendor/web-auth/webauthn-lib/LICENSE */ namespace Joomla\Plugin\System\Webauthn\Hotfix; use Assert\Assertion; use Cose\Algorithm\Algorithm; use Cose\Algorithm\ManagerFactory; use Cose\Algorithm\Signature\ECDSA; use Cose\Algorithm\Signature\EdDSA; use Cose\Algorithm\Signature\RSA; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestFactoryInterface; use Psr\Http\Message\ServerRequestInterface; use Webauthn\AttestationStatement\AndroidSafetyNetAttestationStatementSupport; use Webauthn\AttestationStatement\AttestationObjectLoader; use Webauthn\AttestationStatement\AttestationStatementSupportManager; use Webauthn\AttestationStatement\NoneAttestationStatementSupport; use Webauthn\AttestationStatement\PackedAttestationStatementSupport; use Webauthn\AttestationStatement\TPMAttestationStatementSupport; use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs; use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler; use Webauthn\AuthenticatorAssertionResponse; use Webauthn\AuthenticatorAssertionResponseValidator; use Webauthn\AuthenticatorAttestationResponse; use Webauthn\AuthenticatorAttestationResponseValidator; use Webauthn\AuthenticatorSelectionCriteria; use Webauthn\MetadataService\MetadataStatementRepository; use Webauthn\PublicKeyCredentialCreationOptions; use Webauthn\PublicKeyCredentialDescriptor; use Webauthn\PublicKeyCredentialLoader; use Webauthn\PublicKeyCredentialParameters; use Webauthn\PublicKeyCredentialRequestOptions; use Webauthn\PublicKeyCredentialRpEntity; use Webauthn\PublicKeyCredentialSource; use Webauthn\PublicKeyCredentialSourceRepository; use Webauthn\PublicKeyCredentialUserEntity; use Webauthn\TokenBinding\TokenBindingNotSupportedHandler; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Customised WebAuthn server object. * * We had to fork the server object from the WebAuthn server package to address an issue with PHP 8. * * We are currently using an older version of the WebAuthn library (2.x) which was written before * PHP 8 was developed. We cannot upgrade the WebAuthn library to a newer major version because of * Joomla's Semantic Versioning promise. * * The FidoU2FAttestationStatementSupport and AndroidKeyAttestationStatementSupport classes force * an assertion on the result of the openssl_pkey_get_public() function, assuming it will return a * resource. However, starting with PHP 8.0 this function returns an OpenSSLAsymmetricKey object * and the assertion fails. As a result, you cannot use Android or FIDO U2F keys with WebAuthn. * * The assertion check is in a private method, therefore we have to fork both attestation support * classes to change the assertion. The assertion takes place through a third party library we * cannot (and should not!) modify. * * The assertions objects, however, are injected to the attestation support manager in a private * method of the Server object. Because literally everything in this class is private we have no * option than to fork the entire class to apply our two forked attestation support classes. * * This is marked as deprecated because we'll be able to upgrade the WebAuthn library on Joomla 5. * * @since 4.2.0 * * @deprecated 4.2 will be removed in 6.0 * Will be removed without replacement * We will upgrade the WebAuthn library to version 3 or later and this will go away. */ final class Server extends \Webauthn\Server { /** * @var integer * @since 4.2.0 */ public $timeout = 60000; /** * @var integer * @since 4.2.0 */ public $challengeSize = 32; /** * @var PublicKeyCredentialRpEntity * @since 4.2.0 */ private $rpEntity; /** * @var ManagerFactory * @since 4.2.0 */ private $coseAlgorithmManagerFactory; /** * @var PublicKeyCredentialSourceRepository * @since 4.2.0 */ private $publicKeyCredentialSourceRepository; /** * @var TokenBindingNotSupportedHandler * @since 4.2.0 */ private $tokenBindingHandler; /** * @var ExtensionOutputCheckerHandler * @since 4.2.0 */ private $extensionOutputCheckerHandler; /** * @var string[] * @since 4.2.0 */ private $selectedAlgorithms; /** * @var MetadataStatementRepository|null * @since 4.2.0 */ private $metadataStatementRepository; /** * @var ClientInterface * @since 4.2.0 */ private $httpClient; /** * @var string * @since 4.2.0 */ private $googleApiKey; /** * @var RequestFactoryInterface * @since 4.2.0 */ private $requestFactory; /** * Overridden constructor. * * @param PublicKeyCredentialRpEntity $relayingParty Obvious * @param PublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository Obvious * @param MetadataStatementRepository|null $metadataStatementRepository Obvious * * @since 4.2.0 */ public function __construct( PublicKeyCredentialRpEntity $relayingParty, PublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository, ?MetadataStatementRepository $metadataStatementRepository ) { $this->rpEntity = $relayingParty; $this->coseAlgorithmManagerFactory = new ManagerFactory(); $this->coseAlgorithmManagerFactory->add('RS1', new RSA\RS1()); $this->coseAlgorithmManagerFactory->add('RS256', new RSA\RS256()); $this->coseAlgorithmManagerFactory->add('RS384', new RSA\RS384()); $this->coseAlgorithmManagerFactory->add('RS512', new RSA\RS512()); $this->coseAlgorithmManagerFactory->add('PS256', new RSA\PS256()); $this->coseAlgorithmManagerFactory->add('PS384', new RSA\PS384()); $this->coseAlgorithmManagerFactory->add('PS512', new RSA\PS512()); $this->coseAlgorithmManagerFactory->add('ES256', new ECDSA\ES256()); $this->coseAlgorithmManagerFactory->add('ES256K', new ECDSA\ES256K()); $this->coseAlgorithmManagerFactory->add('ES384', new ECDSA\ES384()); $this->coseAlgorithmManagerFactory->add('ES512', new ECDSA\ES512()); $this->coseAlgorithmManagerFactory->add('Ed25519', new EdDSA\Ed25519()); $this->selectedAlgorithms = ['RS256', 'RS512', 'PS256', 'PS512', 'ES256', 'ES512', 'Ed25519']; $this->publicKeyCredentialSourceRepository = $publicKeyCredentialSourceRepository; $this->tokenBindingHandler = new TokenBindingNotSupportedHandler(); $this->extensionOutputCheckerHandler = new ExtensionOutputCheckerHandler(); $this->metadataStatementRepository = $metadataStatementRepository; } /** * @param string[] $selectedAlgorithms Obvious * * @return void * @since 4.2.0 */ public function setSelectedAlgorithms(array $selectedAlgorithms): void { $this->selectedAlgorithms = $selectedAlgorithms; } /** * @param TokenBindingNotSupportedHandler $tokenBindingHandler Obvious * * @return void * @since 4.2.0 */ public function setTokenBindingHandler(TokenBindingNotSupportedHandler $tokenBindingHandler): void { $this->tokenBindingHandler = $tokenBindingHandler; } /** * @param string $alias Obvious * @param Algorithm $algorithm Obvious * * @return void * @since 4.2.0 */ public function addAlgorithm(string $alias, Algorithm $algorithm): void { $this->coseAlgorithmManagerFactory->add($alias, $algorithm); $this->selectedAlgorithms[] = $alias; $this->selectedAlgorithms = array_unique($this->selectedAlgorithms); } /** * @param ExtensionOutputCheckerHandler $extensionOutputCheckerHandler Obvious * * @return void * @since 4.2.0 */ public function setExtensionOutputCheckerHandler(ExtensionOutputCheckerHandler $extensionOutputCheckerHandler): void { $this->extensionOutputCheckerHandler = $extensionOutputCheckerHandler; } /** * @param string|null $userVerification Obvious * @param PublicKeyCredentialDescriptor[] $allowedPublicKeyDescriptors Obvious * @param AuthenticationExtensionsClientInputs|null $extensions Obvious * * @return PublicKeyCredentialRequestOptions * @throws \Exception * @since 4.2.0 */ public function generatePublicKeyCredentialRequestOptions( ?string $userVerification = PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_PREFERRED, array $allowedPublicKeyDescriptors = [], ?AuthenticationExtensionsClientInputs $extensions = null ): PublicKeyCredentialRequestOptions { return new PublicKeyCredentialRequestOptions( random_bytes($this->challengeSize), $this->timeout, $this->rpEntity->getId(), $allowedPublicKeyDescriptors, $userVerification, $extensions ?? new AuthenticationExtensionsClientInputs() ); } /** * @param PublicKeyCredentialUserEntity $userEntity Obvious * @param string|null $attestationMode Obvious * @param PublicKeyCredentialDescriptor[] $excludedPublicKeyDescriptors Obvious * @param AuthenticatorSelectionCriteria|null $criteria Obvious * @param AuthenticationExtensionsClientInputs|null $extensions Obvious * * @return PublicKeyCredentialCreationOptions * @throws \Exception * @since 4.2.0 */ public function generatePublicKeyCredentialCreationOptions( PublicKeyCredentialUserEntity $userEntity, ?string $attestationMode = PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE, array $excludedPublicKeyDescriptors = [], ?AuthenticatorSelectionCriteria $criteria = null, ?AuthenticationExtensionsClientInputs $extensions = null ): PublicKeyCredentialCreationOptions { $coseAlgorithmManager = $this->coseAlgorithmManagerFactory->create($this->selectedAlgorithms); $publicKeyCredentialParametersList = []; foreach ($coseAlgorithmManager->all() as $algorithm) { $publicKeyCredentialParametersList[] = new PublicKeyCredentialParameters( PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, $algorithm::identifier() ); } $criteria = $criteria ?? new AuthenticatorSelectionCriteria(); $extensions = $extensions ?? new AuthenticationExtensionsClientInputs(); $challenge = random_bytes($this->challengeSize); return new PublicKeyCredentialCreationOptions( $this->rpEntity, $userEntity, $challenge, $publicKeyCredentialParametersList, $this->timeout, $excludedPublicKeyDescriptors, $criteria, $attestationMode, $extensions ); } /** * @param string $data Obvious * @param PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions Obvious * @param ServerRequestInterface $serverRequest Obvious * * @return PublicKeyCredentialSource * @throws \Assert\AssertionFailedException * @since 4.2.0 */ public function loadAndCheckAttestationResponse( string $data, PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, ServerRequestInterface $serverRequest ): PublicKeyCredentialSource { $attestationStatementSupportManager = $this->getAttestationStatementSupportManager(); $attestationObjectLoader = new AttestationObjectLoader($attestationStatementSupportManager); $publicKeyCredentialLoader = new PublicKeyCredentialLoader($attestationObjectLoader); $publicKeyCredential = $publicKeyCredentialLoader->load($data); $authenticatorResponse = $publicKeyCredential->getResponse(); Assertion::isInstanceOf($authenticatorResponse, AuthenticatorAttestationResponse::class, 'Not an authenticator attestation response'); $authenticatorAttestationResponseValidator = new AuthenticatorAttestationResponseValidator( $attestationStatementSupportManager, $this->publicKeyCredentialSourceRepository, $this->tokenBindingHandler, $this->extensionOutputCheckerHandler ); return $authenticatorAttestationResponseValidator->check($authenticatorResponse, $publicKeyCredentialCreationOptions, $serverRequest); } /** * @param string $data Obvious * @param PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions Obvious * @param PublicKeyCredentialUserEntity|null $userEntity Obvious * @param ServerRequestInterface $serverRequest Obvious * * @return PublicKeyCredentialSource * @throws \Assert\AssertionFailedException * @since 4.2.0 */ public function loadAndCheckAssertionResponse( string $data, PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions, ?PublicKeyCredentialUserEntity $userEntity, ServerRequestInterface $serverRequest ): PublicKeyCredentialSource { $attestationStatementSupportManager = $this->getAttestationStatementSupportManager(); $attestationObjectLoader = new AttestationObjectLoader($attestationStatementSupportManager); $publicKeyCredentialLoader = new PublicKeyCredentialLoader($attestationObjectLoader); $publicKeyCredential = $publicKeyCredentialLoader->load($data); $authenticatorResponse = $publicKeyCredential->getResponse(); Assertion::isInstanceOf($authenticatorResponse, AuthenticatorAssertionResponse::class, 'Not an authenticator assertion response'); $authenticatorAssertionResponseValidator = new AuthenticatorAssertionResponseValidator( $this->publicKeyCredentialSourceRepository, null, $this->tokenBindingHandler, $this->extensionOutputCheckerHandler, $this->coseAlgorithmManagerFactory->create($this->selectedAlgorithms) ); return $authenticatorAssertionResponseValidator->check( $publicKeyCredential->getRawId(), $authenticatorResponse, $publicKeyCredentialRequestOptions, $serverRequest, null !== $userEntity ? $userEntity->getId() : null ); } /** * @param ClientInterface $client Obvious * @param string $apiKey Obvious * @param RequestFactoryInterface $requestFactory Obvious * * @return void * @since 4.2.0 */ public function enforceAndroidSafetyNetVerification( ClientInterface $client, string $apiKey, RequestFactoryInterface $requestFactory ): void { $this->httpClient = $client; $this->googleApiKey = $apiKey; $this->requestFactory = $requestFactory; } /** * @return AttestationStatementSupportManager * @since 4.2.0 */ private function getAttestationStatementSupportManager(): AttestationStatementSupportManager { $attestationStatementSupportManager = new AttestationStatementSupportManager(); $attestationStatementSupportManager->add(new NoneAttestationStatementSupport()); if ($this->metadataStatementRepository !== null) { $coseAlgorithmManager = $this->coseAlgorithmManagerFactory->create($this->selectedAlgorithms); $attestationStatementSupportManager->add(new FidoU2FAttestationStatementSupport(null, $this->metadataStatementRepository)); /** * Work around a third party library (web-token/jwt-signature-algorithm-eddsa) bug. * * On PHP 8 libsodium is compiled into PHP, it is not an extension. However, the third party library does * not check if the libsodium function are available; it checks if the "sodium" extension is loaded. This of * course causes an immediate failure with a Runtime exception EVEN IF the attested data isn't attested by * Android Safety Net. Therefore we have to not even load the AndroidSafetyNetAttestationStatementSupport * class in this case... */ if (function_exists('sodium_crypto_sign_seed_keypair') && function_exists('extension_loaded') && extension_loaded('sodium')) { $attestationStatementSupportManager->add( new AndroidSafetyNetAttestationStatementSupport( $this->httpClient, $this->googleApiKey, $this->requestFactory, 2000, 60000, $this->metadataStatementRepository ) ); } $attestationStatementSupportManager->add(new AndroidKeyAttestationStatementSupport(null, $this->metadataStatementRepository)); $attestationStatementSupportManager->add(new TPMAttestationStatementSupport($this->metadataStatementRepository)); $attestationStatementSupportManager->add( new PackedAttestationStatementSupport( null, $coseAlgorithmManager, $this->metadataStatementRepository ) ); } return $attestationStatementSupportManager; } } AndroidKeyAttestationStatementSupport.php 0000644 00000025772 15174372665 0015045 0 ustar 00 <?php /** * @package Joomla.Plugin * @subpackage System.Webauthn * * @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org> * @license MIT; see libraries/vendor/web-auth/webauthn-lib/LICENSE */ namespace Joomla\Plugin\System\Webauthn\Hotfix; use Assert\Assertion; use CBOR\Decoder; use CBOR\OtherObject\OtherObjectManager; use CBOR\Tag\TagObjectManager; use Cose\Algorithms; use Cose\Key\Ec2Key; use Cose\Key\Key; use Cose\Key\RsaKey; use FG\ASN1\ASNObject; use FG\ASN1\ExplicitlyTaggedObject; use FG\ASN1\Universal\OctetString; use FG\ASN1\Universal\Sequence; use Webauthn\AttestationStatement\AttestationStatement; use Webauthn\AttestationStatement\AttestationStatementSupport; use Webauthn\AuthenticatorData; use Webauthn\CertificateToolbox; use Webauthn\MetadataService\MetadataStatementRepository; use Webauthn\StringStream; use Webauthn\TrustPath\CertificateTrustPath; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * We had to fork the key attestation support object from the WebAuthn server package to address an * issue with PHP 8. * * We are currently using an older version of the WebAuthn library (2.x) which was written before * PHP 8 was developed. We cannot upgrade the WebAuthn library to a newer major version because of * Joomla's Semantic Versioning promise. * * The AndroidKeyAttestationStatementSupport class forces an assertion on the result of the * openssl_pkey_get_public() function, assuming it will return a resource. However, starting with * PHP 8.0 this function returns an OpenSSLAsymmetricKey object and the assertion fails. As a * result, you cannot use Android or FIDO U2F keys with WebAuthn. * * The assertion check is in a private method, therefore we have to fork both attestation support * class to change the assertion. The assertion takes place through a third party library we cannot * (and should not!) modify. * * @since 4.2.0 * * @deprecated 4.2 will be removed in 6.0 * Will be removed without replacement * We will upgrade the WebAuthn library to version 3 or later and this will go away. */ final class AndroidKeyAttestationStatementSupport implements AttestationStatementSupport { /** * @var Decoder * @since 4.2.0 */ private $decoder; /** * @var MetadataStatementRepository|null * @since 4.2.0 */ private $metadataStatementRepository; /** * @param Decoder|null $decoder Obvious * @param MetadataStatementRepository|null $metadataStatementRepository Obvious * * @since 4.2.0 */ public function __construct( ?Decoder $decoder = null, ?MetadataStatementRepository $metadataStatementRepository = null ) { if ($decoder !== null) { @trigger_error('The argument "$decoder" is deprecated since 2.1 and will be removed in v3.0. Set null instead', E_USER_DEPRECATED); } if ($metadataStatementRepository === null) { @trigger_error( 'Setting "null" for argument "$metadataStatementRepository" is deprecated since 2.1 and will be mandatory in v3.0.', E_USER_DEPRECATED ); } $this->decoder = $decoder ?? new Decoder(new TagObjectManager(), new OtherObjectManager()); $this->metadataStatementRepository = $metadataStatementRepository; } /** * @return string * @since 4.2.0 */ public function name(): string { return 'android-key'; } /** * @param array $attestation Obvious * * @return AttestationStatement * @throws \Assert\AssertionFailedException * @since 4.2.0 */ public function load(array $attestation): AttestationStatement { Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object'); foreach (['sig', 'x5c', 'alg'] as $key) { Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key)); } $certificates = $attestation['attStmt']['x5c']; Assertion::isArray($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.'); Assertion::greaterThan(\count($certificates), 0, 'The attestation statement value "x5c" must be a list with at least one certificate.'); Assertion::allString($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.'); $certificates = CertificateToolbox::convertAllDERToPEM($certificates); return AttestationStatement::createBasic($attestation['fmt'], $attestation['attStmt'], new CertificateTrustPath($certificates)); } /** * @param string $clientDataJSONHash Obvious * @param AttestationStatement $attestationStatement Obvious * @param AuthenticatorData $authenticatorData Obvious * * @return boolean * @throws \Assert\AssertionFailedException * @since 4.2.0 */ public function isValid( string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData ): bool { $trustPath = $attestationStatement->getTrustPath(); Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path'); $certificates = $trustPath->getCertificates(); if ($this->metadataStatementRepository !== null) { $certificates = CertificateToolbox::checkAttestationMedata( $attestationStatement, $authenticatorData->getAttestedCredentialData()->getAaguid()->toString(), $certificates, $this->metadataStatementRepository ); } // Decode leaf attestation certificate $leaf = $certificates[0]; $this->checkCertificateAndGetPublicKey($leaf, $clientDataJSONHash, $authenticatorData); $signedData = $authenticatorData->getAuthData() . $clientDataJSONHash; $alg = $attestationStatement->get('alg'); return openssl_verify($signedData, $attestationStatement->get('sig'), $leaf, Algorithms::getOpensslAlgorithmFor((int) $alg)) === 1; } /** * @param string $certificate Obvious * @param string $clientDataHash Obvious * @param AuthenticatorData $authenticatorData Obvious * * @return void * @throws \Assert\AssertionFailedException * @throws \FG\ASN1\Exception\ParserException * @since 4.2.0 */ private function checkCertificateAndGetPublicKey( string $certificate, string $clientDataHash, AuthenticatorData $authenticatorData ): void { $resource = openssl_pkey_get_public($certificate); if (version_compare(PHP_VERSION, '8.0', 'lt')) { Assertion::isResource($resource, 'Unable to read the certificate'); } else { /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */ Assertion::isInstanceOf($resource, \OpenSSLAsymmetricKey::class, 'Unable to read the certificate'); } $details = openssl_pkey_get_details($resource); Assertion::isArray($details, 'Unable to read the certificate'); // Check that authData publicKey matches the public key in the attestation certificate $attestedCredentialData = $authenticatorData->getAttestedCredentialData(); Assertion::notNull($attestedCredentialData, 'No attested credential data found'); $publicKeyData = $attestedCredentialData->getCredentialPublicKey(); Assertion::notNull($publicKeyData, 'No attested public key found'); $publicDataStream = new StringStream($publicKeyData); $coseKey = $this->decoder->decode($publicDataStream)->getNormalizedData(false); Assertion::true($publicDataStream->isEOF(), 'Invalid public key data. Presence of extra bytes.'); $publicDataStream->close(); $publicKey = Key::createFromData($coseKey); Assertion::true(($publicKey instanceof Ec2Key) || ($publicKey instanceof RsaKey), 'Unsupported key type'); Assertion::eq($publicKey->asPEM(), $details['key'], 'Invalid key'); $certDetails = openssl_x509_parse($certificate); // Find Android KeyStore Extension with OID “1.3.6.1.4.1.11129.2.1.17” in certificate extensions Assertion::keyExists($certDetails, 'extensions', 'The certificate has no extension'); Assertion::isArray($certDetails['extensions'], 'The certificate has no extension'); Assertion::keyExists( $certDetails['extensions'], '1.3.6.1.4.1.11129.2.1.17', 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is missing' ); $extension = $certDetails['extensions']['1.3.6.1.4.1.11129.2.1.17']; $extensionAsAsn1 = ASNObject::fromBinary($extension); Assertion::isInstanceOf($extensionAsAsn1, Sequence::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); $objects = $extensionAsAsn1->getChildren(); // Check that attestationChallenge is set to the clientDataHash. Assertion::keyExists($objects, 4, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); Assertion::isInstanceOf($objects[4], OctetString::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); Assertion::eq($clientDataHash, hex2bin(($objects[4])->getContent()), 'The client data hash is not valid'); // Check that both teeEnforced and softwareEnforced structures don’t contain allApplications(600) tag. Assertion::keyExists($objects, 6, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); $softwareEnforcedFlags = $objects[6]; Assertion::isInstanceOf($softwareEnforcedFlags, Sequence::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); $this->checkAbsenceOfAllApplicationsTag($softwareEnforcedFlags); Assertion::keyExists($objects, 7, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); $teeEnforcedFlags = $objects[6]; Assertion::isInstanceOf($teeEnforcedFlags, Sequence::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); $this->checkAbsenceOfAllApplicationsTag($teeEnforcedFlags); } /** * @param Sequence $sequence Obvious * * @return void * @throws \Assert\AssertionFailedException * @since 4.2.0 */ private function checkAbsenceOfAllApplicationsTag(Sequence $sequence): void { foreach ($sequence->getChildren() as $tag) { Assertion::isInstanceOf($tag, ExplicitlyTaggedObject::class, 'Invalid tag'); /** * @var ExplicitlyTaggedObject $tag It is silly that I have to do that for PHPCS to be happy. */ Assertion::notEq(600, (int) $tag->getTag(), 'Forbidden tag 600 found'); } } }
| ver. 1.4 |
Github
|
.
| PHP 8.3.23 | Generation time: 0 |
proxy
|
phpinfo
|
Settings