[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/plugins/multifactorauth/webauthn/src/Hotfix/ -> FidoU2FAttestationStatementSupport.php (source)

   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 CBOR\Decoder;
  19  use CBOR\MapObject;
  20  use CBOR\OtherObject\OtherObjectManager;
  21  use CBOR\Tag\TagObjectManager;
  22  use Cose\Key\Ec2Key;
  23  use Webauthn\AttestationStatement\AttestationStatement;
  24  use Webauthn\AttestationStatement\AttestationStatementSupport;
  25  use Webauthn\AuthenticatorData;
  26  use Webauthn\CertificateToolbox;
  27  use Webauthn\MetadataService\MetadataStatementRepository;
  28  use Webauthn\StringStream;
  29  use Webauthn\TrustPath\CertificateTrustPath;
  30  
  31  // phpcs:disable PSR1.Files.SideEffects
  32  \defined('_JEXEC') or die;
  33  // phpcs:enable PSR1.Files.SideEffects
  34  
  35  /**
  36   * We had to fork the key attestation support object from the WebAuthn server package to address an
  37   * issue with PHP 8.
  38   *
  39   * We are currently using an older version of the WebAuthn library (2.x) which was written before
  40   * PHP 8 was developed. We cannot upgrade the WebAuthn library to a newer major version because of
  41   * Joomla's Semantic Versioning promise.
  42   *
  43   * The FidoU2FAttestationStatementSupport class forces an assertion on the result of the
  44   * openssl_pkey_get_public() function, assuming it will return a resource. However, starting with
  45   * PHP 8.0 this function returns an OpenSSLAsymmetricKey object and the assertion fails. As a
  46   * result, you cannot use Android or FIDO U2F keys with WebAuthn.
  47   *
  48   * The assertion check is in a private method, therefore we have to fork both attestation support
  49   * class to change the assertion. The assertion takes place through a third party library we cannot
  50   * (and should not!) modify.
  51   *
  52   * @since   4.2.0
  53   *
  54   * @deprecated 5.0 We will upgrade the WebAuthn library to version 3 or later and this will go away.
  55   */
  56  final class FidoU2FAttestationStatementSupport implements AttestationStatementSupport
  57  {
  58      /**
  59       * @var   Decoder
  60       * @since 4.2.0
  61       */
  62      private $decoder;
  63  
  64      /**
  65       * @var   MetadataStatementRepository|null
  66       * @since 4.2.0
  67       */
  68      private $metadataStatementRepository;
  69  
  70      /**
  71       * @param   Decoder|null                      $decoder                      Obvious
  72       * @param   MetadataStatementRepository|null  $metadataStatementRepository  Obvious
  73       *
  74       * @since   4.2.0
  75       */
  76      public function __construct(
  77          ?Decoder $decoder = null,
  78          ?MetadataStatementRepository $metadataStatementRepository = null
  79      ) {
  80          if ($decoder !== null) {
  81              @trigger_error('The argument "$decoder" is deprecated since 2.1 and will be removed in v3.0. Set null instead', E_USER_DEPRECATED);
  82          }
  83  
  84          if ($metadataStatementRepository === null) {
  85              @trigger_error(
  86                  'Setting "null" for argument "$metadataStatementRepository" is deprecated since 2.1 and will be mandatory in v3.0.',
  87                  E_USER_DEPRECATED
  88              );
  89          }
  90  
  91          $this->decoder = $decoder ?? new Decoder(new TagObjectManager(), new OtherObjectManager());
  92          $this->metadataStatementRepository = $metadataStatementRepository;
  93      }
  94  
  95      /**
  96       * @return  string
  97       * @since   4.2.0
  98       */
  99      public function name(): string
 100      {
 101          return 'fido-u2f';
 102      }
 103  
 104      /**
 105       * @param   array  $attestation Obvious
 106       *
 107       * @return AttestationStatement
 108       * @throws \Assert\AssertionFailedException
 109       *
 110       * @since   4.2.0
 111       */
 112      public function load(array $attestation): AttestationStatement
 113      {
 114          Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object');
 115  
 116          foreach (['sig', 'x5c'] as $key) {
 117              Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key));
 118          }
 119  
 120          $certificates = $attestation['attStmt']['x5c'];
 121          Assertion::isArray($certificates, 'The attestation statement value "x5c" must be a list with one certificate.');
 122          Assertion::count($certificates, 1, 'The attestation statement value "x5c" must be a list with one certificate.');
 123          Assertion::allString($certificates, 'The attestation statement value "x5c" must be a list with one certificate.');
 124  
 125          reset($certificates);
 126          $certificates = CertificateToolbox::convertAllDERToPEM($certificates);
 127          $this->checkCertificate($certificates[0]);
 128  
 129          return AttestationStatement::createBasic($attestation['fmt'], $attestation['attStmt'], new CertificateTrustPath($certificates));
 130      }
 131  
 132      /**
 133       * @param   string                $clientDataJSONHash    Obvious
 134       * @param   AttestationStatement  $attestationStatement  Obvious
 135       * @param   AuthenticatorData     $authenticatorData     Obvious
 136       *
 137       * @return  boolean
 138       * @throws  \Assert\AssertionFailedException
 139       * @since   4.2.0
 140       */
 141      public function isValid(
 142          string $clientDataJSONHash,
 143          AttestationStatement $attestationStatement,
 144          AuthenticatorData $authenticatorData
 145      ): bool {
 146          Assertion::eq(
 147              $authenticatorData->getAttestedCredentialData()->getAaguid()->toString(),
 148              '00000000-0000-0000-0000-000000000000',
 149              'Invalid AAGUID for fido-u2f attestation statement. Shall be "00000000-0000-0000-0000-000000000000"'
 150          );
 151  
 152          if ($this->metadataStatementRepository !== null) {
 153              CertificateToolbox::checkAttestationMedata(
 154                  $attestationStatement,
 155                  $authenticatorData->getAttestedCredentialData()->getAaguid()->toString(),
 156                  [],
 157                  $this->metadataStatementRepository
 158              );
 159          }
 160  
 161          $trustPath = $attestationStatement->getTrustPath();
 162          Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path');
 163          $dataToVerify = "\0";
 164          $dataToVerify .= $authenticatorData->getRpIdHash();
 165          $dataToVerify .= $clientDataJSONHash;
 166          $dataToVerify .= $authenticatorData->getAttestedCredentialData()->getCredentialId();
 167          $dataToVerify .= $this->extractPublicKey($authenticatorData->getAttestedCredentialData()->getCredentialPublicKey());
 168  
 169          return openssl_verify($dataToVerify, $attestationStatement->get('sig'), $trustPath->getCertificates()[0], OPENSSL_ALGO_SHA256) === 1;
 170      }
 171  
 172      /**
 173       * @param   string|null  $publicKey Obvious
 174       *
 175       * @return  string
 176       * @throws  \Assert\AssertionFailedException
 177       * @since   4.2.0
 178       */
 179      private function extractPublicKey(?string $publicKey): string
 180      {
 181          Assertion::notNull($publicKey, 'The attested credential data does not contain a valid public key.');
 182  
 183          $publicKeyStream = new StringStream($publicKey);
 184          $coseKey = $this->decoder->decode($publicKeyStream);
 185          Assertion::true($publicKeyStream->isEOF(), 'Invalid public key. Presence of extra bytes.');
 186          $publicKeyStream->close();
 187          Assertion::isInstanceOf($coseKey, MapObject::class, 'The attested credential data does not contain a valid public key.');
 188  
 189          $coseKey = $coseKey->getNormalizedData();
 190          $ec2Key = new Ec2Key($coseKey + [Ec2Key::TYPE => 2, Ec2Key::DATA_CURVE => Ec2Key::CURVE_P256]);
 191  
 192          return "\x04" . $ec2Key->x() . $ec2Key->y();
 193      }
 194  
 195      /**
 196       * @param   string  $publicKey Obvious
 197       *
 198       * @return  void
 199       * @throws  \Assert\AssertionFailedException
 200       * @since   4.2.0
 201       */
 202      private function checkCertificate(string $publicKey): void
 203      {
 204          try {
 205              $resource = openssl_pkey_get_public($publicKey);
 206  
 207              if (version_compare(PHP_VERSION, '8.0', 'lt')) {
 208                  Assertion::isResource($resource, 'Unable to read the certificate');
 209              } else {
 210                  /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */
 211                  Assertion::isInstanceOf($resource, \OpenSSLAsymmetricKey::class, 'Unable to read the certificate');
 212              }
 213          } catch (\Throwable $throwable) {
 214              throw new \InvalidArgumentException('Invalid certificate or certificate chain', 0, $throwable);
 215          }
 216  
 217          $details = openssl_pkey_get_details($resource);
 218          Assertion::keyExists($details, 'ec', 'Invalid certificate or certificate chain');
 219          Assertion::keyExists($details['ec'], 'curve_name', 'Invalid certificate or certificate chain');
 220          Assertion::eq($details['ec']['curve_name'], 'prime256v1', 'Invalid certificate or certificate chain');
 221          Assertion::keyExists($details['ec'], 'curve_oid', 'Invalid certificate or certificate chain');
 222          Assertion::eq($details['ec']['curve_oid'], '1.2.840.10045.3.1.7', 'Invalid certificate or certificate chain');
 223      }
 224  }


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer