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\Binary;
   5  use lbuchs\WebAuthn\WebAuthnException;
   6  
   7  /**
   8   * Modified version of https://github.com/madwizard-thomas/webauthn-server/blob/master/src/Format/ByteBuffer.php
   9   * Copyright © 2018 Thomas Bleeker - MIT licensed
  10   * Modified by Lukas Buchs
  11   * Thanks Thomas for your work!
  12   */
  13  class ByteBuffer implements \JsonSerializable, \Serializable {
  14      /**
  15       * @var bool
  16       */
  17      public static $useBase64UrlEncoding = false;
  18  
  19      /**
  20       * @var string
  21       */
  22      private $_data;
  23  
  24      /**
  25       * @var int
  26       */
  27      private $_length;
  28  
  29      public function __construct($binaryData) {
  30          $this->_data = (string)$binaryData;
  31          $this->_length = \strlen($binaryData);
  32      }
  33  
  34  
  35      // -----------------------
  36      // PUBLIC STATIC
  37      // -----------------------
  38  
  39      /**
  40       * create a ByteBuffer from a base64 url encoded string
  41       * @param string $base64url
  42       * @return ByteBuffer
  43       */
  44      public static function fromBase64Url($base64url): ByteBuffer {
  45          $bin = self::_base64url_decode($base64url);
  46          if ($bin === false) {
  47              throw new WebAuthnException('ByteBuffer: Invalid base64 url string', WebAuthnException::BYTEBUFFER);
  48          }
  49          return new ByteBuffer($bin);
  50      }
  51  
  52      /**
  53       * create a ByteBuffer from a base64 url encoded string
  54       * @param string $hex
  55       * @return ByteBuffer
  56       */
  57      public static function fromHex($hex): ByteBuffer {
  58          $bin = \hex2bin($hex);
  59          if ($bin === false) {
  60              throw new WebAuthnException('ByteBuffer: Invalid hex string', WebAuthnException::BYTEBUFFER);
  61          }
  62          return new ByteBuffer($bin);
  63      }
  64  
  65      /**
  66       * create a random ByteBuffer
  67       * @param string $length
  68       * @return ByteBuffer
  69       */
  70      public static function randomBuffer($length): ByteBuffer {
  71          if (\function_exists('random_bytes')) { // >PHP 7.0
  72              return new ByteBuffer(\random_bytes($length));
  73  
  74          } else if (\function_exists('openssl_random_pseudo_bytes')) {
  75              return new ByteBuffer(\openssl_random_pseudo_bytes($length));
  76  
  77          } else {
  78              throw new WebAuthnException('ByteBuffer: cannot generate random bytes', WebAuthnException::BYTEBUFFER);
  79          }
  80      }
  81  
  82      // -----------------------
  83      // PUBLIC
  84      // -----------------------
  85  
  86      public function getBytes($offset, $length): string {
  87          if ($offset < 0 || $length < 0 || ($offset + $length > $this->_length)) {
  88              throw new WebAuthnException('ByteBuffer: Invalid offset or length', WebAuthnException::BYTEBUFFER);
  89          }
  90          return \substr($this->_data, $offset, $length);
  91      }
  92  
  93      public function getByteVal($offset): int {
  94          if ($offset < 0 || $offset >= $this->_length) {
  95              throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
  96          }
  97          return \ord(\substr($this->_data, $offset, 1));
  98      }
  99  
 100      public function getJson($jsonFlags=0) {
 101          $data = \json_decode($this->getBinaryString(), null, 512, $jsonFlags);
 102          if (\json_last_error() !== JSON_ERROR_NONE) {
 103              throw new WebAuthnException(\json_last_error_msg(), WebAuthnException::BYTEBUFFER);
 104          }
 105          return $data;
 106      }
 107  
 108      public function getLength(): int {
 109          return $this->_length;
 110      }
 111  
 112      public function getUint16Val($offset) {
 113          if ($offset < 0 || ($offset + 2) > $this->_length) {
 114              throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
 115          }
 116          return unpack('n', $this->_data, $offset)[1];
 117      }
 118  
 119      public function getUint32Val($offset) {
 120          if ($offset < 0 || ($offset + 4) > $this->_length) {
 121              throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
 122          }
 123          $val = unpack('N', $this->_data, $offset)[1];
 124  
 125          // Signed integer overflow causes signed negative numbers
 126          if ($val < 0) {
 127              throw new WebAuthnException('ByteBuffer: Value out of integer range.', WebAuthnException::BYTEBUFFER);
 128          }
 129          return $val;
 130      }
 131  
 132      public function getUint64Val($offset) {
 133          if (PHP_INT_SIZE < 8) {
 134              throw new WebAuthnException('ByteBuffer: 64-bit values not supported by this system', WebAuthnException::BYTEBUFFER);
 135          }
 136          if ($offset < 0 || ($offset + 8) > $this->_length) {
 137              throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
 138          }
 139          $val = unpack('J', $this->_data, $offset)[1];
 140  
 141          // Signed integer overflow causes signed negative numbers
 142          if ($val < 0) {
 143              throw new WebAuthnException('ByteBuffer: Value out of integer range.', WebAuthnException::BYTEBUFFER);
 144          }
 145  
 146          return $val;
 147      }
 148  
 149      public function getHalfFloatVal($offset) {
 150          //FROM spec pseudo decode_half(unsigned char *halfp)
 151          $half = $this->getUint16Val($offset);
 152  
 153          $exp = ($half >> 10) & 0x1f;
 154          $mant = $half & 0x3ff;
 155  
 156          if ($exp === 0) {
 157              $val = $mant * (2 ** -24);
 158          } elseif ($exp !== 31) {
 159              $val = ($mant + 1024) * (2 ** ($exp - 25));
 160          } else {
 161              $val = ($mant === 0) ? INF : NAN;
 162          }
 163  
 164          return ($half & 0x8000) ? -$val : $val;
 165      }
 166  
 167      public function getFloatVal($offset) {
 168          if ($offset < 0 || ($offset + 4) > $this->_length) {
 169              throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
 170          }
 171          return unpack('G', $this->_data, $offset)[1];
 172      }
 173  
 174      public function getDoubleVal($offset) {
 175          if ($offset < 0 || ($offset + 8) > $this->_length) {
 176              throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
 177          }
 178          return unpack('E', $this->_data, $offset)[1];
 179      }
 180  
 181      /**
 182       * @return string
 183       */
 184      public function getBinaryString(): string {
 185          return $this->_data;
 186      }
 187  
 188      /**
 189       * @param string|ByteBuffer $buffer
 190       * @return bool
 191       */
 192      public function equals($buffer): bool {
 193          if (is_object($buffer) && $buffer instanceof ByteBuffer) {
 194              return $buffer->getBinaryString() === $this->getBinaryString();
 195  
 196          } else if (is_string($buffer)) {
 197              return $buffer === $this->getBinaryString();
 198          }
 199          
 200          return false;
 201      }
 202  
 203      /**
 204       * @return string
 205       */
 206      public function getHex(): string {
 207          return \bin2hex($this->_data);
 208      }
 209  
 210      /**
 211       * @return bool
 212       */
 213      public function isEmpty(): bool {
 214          return $this->_length === 0;
 215      }
 216  
 217  
 218      /**
 219       * jsonSerialize interface
 220       * return binary data in RFC 1342-Like serialized string
 221       * @return string
 222       */
 223      public function jsonSerialize(): string {
 224          if (ByteBuffer::$useBase64UrlEncoding) {
 225              return self::_base64url_encode($this->_data);
 226  
 227          } else {
 228              return '=?BINARY?B?' . \base64_encode($this->_data) . '?=';
 229          }
 230      }
 231  
 232      /**
 233       * Serializable-Interface
 234       * @return string
 235       */
 236      public function serialize(): string {
 237          return \serialize($this->_data);
 238      }
 239  
 240      /**
 241       * Serializable-Interface
 242       * @param string $serialized
 243       */
 244      public function unserialize($serialized) {
 245          $this->_data = \unserialize($serialized);
 246          $this->_length = \strlen($this->_data);
 247      }
 248  
 249      /**
 250       * (PHP 8 deprecates Serializable-Interface)
 251       * @return array
 252       */
 253      public function __serialize(): array {
 254          return [
 255              'data' => \serialize($this->_data)
 256          ];
 257      }
 258  
 259      /**
 260       * object to string
 261       * @return string
 262       */
 263      public function __toString(): string {
 264          return $this->getHex();
 265      }
 266  
 267      /**
 268       * (PHP 8 deprecates Serializable-Interface)
 269       * @param array $data
 270       * @return void
 271       */
 272      public function __unserialize($data) {
 273          if ($data && isset($data['data'])) {
 274              $this->_data = \unserialize($data['data']);
 275              $this->_length = \strlen($this->_data);
 276          }
 277      }
 278  
 279      // -----------------------
 280      // PROTECTED STATIC
 281      // -----------------------
 282  
 283      /**
 284       * base64 url decoding
 285       * @param string $data
 286       * @return string
 287       */
 288      protected static function _base64url_decode($data): string {
 289          return \base64_decode(\strtr($data, '-_', '+/') . \str_repeat('=', 3 - (3 + \strlen($data)) % 4));
 290      }
 291  
 292      /**
 293       * base64 url encoding
 294       * @param string $data
 295       * @return string
 296       */
 297      protected static function _base64url_encode($data): string {
 298          return \rtrim(\strtr(\base64_encode($data), '+/', '-_'), '=');
 299      }
 300  }