CborDecoder.php 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. <?php
  2. namespace lbuchs\WebAuthn\CBOR;
  3. use lbuchs\WebAuthn\WebAuthnException;
  4. use lbuchs\WebAuthn\Binary\ByteBuffer;
  5. /**
  6. * Modified version of https://github.com/madwizard-thomas/webauthn-server/blob/master/src/Format/CborDecoder.php
  7. * Copyright © 2018 Thomas Bleeker - MIT licensed
  8. * Modified by Lukas Buchs
  9. * Thanks Thomas for your work!
  10. */
  11. class CborDecoder {
  12. const CBOR_MAJOR_UNSIGNED_INT = 0;
  13. const CBOR_MAJOR_TEXT_STRING = 3;
  14. const CBOR_MAJOR_FLOAT_SIMPLE = 7;
  15. const CBOR_MAJOR_NEGATIVE_INT = 1;
  16. const CBOR_MAJOR_ARRAY = 4;
  17. const CBOR_MAJOR_TAG = 6;
  18. const CBOR_MAJOR_MAP = 5;
  19. const CBOR_MAJOR_BYTE_STRING = 2;
  20. /**
  21. * @param ByteBuffer|string $bufOrBin
  22. * @return mixed
  23. * @throws WebAuthnException
  24. */
  25. public static function decode($bufOrBin) {
  26. $buf = $bufOrBin instanceof ByteBuffer ? $bufOrBin : new ByteBuffer($bufOrBin);
  27. $offset = 0;
  28. $result = self::_parseItem($buf, $offset);
  29. if ($offset !== $buf->getLength()) {
  30. throw new WebAuthnException('Unused bytes after data item.', WebAuthnException::CBOR);
  31. }
  32. return $result;
  33. }
  34. /**
  35. * @param ByteBuffer|string $bufOrBin
  36. * @param int $startOffset
  37. * @param int|null $endOffset
  38. * @return mixed
  39. */
  40. public static function decodeInPlace($bufOrBin, $startOffset, &$endOffset = null) {
  41. $buf = $bufOrBin instanceof ByteBuffer ? $bufOrBin : new ByteBuffer($bufOrBin);
  42. $offset = $startOffset;
  43. $data = self::_parseItem($buf, $offset);
  44. $endOffset = $offset;
  45. return $data;
  46. }
  47. // ---------------------
  48. // protected
  49. // ---------------------
  50. /**
  51. * @param ByteBuffer $buf
  52. * @param int $offset
  53. * @return mixed
  54. */
  55. protected static function _parseItem(ByteBuffer $buf, &$offset) {
  56. $first = $buf->getByteVal($offset++);
  57. $type = $first >> 5;
  58. $val = $first & 0b11111;
  59. if ($type === self::CBOR_MAJOR_FLOAT_SIMPLE) {
  60. return self::_parseFloatSimple($val, $buf, $offset);
  61. }
  62. $val = self::_parseExtraLength($val, $buf, $offset);
  63. return self::_parseItemData($type, $val, $buf, $offset);
  64. }
  65. protected static function _parseFloatSimple($val, ByteBuffer $buf, &$offset) {
  66. switch ($val) {
  67. case 24:
  68. $val = $buf->getByteVal($offset);
  69. $offset++;
  70. return self::_parseSimple($val);
  71. case 25:
  72. $floatValue = $buf->getHalfFloatVal($offset);
  73. $offset += 2;
  74. return $floatValue;
  75. case 26:
  76. $floatValue = $buf->getFloatVal($offset);
  77. $offset += 4;
  78. return $floatValue;
  79. case 27:
  80. $floatValue = $buf->getDoubleVal($offset);
  81. $offset += 8;
  82. return $floatValue;
  83. case 28:
  84. case 29:
  85. case 30:
  86. throw new WebAuthnException('Reserved value used.', WebAuthnException::CBOR);
  87. case 31:
  88. throw new WebAuthnException('Indefinite length is not supported.', WebAuthnException::CBOR);
  89. }
  90. return self::_parseSimple($val);
  91. }
  92. /**
  93. * @param int $val
  94. * @return mixed
  95. * @throws WebAuthnException
  96. */
  97. protected static function _parseSimple($val) {
  98. if ($val === 20) {
  99. return false;
  100. }
  101. if ($val === 21) {
  102. return true;
  103. }
  104. if ($val === 22) {
  105. return null;
  106. }
  107. throw new WebAuthnException(sprintf('Unsupported simple value %d.', $val), WebAuthnException::CBOR);
  108. }
  109. protected static function _parseExtraLength($val, ByteBuffer $buf, &$offset) {
  110. switch ($val) {
  111. case 24:
  112. $val = $buf->getByteVal($offset);
  113. $offset++;
  114. break;
  115. case 25:
  116. $val = $buf->getUint16Val($offset);
  117. $offset += 2;
  118. break;
  119. case 26:
  120. $val = $buf->getUint32Val($offset);
  121. $offset += 4;
  122. break;
  123. case 27:
  124. $val = $buf->getUint64Val($offset);
  125. $offset += 8;
  126. break;
  127. case 28:
  128. case 29:
  129. case 30:
  130. throw new WebAuthnException('Reserved value used.', WebAuthnException::CBOR);
  131. case 31:
  132. throw new WebAuthnException('Indefinite length is not supported.', WebAuthnException::CBOR);
  133. }
  134. return $val;
  135. }
  136. protected static function _parseItemData($type, $val, ByteBuffer $buf, &$offset) {
  137. switch ($type) {
  138. case self::CBOR_MAJOR_UNSIGNED_INT: // uint
  139. return $val;
  140. case self::CBOR_MAJOR_NEGATIVE_INT:
  141. return -1 - $val;
  142. case self::CBOR_MAJOR_BYTE_STRING:
  143. $data = $buf->getBytes($offset, $val);
  144. $offset += $val;
  145. return new ByteBuffer($data); // bytes
  146. case self::CBOR_MAJOR_TEXT_STRING:
  147. $data = $buf->getBytes($offset, $val);
  148. $offset += $val;
  149. return $data; // UTF-8
  150. case self::CBOR_MAJOR_ARRAY:
  151. return self::_parseArray($buf, $offset, $val);
  152. case self::CBOR_MAJOR_MAP:
  153. return self::_parseMap($buf, $offset, $val);
  154. case self::CBOR_MAJOR_TAG:
  155. return self::_parseItem($buf, $offset); // 1 embedded data item
  156. }
  157. // This should never be reached
  158. throw new WebAuthnException(sprintf('Unknown major type %d.', $type), WebAuthnException::CBOR);
  159. }
  160. protected static function _parseMap(ByteBuffer $buf, &$offset, $count) {
  161. $map = array();
  162. for ($i = 0; $i < $count; $i++) {
  163. $mapKey = self::_parseItem($buf, $offset);
  164. $mapVal = self::_parseItem($buf, $offset);
  165. if (!\is_int($mapKey) && !\is_string($mapKey)) {
  166. throw new WebAuthnException('Can only use strings or integers as map keys', WebAuthnException::CBOR);
  167. }
  168. $map[$mapKey] = $mapVal; // todo dup
  169. }
  170. return $map;
  171. }
  172. protected static function _parseArray(ByteBuffer $buf, &$offset, $count) {
  173. $arr = array();
  174. for ($i = 0; $i < $count; $i++) {
  175. $arr[] = self::_parseItem($buf, $offset);
  176. }
  177. return $arr;
  178. }
  179. }