2
0

ByteBuffer.php 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. <?php
  2. namespace lbuchs\WebAuthn\Binary;
  3. use lbuchs\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 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 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 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 getJson($jsonFlags=0) {
  84. $data = \json_decode($this->getBinaryString(), null, 512, $jsonFlags);
  85. if (\json_last_error() !== JSON_ERROR_NONE) {
  86. throw new WebAuthnException(\json_last_error_msg(), WebAuthnException::BYTEBUFFER);
  87. }
  88. return $data;
  89. }
  90. public function getLength() {
  91. return $this->_length;
  92. }
  93. public function getUint16Val($offset) {
  94. if ($offset < 0 || ($offset + 2) > $this->_length) {
  95. throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
  96. }
  97. return unpack('n', $this->_data, $offset)[1];
  98. }
  99. public function getUint32Val($offset) {
  100. if ($offset < 0 || ($offset + 4) > $this->_length) {
  101. throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
  102. }
  103. $val = unpack('N', $this->_data, $offset)[1];
  104. // Signed integer overflow causes signed negative numbers
  105. if ($val < 0) {
  106. throw new WebAuthnException('ByteBuffer: Value out of integer range.', WebAuthnException::BYTEBUFFER);
  107. }
  108. return $val;
  109. }
  110. public function getUint64Val($offset) {
  111. if (PHP_INT_SIZE < 8) {
  112. throw new WebAuthnException('ByteBuffer: 64-bit values not supported by this system', WebAuthnException::BYTEBUFFER);
  113. }
  114. if ($offset < 0 || ($offset + 8) > $this->_length) {
  115. throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
  116. }
  117. $val = unpack('J', $this->_data, $offset)[1];
  118. // Signed integer overflow causes signed negative numbers
  119. if ($val < 0) {
  120. throw new WebAuthnException('ByteBuffer: Value out of integer range.', WebAuthnException::BYTEBUFFER);
  121. }
  122. return $val;
  123. }
  124. public function getHalfFloatVal($offset) {
  125. //FROM spec pseudo decode_half(unsigned char *halfp)
  126. $half = $this->getUint16Val($offset);
  127. $exp = ($half >> 10) & 0x1f;
  128. $mant = $half & 0x3ff;
  129. if ($exp === 0) {
  130. $val = $mant * (2 ** -24);
  131. } elseif ($exp !== 31) {
  132. $val = ($mant + 1024) * (2 ** ($exp - 25));
  133. } else {
  134. $val = ($mant === 0) ? INF : NAN;
  135. }
  136. return ($half & 0x8000) ? -$val : $val;
  137. }
  138. public function getFloatVal($offset) {
  139. if ($offset < 0 || ($offset + 4) > $this->_length) {
  140. throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
  141. }
  142. return unpack('G', $this->_data, $offset)[1];
  143. }
  144. public function getDoubleVal($offset) {
  145. if ($offset < 0 || ($offset + 8) > $this->_length) {
  146. throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
  147. }
  148. return unpack('E', $this->_data, $offset)[1];
  149. }
  150. /**
  151. * @return string
  152. */
  153. public function getBinaryString() {
  154. return $this->_data;
  155. }
  156. /**
  157. * @param string $buffer
  158. * @return bool
  159. */
  160. public function equals($buffer) {
  161. return is_string($this->_data) && $this->_data === $buffer->data;
  162. }
  163. /**
  164. * @return string
  165. */
  166. public function getHex() {
  167. return \bin2hex($this->_data);
  168. }
  169. /**
  170. * @return bool
  171. */
  172. public function isEmpty() {
  173. return $this->_length === 0;
  174. }
  175. /**
  176. * jsonSerialize interface
  177. * return binary data in RFC 1342-Like serialized string
  178. * @return string
  179. */
  180. public function jsonSerialize() {
  181. if (ByteBuffer::$useBase64UrlEncoding) {
  182. return self::_base64url_encode($this->_data);
  183. } else {
  184. return '=?BINARY?B?' . \base64_encode($this->_data) . '?=';
  185. }
  186. }
  187. /**
  188. * Serializable-Interface
  189. * @return string
  190. */
  191. public function serialize() {
  192. return \serialize($this->_data);
  193. }
  194. /**
  195. * Serializable-Interface
  196. * @param string $serialized
  197. */
  198. public function unserialize($serialized) {
  199. $this->_data = \unserialize($serialized);
  200. $this->_length = \strlen($this->_data);
  201. }
  202. /**
  203. * (PHP 8 deprecates Serializable-Interface)
  204. * @return array
  205. */
  206. public function __serialize() {
  207. return [
  208. 'data' => \serialize($this->_data)
  209. ];
  210. }
  211. /**
  212. * object to string
  213. * @return string
  214. */
  215. public function __toString() {
  216. return $this->getHex();
  217. }
  218. /**
  219. * (PHP 8 deprecates Serializable-Interface)
  220. * @param array $data
  221. * @return void
  222. */
  223. public function __unserialize($data) {
  224. if ($data && isset($data['data'])) {
  225. $this->_data = \unserialize($data['data']);
  226. $this->_length = \strlen($this->_data);
  227. }
  228. }
  229. // -----------------------
  230. // PROTECTED STATIC
  231. // -----------------------
  232. /**
  233. * base64 url decoding
  234. * @param string $data
  235. * @return string
  236. */
  237. protected static function _base64url_decode($data) {
  238. return \base64_decode(\strtr($data, '-_', '+/') . \str_repeat('=', 3 - (3 + \strlen($data)) % 4));
  239. }
  240. /**
  241. * base64 url encoding
  242. * @param string $data
  243. * @return string
  244. */
  245. protected static function _base64url_encode($data) {
  246. return \rtrim(\strtr(\base64_encode($data), '+/', '-_'), '=');
  247. }
  248. }