[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

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

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


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