[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/vendor/web-auth/webauthn-lib/src/AttestationStatement/ -> PackedAttestationStatementSupport.php (source)

   1  <?php
   2  
   3  declare(strict_types=1);
   4  
   5  /*
   6   * The MIT License (MIT)
   7   *
   8   * Copyright (c) 2014-2019 Spomky-Labs
   9   *
  10   * This software may be modified and distributed under the terms
  11   * of the MIT license.  See the LICENSE file for details.
  12   */
  13  
  14  namespace Webauthn\AttestationStatement;
  15  
  16  use Assert\Assertion;
  17  use CBOR\Decoder;
  18  use CBOR\MapObject;
  19  use CBOR\OtherObject\OtherObjectManager;
  20  use CBOR\Tag\TagObjectManager;
  21  use Cose\Algorithm\Manager;
  22  use Cose\Algorithm\Signature\Signature;
  23  use Cose\Algorithms;
  24  use Cose\Key\Key;
  25  use InvalidArgumentException;
  26  use RuntimeException;
  27  use Webauthn\AuthenticatorData;
  28  use Webauthn\CertificateToolbox;
  29  use Webauthn\MetadataService\MetadataStatementRepository;
  30  use Webauthn\StringStream;
  31  use Webauthn\TrustPath\CertificateTrustPath;
  32  use Webauthn\TrustPath\EcdaaKeyIdTrustPath;
  33  use Webauthn\TrustPath\EmptyTrustPath;
  34  use Webauthn\Util\CoseSignatureFixer;
  35  
  36  final class PackedAttestationStatementSupport implements AttestationStatementSupport
  37  {
  38      /**
  39       * @var Decoder
  40       */
  41      private $decoder;
  42  
  43      /**
  44       * @var Manager
  45       */
  46      private $algorithmManager;
  47  
  48      /**
  49       * @var MetadataStatementRepository|null
  50       */
  51      private $metadataStatementRepository;
  52  
  53      public function __construct(?Decoder $decoder, Manager $algorithmManager, ?MetadataStatementRepository $metadataStatementRepository = null)
  54      {
  55          if (null !== $decoder) {
  56              @trigger_error('The argument "$decoder" is deprecated since 2.1 and will be removed in v3.0. Set null instead', E_USER_DEPRECATED);
  57          }
  58          if (null === $metadataStatementRepository) {
  59              @trigger_error('Setting "null" for argument "$metadataStatementRepository" is deprecated since 2.1 and will be mandatory in v3.0.', E_USER_DEPRECATED);
  60          }
  61          $this->decoder = $decoder ?? new Decoder(new TagObjectManager(), new OtherObjectManager());
  62          $this->algorithmManager = $algorithmManager;
  63          $this->metadataStatementRepository = $metadataStatementRepository;
  64      }
  65  
  66      public function name(): string
  67      {
  68          return 'packed';
  69      }
  70  
  71      public function load(array $attestation): AttestationStatement
  72      {
  73          Assertion::keyExists($attestation['attStmt'], 'sig', 'The attestation statement value "sig" is missing.');
  74          Assertion::keyExists($attestation['attStmt'], 'alg', 'The attestation statement value "alg" is missing.');
  75          Assertion::string($attestation['attStmt']['sig'], 'The attestation statement value "sig" is missing.');
  76          switch (true) {
  77              case \array_key_exists('x5c', $attestation['attStmt']):
  78                  return $this->loadBasicType($attestation);
  79              case \array_key_exists('ecdaaKeyId', $attestation['attStmt']):
  80                  return $this->loadEcdaaType($attestation['attStmt']);
  81              default:
  82                  return $this->loadEmptyType($attestation);
  83          }
  84      }
  85  
  86      public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool
  87      {
  88          $trustPath = $attestationStatement->getTrustPath();
  89          switch (true) {
  90              case $trustPath instanceof CertificateTrustPath:
  91                  return $this->processWithCertificate($clientDataJSONHash, $attestationStatement, $authenticatorData, $trustPath);
  92              case $trustPath instanceof EcdaaKeyIdTrustPath:
  93                  return $this->processWithECDAA();
  94              case $trustPath instanceof EmptyTrustPath:
  95                  return $this->processWithSelfAttestation($clientDataJSONHash, $attestationStatement, $authenticatorData);
  96              default:
  97                  throw new InvalidArgumentException('Unsupported attestation statement');
  98          }
  99      }
 100  
 101      private function loadBasicType(array $attestation): AttestationStatement
 102      {
 103          $certificates = $attestation['attStmt']['x5c'];
 104          Assertion::isArray($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.');
 105          Assertion::minCount($certificates, 1, 'The attestation statement value "x5c" must be a list with at least one certificate.');
 106          $certificates = CertificateToolbox::convertAllDERToPEM($certificates);
 107  
 108          return AttestationStatement::createBasic($attestation['fmt'], $attestation['attStmt'], new CertificateTrustPath($certificates));
 109      }
 110  
 111      private function loadEcdaaType(array $attestation): AttestationStatement
 112      {
 113          $ecdaaKeyId = $attestation['attStmt']['ecdaaKeyId'];
 114          Assertion::string($ecdaaKeyId, 'The attestation statement value "ecdaaKeyId" is invalid.');
 115  
 116          return AttestationStatement::createEcdaa($attestation['fmt'], $attestation['attStmt'], new EcdaaKeyIdTrustPath($attestation['ecdaaKeyId']));
 117      }
 118  
 119      private function loadEmptyType(array $attestation): AttestationStatement
 120      {
 121          return AttestationStatement::createSelf($attestation['fmt'], $attestation['attStmt'], new EmptyTrustPath());
 122      }
 123  
 124      private function checkCertificate(string $attestnCert, AuthenticatorData $authenticatorData): void
 125      {
 126          $parsed = openssl_x509_parse($attestnCert);
 127          Assertion::isArray($parsed, 'Invalid certificate');
 128  
 129          //Check version
 130          Assertion::false(!isset($parsed['version']) || 2 !== $parsed['version'], 'Invalid certificate version');
 131  
 132          //Check subject field
 133          Assertion::false(!isset($parsed['name']) || false === mb_strpos($parsed['name'], '/OU=Authenticator Attestation'), 'Invalid certificate name. The Subject Organization Unit must be "Authenticator Attestation"');
 134  
 135          //Check extensions
 136          Assertion::false(!isset($parsed['extensions']) || !\is_array($parsed['extensions']), 'Certificate extensions are missing');
 137  
 138          //Check certificate is not a CA cert
 139          Assertion::false(!isset($parsed['extensions']['basicConstraints']) || 'CA:FALSE' !== $parsed['extensions']['basicConstraints'], 'The Basic Constraints extension must have the CA component set to false');
 140  
 141          $attestedCredentialData = $authenticatorData->getAttestedCredentialData();
 142          Assertion::notNull($attestedCredentialData, 'No attested credential available');
 143  
 144          // id-fido-gen-ce-aaguid OID check
 145          Assertion::false(\in_array('1.3.6.1.4.1.45724.1.1.4', $parsed['extensions'], true) && !hash_equals($attestedCredentialData->getAaguid()->getBytes(), $parsed['extensions']['1.3.6.1.4.1.45724.1.1.4']), 'The value of the "aaguid" does not match with the certificate');
 146      }
 147  
 148      private function processWithCertificate(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData, CertificateTrustPath $trustPath): bool
 149      {
 150          $certificates = $trustPath->getCertificates();
 151  
 152          if (null !== $this->metadataStatementRepository) {
 153              $certificates = CertificateToolbox::checkAttestationMedata(
 154                  $attestationStatement,
 155                  $authenticatorData->getAttestedCredentialData()->getAaguid()->toString(),
 156                  $certificates,
 157                  $this->metadataStatementRepository
 158              );
 159          }
 160  
 161          // Check leaf certificate
 162          $this->checkCertificate($certificates[0], $authenticatorData);
 163  
 164          // Get the COSE algorithm identifier and the corresponding OpenSSL one
 165          $coseAlgorithmIdentifier = (int) $attestationStatement->get('alg');
 166          $opensslAlgorithmIdentifier = Algorithms::getOpensslAlgorithmFor($coseAlgorithmIdentifier);
 167  
 168          // Verification of the signature
 169          $signedData = $authenticatorData->getAuthData().$clientDataJSONHash;
 170          $result = openssl_verify($signedData, $attestationStatement->get('sig'), $certificates[0], $opensslAlgorithmIdentifier);
 171  
 172          return 1 === $result;
 173      }
 174  
 175      private function processWithECDAA(): bool
 176      {
 177          throw new RuntimeException('ECDAA not supported');
 178      }
 179  
 180      private function processWithSelfAttestation(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool
 181      {
 182          $attestedCredentialData = $authenticatorData->getAttestedCredentialData();
 183          Assertion::notNull($attestedCredentialData, 'No attested credential available');
 184          $credentialPublicKey = $attestedCredentialData->getCredentialPublicKey();
 185          Assertion::notNull($credentialPublicKey, 'No credential public key available');
 186          $publicKeyStream = new StringStream($credentialPublicKey);
 187          $publicKey = $this->decoder->decode($publicKeyStream);
 188          Assertion::true($publicKeyStream->isEOF(), 'Invalid public key. Presence of extra bytes.');
 189          $publicKeyStream->close();
 190          Assertion::isInstanceOf($publicKey, MapObject::class, 'The attested credential data does not contain a valid public key.');
 191          $publicKey = $publicKey->getNormalizedData(false);
 192          $publicKey = new Key($publicKey);
 193          Assertion::eq($publicKey->alg(), (int) $attestationStatement->get('alg'), 'The algorithm of the attestation statement and the key are not identical.');
 194  
 195          $dataToVerify = $authenticatorData->getAuthData().$clientDataJSONHash;
 196          $algorithm = $this->algorithmManager->get((int) $attestationStatement->get('alg'));
 197          if (!$algorithm instanceof Signature) {
 198              throw new RuntimeException('Invalid algorithm');
 199          }
 200          $signature = CoseSignatureFixer::fix($attestationStatement->get('sig'), $algorithm);
 201  
 202          return $algorithm->verify($dataToVerify, $publicKey, $signature);
 203      }
 204  }


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