blob: e6b5427d91abeb39ba19141e53bff24c7c024c06 [file] [log] [blame]
<?php
namespace lbuchs\WebAuthn\CBOR;
use lbuchs\WebAuthn\WebAuthnException;
use lbuchs\WebAuthn\Binary\ByteBuffer;
/**
* Modified version of https://github.com/madwizard-thomas/webauthn-server/blob/master/src/Format/CborDecoder.php
* Copyright © 2018 Thomas Bleeker - MIT licensed
* Modified by Lukas Buchs
* Thanks Thomas for your work!
*/
class CborDecoder {
const CBOR_MAJOR_UNSIGNED_INT = 0;
const CBOR_MAJOR_TEXT_STRING = 3;
const CBOR_MAJOR_FLOAT_SIMPLE = 7;
const CBOR_MAJOR_NEGATIVE_INT = 1;
const CBOR_MAJOR_ARRAY = 4;
const CBOR_MAJOR_TAG = 6;
const CBOR_MAJOR_MAP = 5;
const CBOR_MAJOR_BYTE_STRING = 2;
/**
* @param ByteBuffer|string $bufOrBin
* @return mixed
* @throws WebAuthnException
*/
public static function decode($bufOrBin) {
$buf = $bufOrBin instanceof ByteBuffer ? $bufOrBin : new ByteBuffer($bufOrBin);
$offset = 0;
$result = self::_parseItem($buf, $offset);
if ($offset !== $buf->getLength()) {
throw new WebAuthnException('Unused bytes after data item.', WebAuthnException::CBOR);
}
return $result;
}
/**
* @param ByteBuffer|string $bufOrBin
* @param int $startOffset
* @param int|null $endOffset
* @return mixed
*/
public static function decodeInPlace($bufOrBin, $startOffset, &$endOffset = null) {
$buf = $bufOrBin instanceof ByteBuffer ? $bufOrBin : new ByteBuffer($bufOrBin);
$offset = $startOffset;
$data = self::_parseItem($buf, $offset);
$endOffset = $offset;
return $data;
}
// ---------------------
// protected
// ---------------------
/**
* @param ByteBuffer $buf
* @param int $offset
* @return mixed
*/
protected static function _parseItem(ByteBuffer $buf, &$offset) {
$first = $buf->getByteVal($offset++);
$type = $first >> 5;
$val = $first & 0b11111;
if ($type === self::CBOR_MAJOR_FLOAT_SIMPLE) {
return self::_parseFloatSimple($val, $buf, $offset);
}
$val = self::_parseExtraLength($val, $buf, $offset);
return self::_parseItemData($type, $val, $buf, $offset);
}
protected static function _parseFloatSimple($val, ByteBuffer $buf, &$offset) {
switch ($val) {
case 24:
$val = $buf->getByteVal($offset);
$offset++;
return self::_parseSimple($val);
case 25:
$floatValue = $buf->getHalfFloatVal($offset);
$offset += 2;
return $floatValue;
case 26:
$floatValue = $buf->getFloatVal($offset);
$offset += 4;
return $floatValue;
case 27:
$floatValue = $buf->getDoubleVal($offset);
$offset += 8;
return $floatValue;
case 28:
case 29:
case 30:
throw new WebAuthnException('Reserved value used.', WebAuthnException::CBOR);
case 31:
throw new WebAuthnException('Indefinite length is not supported.', WebAuthnException::CBOR);
}
return self::_parseSimple($val);
}
/**
* @param int $val
* @return mixed
* @throws WebAuthnException
*/
protected static function _parseSimple($val) {
if ($val === 20) {
return false;
}
if ($val === 21) {
return true;
}
if ($val === 22) {
return null;
}
throw new WebAuthnException(sprintf('Unsupported simple value %d.', $val), WebAuthnException::CBOR);
}
protected static function _parseExtraLength($val, ByteBuffer $buf, &$offset) {
switch ($val) {
case 24:
$val = $buf->getByteVal($offset);
$offset++;
break;
case 25:
$val = $buf->getUint16Val($offset);
$offset += 2;
break;
case 26:
$val = $buf->getUint32Val($offset);
$offset += 4;
break;
case 27:
$val = $buf->getUint64Val($offset);
$offset += 8;
break;
case 28:
case 29:
case 30:
throw new WebAuthnException('Reserved value used.', WebAuthnException::CBOR);
case 31:
throw new WebAuthnException('Indefinite length is not supported.', WebAuthnException::CBOR);
}
return $val;
}
protected static function _parseItemData($type, $val, ByteBuffer $buf, &$offset) {
switch ($type) {
case self::CBOR_MAJOR_UNSIGNED_INT: // uint
return $val;
case self::CBOR_MAJOR_NEGATIVE_INT:
return -1 - $val;
case self::CBOR_MAJOR_BYTE_STRING:
$data = $buf->getBytes($offset, $val);
$offset += $val;
return new ByteBuffer($data); // bytes
case self::CBOR_MAJOR_TEXT_STRING:
$data = $buf->getBytes($offset, $val);
$offset += $val;
return $data; // UTF-8
case self::CBOR_MAJOR_ARRAY:
return self::_parseArray($buf, $offset, $val);
case self::CBOR_MAJOR_MAP:
return self::_parseMap($buf, $offset, $val);
case self::CBOR_MAJOR_TAG:
return self::_parseItem($buf, $offset); // 1 embedded data item
}
// This should never be reached
throw new WebAuthnException(sprintf('Unknown major type %d.', $type), WebAuthnException::CBOR);
}
protected static function _parseMap(ByteBuffer $buf, &$offset, $count) {
$map = array();
for ($i = 0; $i < $count; $i++) {
$mapKey = self::_parseItem($buf, $offset);
$mapVal = self::_parseItem($buf, $offset);
if (!\is_int($mapKey) && !\is_string($mapKey)) {
throw new WebAuthnException('Can only use strings or integers as map keys', WebAuthnException::CBOR);
}
$map[$mapKey] = $mapVal; // todo dup
}
return $map;
}
protected static function _parseArray(ByteBuffer $buf, &$offset, $count) {
$arr = array();
for ($i = 0; $i < $count; $i++) {
$arr[] = self::_parseItem($buf, $offset);
}
return $arr;
}
}