Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.
   1  <?php
   2  
   3  namespace lbuchs\WebAuthn\Attestation;
   4  use lbuchs\WebAuthn\WebAuthnException;
   5  use lbuchs\WebAuthn\CBOR\CborDecoder;
   6  use lbuchs\WebAuthn\Binary\ByteBuffer;
   7  
   8  /**
   9   * @author Lukas Buchs
  10   * @license https://github.com/lbuchs/WebAuthn/blob/master/LICENSE MIT
  11   */
  12  class AttestationObject {
  13      private $_authenticatorData;
  14      private $_attestationFormat;
  15      private $_attestationFormatName;
  16  
  17      public function __construct($binary , $allowedFormats) {
  18          $enc = CborDecoder::decode($binary);
  19          // validation
  20          if (!\is_array($enc) || !\array_key_exists('fmt', $enc) || !is_string($enc['fmt'])) {
  21              throw new WebAuthnException('invalid attestation format', WebAuthnException::INVALID_DATA);
  22          }
  23  
  24          if (!\array_key_exists('attStmt', $enc) || !\is_array($enc['attStmt'])) {
  25              throw new WebAuthnException('invalid attestation format (attStmt not available)', WebAuthnException::INVALID_DATA);
  26          }
  27  
  28          if (!\array_key_exists('authData', $enc) || !\is_object($enc['authData']) || !($enc['authData'] instanceof ByteBuffer)) {
  29              throw new WebAuthnException('invalid attestation format (authData not available)', WebAuthnException::INVALID_DATA);
  30          }
  31  
  32          $this->_authenticatorData = new AuthenticatorData($enc['authData']->getBinaryString());
  33          $this->_attestationFormatName = $enc['fmt'];
  34  
  35          // Format ok?
  36          if (!in_array($this->_attestationFormatName, $allowedFormats)) {
  37              throw new WebAuthnException('invalid atttestation format: ' . $this->_attestationFormatName, WebAuthnException::INVALID_DATA);
  38          }
  39  
  40  
  41          switch ($this->_attestationFormatName) {
  42              case 'android-key': $this->_attestationFormat = new Format\AndroidKey($enc, $this->_authenticatorData); break;
  43              case 'android-safetynet': $this->_attestationFormat = new Format\AndroidSafetyNet($enc, $this->_authenticatorData); break;
  44              case 'apple': $this->_attestationFormat = new Format\Apple($enc, $this->_authenticatorData); break;
  45              case 'fido-u2f': $this->_attestationFormat = new Format\U2f($enc, $this->_authenticatorData); break;
  46              case 'none': $this->_attestationFormat = new Format\None($enc, $this->_authenticatorData); break;
  47              case 'packed': $this->_attestationFormat = new Format\Packed($enc, $this->_authenticatorData); break;
  48              case 'tpm': $this->_attestationFormat = new Format\Tpm($enc, $this->_authenticatorData); break;
  49              default: throw new WebAuthnException('invalid attestation format: ' . $enc['fmt'], WebAuthnException::INVALID_DATA);
  50          }
  51      }
  52  
  53      /**
  54       * returns the attestation format name
  55       * @return string
  56       */
  57      public function getAttestationFormatName() {
  58          return $this->_attestationFormatName;
  59      }
  60  
  61      /**
  62       * returns the attestation format class
  63       * @return Format\FormatBase
  64       */
  65      public function getAttestationFormat() {
  66          return $this->_attestationFormat;
  67      }
  68  
  69      /**
  70       * returns the attestation public key in PEM format
  71       * @return AuthenticatorData
  72       */
  73      public function getAuthenticatorData() {
  74          return $this->_authenticatorData;
  75      }
  76  
  77      /**
  78       * returns the certificate chain as PEM
  79       * @return string|null
  80       */
  81      public function getCertificateChain() {
  82          return $this->_attestationFormat->getCertificateChain();
  83      }
  84  
  85      /**
  86       * return the certificate issuer as string
  87       * @return string
  88       */
  89      public function getCertificateIssuer() {
  90          $pem = $this->getCertificatePem();
  91          $issuer = '';
  92          if ($pem) {
  93              $certInfo = \openssl_x509_parse($pem);
  94              if (\is_array($certInfo) && \array_key_exists('issuer', $certInfo) && \is_array($certInfo['issuer'])) {
  95  
  96                  $cn = $certInfo['issuer']['CN'] ?? '';
  97                  $o = $certInfo['issuer']['O'] ?? '';
  98                  $ou = $certInfo['issuer']['OU'] ?? '';
  99  
 100                  if ($cn) {
 101                      $issuer .= $cn;
 102                  }
 103                  if ($issuer && ($o || $ou)) {
 104                      $issuer .= ' (' . trim($o . ' ' . $ou) . ')';
 105                  } else {
 106                      $issuer .= trim($o . ' ' . $ou);
 107                  }
 108              }
 109          }
 110  
 111          return $issuer;
 112      }
 113  
 114      /**
 115       * return the certificate subject as string
 116       * @return string
 117       */
 118      public function getCertificateSubject() {
 119          $pem = $this->getCertificatePem();
 120          $subject = '';
 121          if ($pem) {
 122              $certInfo = \openssl_x509_parse($pem);
 123              if (\is_array($certInfo) && \array_key_exists('subject', $certInfo) && \is_array($certInfo['subject'])) {
 124  
 125                  $cn = $certInfo['subject']['CN'] ?? '';
 126                  $o = $certInfo['subject']['O'] ?? '';
 127                  $ou = $certInfo['subject']['OU'] ?? '';
 128  
 129                  if ($cn) {
 130                      $subject .= $cn;
 131                  }
 132                  if ($subject && ($o || $ou)) {
 133                      $subject .= ' (' . trim($o . ' ' . $ou) . ')';
 134                  } else {
 135                      $subject .= trim($o . ' ' . $ou);
 136                  }
 137              }
 138          }
 139  
 140          return $subject;
 141      }
 142  
 143      /**
 144       * returns the key certificate in PEM format
 145       * @return string
 146       */
 147      public function getCertificatePem() {
 148          return $this->_attestationFormat->getCertificatePem();
 149      }
 150  
 151      /**
 152       * checks validity of the signature
 153       * @param string $clientDataHash
 154       * @return bool
 155       * @throws WebAuthnException
 156       */
 157      public function validateAttestation($clientDataHash) {
 158          return $this->_attestationFormat->validateAttestation($clientDataHash);
 159      }
 160  
 161      /**
 162       * validates the certificate against root certificates
 163       * @param array $rootCas
 164       * @return boolean
 165       * @throws WebAuthnException
 166       */
 167      public function validateRootCertificate($rootCas) {
 168          return $this->_attestationFormat->validateRootCertificate($rootCas);
 169      }
 170  
 171      /**
 172       * checks if the RpId-Hash is valid
 173       * @param string$rpIdHash
 174       * @return bool
 175       */
 176      public function validateRpIdHash($rpIdHash) {
 177          return $rpIdHash === $this->_authenticatorData->getRpIdHash();
 178      }
 179  }