ByteBuffer.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. <?php
  2. namespace WebAuthn\Binary;
  3. use WebAuthn\WebAuthnException;
  4. /**
  5. * Modified version of https://github.com/madwizard-thomas/webauthn-server/blob/master/src/Format/ByteBuffer.php
  6. * Copyright © 2018 Thomas Bleeker - MIT licensed
  7. * Modified by Lukas Buchs
  8. * Thanks Thomas for your work!
  9. */
  10. class ByteBuffer implements \JsonSerializable, \Serializable {
  11. /**
  12. * @var bool
  13. */
  14. public static $useBase64UrlEncoding = false;
  15. /**
  16. * @var string
  17. */
  18. private $_data;
  19. /**
  20. * @var int
  21. */
  22. private $_length;
  23. public function __construct($binaryData) {
  24. $this->_data = $binaryData;
  25. $this->_length = \strlen($binaryData);
  26. }
  27. // -----------------------
  28. // PUBLIC STATIC
  29. // -----------------------
  30. /**
  31. * create a ByteBuffer from a base64 url encoded string
  32. * @param string $base64url
  33. * @return \WebAuthn\Binary\ByteBuffer
  34. */
  35. public static function fromBase64Url($base64url) {
  36. $bin = self::_base64url_decode($base64url);
  37. if ($bin === false) {
  38. throw new WebAuthnException('ByteBuffer: Invalid base64 url string', WebAuthnException::BYTEBUFFER);
  39. }
  40. return new ByteBuffer($bin);
  41. }
  42. /**
  43. * create a ByteBuffer from a base64 url encoded string
  44. * @param string $hex
  45. * @return \WebAuthn\Binary\ByteBuffer
  46. */
  47. public static function fromHex($hex) {
  48. $bin = \hex2bin($hex);
  49. if ($bin === false) {
  50. throw new WebAuthnException('ByteBuffer: Invalid hex string', WebAuthnException::BYTEBUFFER);
  51. }
  52. return new ByteBuffer($bin);
  53. }
  54. /**
  55. * create a random ByteBuffer
  56. * @param string $length
  57. * @return \WebAuthn\Binary\ByteBuffer
  58. */
  59. public static function randomBuffer($length) {
  60. if (\function_exists('random_bytes')) { // >PHP 7.0
  61. return new ByteBuffer(\random_bytes($length));
  62. } else if (\function_exists('openssl_random_pseudo_bytes')) {
  63. return new ByteBuffer(\openssl_random_pseudo_bytes($length));
  64. } else {
  65. throw new WebAuthnException('ByteBuffer: cannot generate random bytes', WebAuthnException::BYTEBUFFER);
  66. }
  67. }
  68. // -----------------------
  69. // PUBLIC
  70. // -----------------------
  71. public function getBytes($offset, $length) {
  72. if ($offset < 0 || $length < 0 || ($offset + $length > $this->_length)) {
  73. throw new WebAuthnException('ByteBuffer: Invalid offset or length', WebAuthnException::BYTEBUFFER);
  74. }
  75. return \substr($this->_data, $offset, $length);
  76. }
  77. public function getByteVal($offset) {
  78. if ($offset < 0 || $offset >= $this->_length) {
  79. throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
  80. }
  81. return \ord(\substr($this->_data, $offset, 1));
  82. }
  83. public function getLength() {
  84. return $this->_length;
  85. }
  86. public function getUint16Val($offset) {
  87. if ($offset < 0 || ($offset + 2) > $this->_length) {
  88. throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
  89. }
  90. return unpack('n', $this->_data, $offset)[1];
  91. }
  92. public function getUint32Val($offset) {
  93. if ($offset < 0 || ($offset + 4) > $this->_length) {
  94. throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
  95. }
  96. $val = unpack('N', $this->_data, $offset)[1];
  97. // Signed integer overflow causes signed negative numbers
  98. if ($val < 0) {
  99. throw new WebAuthnException('ByteBuffer: Value out of integer range.', WebAuthnException::BYTEBUFFER);
  100. }
  101. return $val;
  102. }
  103. public function getUint64Val($offset) {
  104. if (PHP_INT_SIZE < 8) {
  105. throw new WebAuthnException('ByteBuffer: 64-bit values not supported by this system', WebAuthnException::BYTEBUFFER);
  106. }
  107. if ($offset < 0 || ($offset + 8) > $this->_length) {
  108. throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
  109. }
  110. $val = unpack('J', $this->_data, $offset)[1];
  111. // Signed integer overflow causes signed negative numbers
  112. if ($val < 0) {
  113. throw new WebAuthnException('ByteBuffer: Value out of integer range.', WebAuthnException::BYTEBUFFER);
  114. }
  115. return $val;
  116. }
  117. public function getHalfFloatVal($offset) {
  118. //FROM spec pseudo decode_half(unsigned char *halfp)
  119. $half = $this->getUint16Val($offset);
  120. $exp = ($half >> 10) & 0x1f;
  121. $mant = $half & 0x3ff;
  122. if ($exp === 0) {
  123. $val = $mant * (2 ** -24);
  124. } elseif ($exp !== 31) {
  125. $val = ($mant + 1024) * (2 ** ($exp - 25));
  126. } else {
  127. $val = ($mant === 0) ? INF : NAN;
  128. }
  129. return ($half & 0x8000) ? -$val : $val;
  130. }
  131. public function getFloatVal($offset) {
  132. if ($offset < 0 || ($offset + 4) > $this->_length) {
  133. throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
  134. }
  135. return unpack('G', $this->_data, $offset)[1];
  136. }
  137. public function getDoubleVal($offset) {
  138. if ($offset < 0 || ($offset + 8) > $this->_length) {
  139. throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
  140. }
  141. return unpack('E', $this->_data, $offset)[1];
  142. }
  143. /**
  144. * @return string
  145. */
  146. public function getBinaryString() {
  147. return $this->_data;
  148. }
  149. /**
  150. * @param string $buffer
  151. * @return bool
  152. */
  153. public function equals($buffer) {
  154. return is_string($this->_data) && $this->_data === $buffer->data;
  155. }
  156. /**
  157. * @return string
  158. */
  159. public function getHex() {
  160. return \bin2hex($this->_data);
  161. }
  162. /**
  163. * @return bool
  164. */
  165. public function isEmpty() {
  166. return $this->_length === 0;
  167. }
  168. /**
  169. * jsonSerialize interface
  170. * return binary data in RFC 1342-Like serialized string
  171. * @return \stdClass
  172. */
  173. public function jsonSerialize() {
  174. if (ByteBuffer::$useBase64UrlEncoding) {
  175. return self::_base64url_encode($this->_data);
  176. } else {
  177. return '=?BINARY?B?' . \base64_encode($this->_data) . '?=';
  178. }
  179. }
  180. /**
  181. * Serializable-Interface
  182. * @return string
  183. */
  184. public function serialize() {
  185. return \serialize($this->_data);
  186. }
  187. /**
  188. * Serializable-Interface
  189. * @param string $serialized
  190. */
  191. public function unserialize($serialized) {
  192. $this->_data = \unserialize($serialized);
  193. $this->_length = \strlen($this->_data);
  194. }
  195. // -----------------------
  196. // PROTECTED STATIC
  197. // -----------------------
  198. /**
  199. * base64 url decoding
  200. * @param string $data
  201. * @return string
  202. */
  203. protected static function _base64url_decode($data) {
  204. return \base64_decode(\strtr($data, '-_', '+/') . \str_repeat('=', 3 - (3 + \strlen($data)) % 4));
  205. }
  206. /**
  207. * base64 url encoding
  208. * @param string $data
  209. * @return string
  210. */
  211. protected static function _base64url_encode($data) {
  212. return \rtrim(\strtr(\base64_encode($data), '+/', '-_'), '=');
  213. }
  214. }