Issues (7)

src/Helper/CborDecoder.php (2 issues)

Severity
1
<?php
2
3
/**
4
 * Platine Webauth
5
 *
6
 * Platine Webauthn is the implementation of webauthn specifications
7
 *
8
 * This content is released under the MIT License (MIT)
9
 *
10
 * Copyright (c) 2020 Platine Webauth
11
 * Copyright (c) Jakob Bennemann <[email protected]>
12
 *
13
 * Permission is hereby granted, free of charge, to any person obtaining a copy
14
 * of this software and associated documentation files (the "Software"), to deal
15
 * in the Software without restriction, including without limitation the rights
16
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
 * copies of the Software, and to permit persons to whom the Software is
18
 * furnished to do so, subject to the following conditions:
19
 *
20
 * The above copyright notice and this permission notice shall be included in all
21
 * copies or substantial portions of the Software.
22
 *
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
 * SOFTWARE.
30
 */
31
32
declare(strict_types=1);
33
34
namespace Platine\Webauthn\Helper;
35
36
use Platine\Webauthn\Exception\WebauthnException;
37
38
/**
39
 * @class CborDecoder
40
 * @package Platine\Webauthn\Helper
41
 */
42
class CborDecoder
43
{
44
    public const CBOR_MAJOR_UNSIGNED_INT = 0;
45
    public const CBOR_MAJOR_NEGATIVE_INT = 1;
46
    public const CBOR_MAJOR_BYTE_STRING = 2;
47
    public const CBOR_MAJOR_TEXT_STRING = 3;
48
    public const CBOR_MAJOR_ARRAY = 4;
49
    public const CBOR_MAJOR_MAP = 5;
50
    public const CBOR_MAJOR_TAG = 6;
51
    public const CBOR_MAJOR_FLOAT_SIMPLE = 7;
52
53
    /**
54
     * Decode the given data
55
     * @param ByteBuffer|string $data
56
     * @return mixed
57
     */
58
    public static function decode(ByteBuffer|string $data): mixed
59
    {
60
        if (! $data instanceof ByteBuffer) {
61
            $data = new ByteBuffer($data);
62
        }
63
64
        $offset = 0;
65
        $result = self::parseItem($data, $offset);
66
        if ($offset !== $data->getLength()) {
67
            throw new WebauthnException(sprintf(
68
                'There still unsed bytes [%d] after parse data',
69
                abs($offset - $data->getLength())
70
            ));
71
        }
72
73
        return $result;
74
    }
75
76
    /**
77
     * Decode the data using custom start and end offset
78
     * @param ByteBuffer|string $data
79
     * @param int $startoffset
80
     * @param int|null $endOffset
81
     * @return mixed
82
     */
83
    public static function decodeInPlace(
84
        ByteBuffer|string $data,
85
        int $startoffset,
86
        ?int $endOffset = null
0 ignored issues
show
The parameter $endOffset is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

86
        /** @scrutinizer ignore-unused */ ?int $endOffset = null

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
87
    ): mixed {
88
        if (! $data instanceof ByteBuffer) {
89
            $data = new ByteBuffer($data);
90
        }
91
92
        $offset = $startoffset;
93
        $result = self::parseItem($data, $offset);
94
        $endOffset = $offset;
0 ignored issues
show
The assignment to $endOffset is dead and can be removed.
Loading history...
95
96
        return $result;
97
    }
98
99
    /**
100
     * Parse the item in the given offset
101
     * @param ByteBuffer $buffer
102
     * @param int $offset
103
     * @return mixed
104
     */
105
    protected static function parseItem(ByteBuffer $buffer, int &$offset): mixed
106
    {
107
        $first = $buffer->getByteValue($offset++);
108
        $type = $first >> 5;
109
        $value = $first & 0b11111;
110
        if ($type === self::CBOR_MAJOR_FLOAT_SIMPLE) {
111
            return self::parseSimpleFloat($value, $buffer, $offset);
112
        }
113
114
        $val = self::extractLength($value, $buffer, $offset);
115
116
        return self::parseItemData($type, $val, $buffer, $offset);
117
    }
118
119
    /**
120
     * Parse the simple float value
121
     * @param int $value
122
     * @param ByteBuffer $buffer
123
     * @param int $offset
124
     * @return mixed
125
     */
126
    protected static function parseSimpleFloat(int $value, ByteBuffer $buffer, int &$offset): mixed
127
    {
128
        switch ($value) {
129
            case 24:
130
                $value = $buffer->getByteValue($offset);
131
                $offset++;
132
                return self::parseSimpleValue($value);
133
134
            case 25:
135
                $floatValue = $buffer->getHalfFloatValue($offset);
136
                $offset += 2;
137
138
                return $floatValue;
139
140
            case 26:
141
                $floatValue = $buffer->getFloatValue($offset);
142
                $offset += 4;
143
144
                return $floatValue;
145
146
            case 27:
147
                $floatValue = $buffer->getDoubleValue($offset);
148
                $offset += 8;
149
150
                return $floatValue;
151
152
            case 28:
153
            case 29:
154
            case 30:
155
                throw new WebauthnException(sprintf('Reserved value [%d] used', $value));
156
157
            case 31:
158
                throw new WebauthnException(sprintf('Indefinite value [%d] length is not supported', $value));
159
        }
160
161
        return self::parseSimpleValue($value);
162
    }
163
164
    /**
165
     * Parse simple value
166
     * @param int $value
167
     * @return bool|null
168
     */
169
    protected static function parseSimpleValue(int $value): ?bool
170
    {
171
        if ($value === 20) {
172
            return false;
173
        }
174
175
        if ($value === 21) {
176
            return true;
177
        }
178
179
        if ($value === 23) {
180
            return null;
181
        }
182
183
        throw new WebauthnException(sprintf('Unsupported simple value [%d]', $value));
184
    }
185
186
    /**
187
     * Parse the item data
188
     * @param int $type
189
     * @param int $value
190
     * @param ByteBuffer $buffer
191
     * @param int $offset
192
     * @return mixed
193
     */
194
    protected static function parseItemData(int $type, int $value, ByteBuffer $buffer, int &$offset): mixed
195
    {
196
        switch ($type) {
197
            case self::CBOR_MAJOR_UNSIGNED_INT:
198
                return $value;
199
200
            case self::CBOR_MAJOR_NEGATIVE_INT:
201
                return -1 - $value;
202
203
            case self::CBOR_MAJOR_BYTE_STRING:
204
                $data = $buffer->getBytes($offset, $value);
205
                $offset += $value;
206
                return new ByteBuffer($data); // bytes
207
208
            case self::CBOR_MAJOR_TEXT_STRING:
209
                $data = $buffer->getBytes($offset, $value);
210
                $offset += $value;
211
                return $data; // UTF-8
212
213
            case self::CBOR_MAJOR_ARRAY:
214
                return self::parseArray($buffer, $offset, $value);
215
216
            case self::CBOR_MAJOR_MAP:
217
                return self::parseMap($buffer, $offset, $value);
218
219
            case self::CBOR_MAJOR_TAG:
220
                return self::parseItem($buffer, $offset); // 1 embedded data item
221
        }
222
223
        throw new WebauthnException(sprintf('Unsupported major type [%d]', $type));
224
    }
225
226
    /**
227
     * Parse an array of values
228
     * @param ByteBuffer $buffer
229
     * @param int $offset
230
     * @param int $count
231
     * @return array<mixed>
232
     */
233
    protected static function parseArray(ByteBuffer $buffer, int &$offset, int $count): array
234
    {
235
        $arr = [];
236
        for ($i = 0; $i < $count; $i++) {
237
            $arr[] = self::parseItem($buffer, $offset);
238
        }
239
240
        return $arr;
241
    }
242
243
    /**
244
     * Parse map of values
245
     * @param ByteBuffer $buffer
246
     * @param int $offset
247
     * @param int $count
248
     * @return array<string|int, mixed>
249
     */
250
    protected static function parseMap(ByteBuffer $buffer, int &$offset, int $count): array
251
    {
252
        $maps = [];
253
        for ($i = 0; $i < $count; $i++) {
254
            $key = self::parseItem($buffer, $offset);
255
            $value = self::parseItem($buffer, $offset);
256
            if (!is_int($key) && !is_string($key)) {
257
                throw new WebauthnException('Can only use integer or string for map key');
258
            }
259
260
            $maps[$key] = $value;
261
        }
262
263
        return $maps;
264
    }
265
266
    /**
267
     *
268
     * @param int $value
269
     * @param ByteBuffer $buffer
270
     * @param int $offset
271
     * @return int
272
     */
273
    protected static function extractLength(int $value, ByteBuffer $buffer, int &$offset): int
274
    {
275
        switch ($value) {
276
            case 24:
277
                $value = $buffer->getByteValue($offset);
278
                $offset++;
279
                break;
280
281
            case 25:
282
                $value = $buffer->getUint16Value($offset);
283
                $offset += 2;
284
                break;
285
286
            case 26:
287
                $value = $buffer->getUint32Value($offset);
288
                $offset += 4;
289
                break;
290
291
            case 27:
292
                $value = $buffer->getUint64Value($offset);
293
                $offset += 8;
294
                break;
295
296
            case 28:
297
            case 29:
298
            case 30:
299
                throw new WebauthnException(sprintf('Reserved value [%d] used', $value));
300
301
            case 31:
302
                throw new WebauthnException(sprintf('Indefinite value [%d] length is not supported', $value));
303
        }
304
305
        return $value;
306
    }
307
}
308