src/CborDecoder.php
<?php
namespace Laragear\WebAuthn;
use InvalidArgumentException;
use Laragear\WebAuthn\Exceptions\DataException;
use function is_int;
use function is_string;
use function sprintf;
/**
* MIT License
*
* Copyright (c) 2018 Thomas Bleeker
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is furnished
* to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* ---
* MIT License
*
* Copyright (c) 2021 Lukas Buchs
* Copyright (c) 2018 Thomas Bleeker (CBOR & ByteBuffer part)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* ---
*
* This file has been modernized to fit Laravel.
*
* @author Lukas Buchs
* @author Thomas Bleeker
* @internal
*/
class CborDecoder
{
public const CBOR_MAJOR_UNSIGNED_INT = 0;
public const CBOR_MAJOR_TEXT_STRING = 3;
public const CBOR_MAJOR_FLOAT_SIMPLE = 7;
public const CBOR_MAJOR_NEGATIVE_INT = 1;
public const CBOR_MAJOR_ARRAY = 4;
public const CBOR_MAJOR_TAG = 6;
public const CBOR_MAJOR_MAP = 5;
public const CBOR_MAJOR_BYTE_STRING = 2;
/**
* Decodes the binary data.
*
* @param \Laragear\WebAuthn\ByteBuffer|string $encoded
* @return \Laragear\WebAuthn\ByteBuffer|array|bool|float|int|string|null
* @throws \Laragear\WebAuthn\Exceptions\DataException
*/
public static function decode(ByteBuffer|string $encoded): ByteBuffer|array|bool|float|int|string|null
{
if (is_string($encoded)) {
$encoded = new ByteBuffer($encoded);
}
$offset = 0;
$result = static::parseItem($encoded, $offset);
if ($offset !== $encoded->getDataLength()) {
throw new DataException('CBOR: Unused bytes after data item.');
}
return $result;
}
/**
* Decodes a portion of the Byte Buffer.
*
* @param ByteBuffer|string $bufOrBin
* @param int $startOffset
* @param int|null $endOffset
* @return \Laragear\WebAuthn\ByteBuffer|array|bool|float|int|string|null
* @throws \Laragear\WebAuthn\Exceptions\DataException
*/
public static function decodePortion(ByteBuffer|string $bufOrBin, int $startOffset, ?int &$endOffset = null): ByteBuffer|array|bool|float|int|string|null
{
$buf = $bufOrBin instanceof ByteBuffer ? $bufOrBin : new ByteBuffer($bufOrBin);
$offset = $startOffset;
$data = static::parseItem($buf, $offset);
$endOffset = $offset;
return $data;
}
/**
* Parses a single item of the Byte Buffer.
*
* @param ByteBuffer $buf
* @param int $offset
* @return \Laragear\WebAuthn\ByteBuffer|array|bool|float|int|string|null
* @throws \Laragear\WebAuthn\Exceptions\DataException
*/
protected static function parseItem(ByteBuffer $buf, int &$offset): ByteBuffer|array|bool|float|int|string|null
{
$first = $buf->getByteVal($offset++);
$type = $first >> 5;
$val = $first & 0b11111;
if ($type === static::CBOR_MAJOR_FLOAT_SIMPLE) {
return static::parseFloatSimple($val, $buf, $offset);
}
$val = static::parseExtraLength($val, $buf, $offset);
try {
return static::parseItemData($type, $val, $buf, $offset);
} catch (InvalidArgumentException $e) {
throw new DataException($e->getMessage());
}
}
/**
* Parses a simple float value.
*
* @param int $val
* @param \Laragear\WebAuthn\ByteBuffer $buf
* @param int $offset
* @return bool|float|null
* @throws \Laragear\WebAuthn\Exceptions\DataException
*/
protected static function parseFloatSimple(int $val, ByteBuffer $buf, int &$offset): bool|float|null
{
switch ($val) {
case 24:
$val = $buf->getByteVal($offset);
$offset++;
return static::parseSimpleValue($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 DataException('Reserved value used.');
case 31:
throw new DataException('Indefinite length is not supported.');
default:
return static::parseSimpleValue($val);
}
}
/**
* Parses a simple value from CBOR.
*
* @param int $val
* @return bool|null
* @throws \Laragear\WebAuthn\Exceptions\DataException
*/
protected static function parseSimpleValue(int $val): ?bool
{
return match ($val) {
20 => false,
21 => true,
22 => null,
default => throw new DataException(sprintf('Unsupported simple value %d.', $val))
};
}
/**
* Parses the CBOR extra length.
*
* @param int $val
* @param \Laragear\WebAuthn\ByteBuffer $buf
* @param int $offset
* @return int
* @throws \Laragear\WebAuthn\Exceptions\DataException
*/
protected static function parseExtraLength(int $val, ByteBuffer $buf, int &$offset): int
{
switch ($val) {
case 24:
$val = $buf->getByteVal($offset);
$offset++;
return $val;
case 25:
$val = $buf->getUint16Val($offset);
$offset += 2;
return $val;
case 26:
$val = $buf->getUint32Val($offset);
$offset += 4;
return $val;
case 27:
$val = $buf->getUint64Val($offset);
$offset += 8;
return $val;
case 28:
case 29:
case 30:
throw new DataException('Reserved value used.');
case 31:
throw new DataException('Indefinite length is not supported.');
default:
return $val;
}
}
/**
* Parses the data inside a Byte Buffer.
*
* @param int $type
* @param int $val
* @param \Laragear\WebAuthn\ByteBuffer $buf
* @param $offset
* @return \Laragear\WebAuthn\ByteBuffer|array|bool|float|int|string|null
* @throws \Laragear\WebAuthn\Exceptions\DataException|\InvalidArgumentException
*/
protected static function parseItemData(
int $type,
int $val,
ByteBuffer $buf,
&$offset
): ByteBuffer|array|bool|float|int|string|null {
switch ($type) {
case static::CBOR_MAJOR_UNSIGNED_INT: // uint
return $val;
case static::CBOR_MAJOR_NEGATIVE_INT:
return -1 - $val;
case static::CBOR_MAJOR_BYTE_STRING:
$data = $buf->getBytes($offset, $val);
$offset += $val;
return new ByteBuffer($data); // bytes
case static::CBOR_MAJOR_TEXT_STRING:
$data = $buf->getBytes($offset, $val);
$offset += $val;
return $data; // UTF-8
case static::CBOR_MAJOR_ARRAY:
return static::parseArray($buf, $offset, $val);
case static::CBOR_MAJOR_MAP:
return static::parseMap($buf, $offset, $val);
case static::CBOR_MAJOR_TAG:
return static::parseItem($buf, $offset); // 1 embedded data item
}
throw new DataException(sprintf('Unknown major type %d.', $type));
}
/**
* Parses an array with string keys.
*
* @param \Laragear\WebAuthn\ByteBuffer $buffer
* @param int $offset
* @param int $count
* @return array<string, mixed>
* @throws \Laragear\WebAuthn\Exceptions\DataException
*/
protected static function parseMap(ByteBuffer $buffer, int &$offset, int $count): array
{
$map = [];
for ($i = 0; $i < $count; $i++) {
$mapKey = static::parseItem($buffer, $offset);
$mapVal = static::parseItem($buffer, $offset);
if (!is_int($mapKey) && !is_string($mapKey)) {
throw new DataException('Can only use strings or integers as map keys');
}
$map[$mapKey] = $mapVal;
}
return $map;
}
/**
* Parses an array from the byte buffer.
*
* @param \Laragear\WebAuthn\ByteBuffer $buf
* @param int $offset
* @param int $count
* @return array
* @throws \Laragear\WebAuthn\Exceptions\DataException
*/
protected static function parseArray(ByteBuffer $buf, int &$offset, int $count): array
{
$arr = [];
for ($i = 0; $i < $count; $i++) {
$arr[] = static::parseItem($buf, $offset);
}
return $arr;
}
}