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  
   4  namespace lbuchs\WebAuthn\Attestation\Format;
   5  use lbuchs\WebAuthn\WebAuthnException;
   6  use lbuchs\WebAuthn\Attestation\AuthenticatorData;
   7  
   8  
   9  abstract class FormatBase {
  10      protected $_attestationObject = null;
  11      protected $_authenticatorData = null;
  12      protected $_x5c_chain = array();
  13      protected $_x5c_tempFile = null;
  14  
  15      /**
  16       *
  17       * @param Array $AttestionObject
  18       * @param AuthenticatorData $authenticatorData
  19       */
  20      public function __construct($AttestionObject, AuthenticatorData $authenticatorData) {
  21          $this->_attestationObject = $AttestionObject;
  22          $this->_authenticatorData = $authenticatorData;
  23      }
  24  
  25      /**
  26       *
  27       */
  28      public function __destruct() {
  29          // delete X.509 chain certificate file after use
  30          if ($this->_x5c_tempFile && \is_file($this->_x5c_tempFile)) {
  31              \unlink($this->_x5c_tempFile);
  32          }
  33      }
  34  
  35      /**
  36       * returns the certificate chain in PEM format
  37       * @return string|null
  38       */
  39      public function getCertificateChain() {
  40          if ($this->_x5c_tempFile && \is_file($this->_x5c_tempFile)) {
  41              return \file_get_contents($this->_x5c_tempFile);
  42          }
  43          return null;
  44      }
  45  
  46      /**
  47       * returns the key X.509 certificate in PEM format
  48       * @return string
  49       */
  50      public function getCertificatePem() {
  51          // need to be overwritten
  52          return null;
  53      }
  54  
  55      /**
  56       * checks validity of the signature
  57       * @param string $clientDataHash
  58       * @return bool
  59       * @throws WebAuthnException
  60       */
  61      public function validateAttestation($clientDataHash) {
  62          // need to be overwritten
  63          return false;
  64      }
  65  
  66      /**
  67       * validates the certificate against root certificates
  68       * @param array $rootCas
  69       * @return boolean
  70       * @throws WebAuthnException
  71       */
  72      public function validateRootCertificate($rootCas) {
  73          // need to be overwritten
  74          return false;
  75      }
  76  
  77  
  78      /**
  79       * create a PEM encoded certificate with X.509 binary data
  80       * @param string $x5c
  81       * @return string
  82       */
  83      protected function _createCertificatePem($x5c) {
  84          $pem = '-----BEGIN CERTIFICATE-----' . "\n";
  85          $pem .= \chunk_split(\base64_encode($x5c), 64, "\n");
  86          $pem .= '-----END CERTIFICATE-----' . "\n";
  87          return $pem;
  88      }
  89  
  90      /**
  91       * creates a PEM encoded chain file
  92       * @return type
  93       */
  94      protected function _createX5cChainFile() {
  95          $content = '';
  96          if (\is_array($this->_x5c_chain) && \count($this->_x5c_chain) > 0) {
  97              foreach ($this->_x5c_chain as $x5c) {
  98                  $certInfo = \openssl_x509_parse($this->_createCertificatePem($x5c));
  99  
 100                  // check if certificate is self signed
 101                  if (\is_array($certInfo) && \is_array($certInfo['issuer']) && \is_array($certInfo['subject'])) {
 102                      $selfSigned = false;
 103  
 104                      $subjectKeyIdentifier = $certInfo['extensions']['subjectKeyIdentifier'] ?? null;
 105                      $authorityKeyIdentifier = $certInfo['extensions']['authorityKeyIdentifier'] ?? null;
 106  
 107                      if ($authorityKeyIdentifier && substr($authorityKeyIdentifier, 0, 6) === 'keyid:') {
 108                          $authorityKeyIdentifier = substr($authorityKeyIdentifier, 6);
 109                      }
 110                      if ($subjectKeyIdentifier && substr($subjectKeyIdentifier, 0, 6) === 'keyid:') {
 111                          $subjectKeyIdentifier = substr($subjectKeyIdentifier, 6);
 112                      }
 113  
 114                      if (($subjectKeyIdentifier && !$authorityKeyIdentifier) || ($authorityKeyIdentifier && $authorityKeyIdentifier === $subjectKeyIdentifier)) {
 115                          $selfSigned = true;
 116                      }
 117  
 118                      if (!$selfSigned) {
 119                          $content .= "\n" . $this->_createCertificatePem($x5c) . "\n";
 120                      }
 121                  }
 122              }
 123          }
 124  
 125          if ($content) {
 126              $this->_x5c_tempFile = \sys_get_temp_dir() . '/x5c_chain_' . \base_convert(\rand(), 10, 36) . '.pem';
 127              if (\file_put_contents($this->_x5c_tempFile, $content) !== false) {
 128                  return $this->_x5c_tempFile;
 129              }
 130          }
 131  
 132          return null;
 133      }
 134  
 135  
 136      /**
 137       * returns the name and openssl key for provided cose number.
 138       * @param int $coseNumber
 139       * @return \stdClass|null
 140       */
 141      protected function _getCoseAlgorithm($coseNumber) {
 142          // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
 143          $coseAlgorithms = array(
 144              array(
 145                  'hash' => 'SHA1',
 146                  'openssl' => OPENSSL_ALGO_SHA1,
 147                  'cose' => array(
 148                      -65535  // RS1
 149                  )),
 150  
 151              array(
 152                  'hash' => 'SHA256',
 153                  'openssl' => OPENSSL_ALGO_SHA256,
 154                  'cose' => array(
 155                      -257, // RS256
 156                      -37,  // PS256
 157                      -7,   // ES256
 158                      5     // HMAC256
 159                  )),
 160  
 161              array(
 162                  'hash' => 'SHA384',
 163                  'openssl' => OPENSSL_ALGO_SHA384,
 164                  'cose' => array(
 165                      -258, // RS384
 166                      -38,  // PS384
 167                      -35,  // ES384
 168                      6     // HMAC384
 169                  )),
 170  
 171              array(
 172                  'hash' => 'SHA512',
 173                  'openssl' => OPENSSL_ALGO_SHA512,
 174                  'cose' => array(
 175                      -259, // RS512
 176                      -39,  // PS512
 177                      -36,  // ES512
 178                      7     // HMAC512
 179                  ))
 180          );
 181  
 182          foreach ($coseAlgorithms as $coseAlgorithm) {
 183              if (\in_array($coseNumber, $coseAlgorithm['cose'], true)) {
 184                  $return = new \stdClass();
 185                  $return->hash = $coseAlgorithm['hash'];
 186                  $return->openssl = $coseAlgorithm['openssl'];
 187                  return $return;
 188              }
 189          }
 190  
 191          return null;
 192      }
 193  }