GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Element::fromDER()   A
last analyzed

Complexity

Conditions 4
Paths 5

Size

Total Lines 23
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 12
c 1
b 0
f 0
nc 5
nop 2
dl 0
loc 23
ccs 13
cts 13
cp 1
crap 4
rs 9.8666
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace Sop\ASN1;
6
7
use Sop\ASN1\Component\Identifier;
8
use Sop\ASN1\Component\Length;
9
use Sop\ASN1\Exception\DecodeException;
10
use Sop\ASN1\Feature\ElementBase;
11
use Sop\ASN1\Type\Constructed;
12
use Sop\ASN1\Type\Constructed\ConstructedString;
13
use Sop\ASN1\Type\Primitive;
14
use Sop\ASN1\Type\StringType;
15
use Sop\ASN1\Type\Tagged\ApplicationType;
16
use Sop\ASN1\Type\Tagged\ContextSpecificType;
17
use Sop\ASN1\Type\Tagged\PrivateType;
18
use Sop\ASN1\Type\TaggedType;
19
use Sop\ASN1\Type\TimeType;
20
use Sop\ASN1\Type\UnspecifiedType;
21
22
/**
23
 * Base class for all ASN.1 type elements.
24
 */
25
abstract class Element implements ElementBase
26
{
27
    // Universal type tags
28
    const TYPE_EOC = 0x00;
29
    const TYPE_BOOLEAN = 0x01;
30
    const TYPE_INTEGER = 0x02;
31
    const TYPE_BIT_STRING = 0x03;
32
    const TYPE_OCTET_STRING = 0x04;
33
    const TYPE_NULL = 0x05;
34
    const TYPE_OBJECT_IDENTIFIER = 0x06;
35
    const TYPE_OBJECT_DESCRIPTOR = 0x07;
36
    const TYPE_EXTERNAL = 0x08;
37
    const TYPE_REAL = 0x09;
38
    const TYPE_ENUMERATED = 0x0a;
39
    const TYPE_EMBEDDED_PDV = 0x0b;
40
    const TYPE_UTF8_STRING = 0x0c;
41
    const TYPE_RELATIVE_OID = 0x0d;
42
    const TYPE_SEQUENCE = 0x10;
43
    const TYPE_SET = 0x11;
44
    const TYPE_NUMERIC_STRING = 0x12;
45
    const TYPE_PRINTABLE_STRING = 0x13;
46
    const TYPE_T61_STRING = 0x14;
47
    const TYPE_VIDEOTEX_STRING = 0x15;
48
    const TYPE_IA5_STRING = 0x16;
49
    const TYPE_UTC_TIME = 0x17;
50
    const TYPE_GENERALIZED_TIME = 0x18;
51
    const TYPE_GRAPHIC_STRING = 0x19;
52
    const TYPE_VISIBLE_STRING = 0x1a;
53
    const TYPE_GENERAL_STRING = 0x1b;
54
    const TYPE_UNIVERSAL_STRING = 0x1c;
55
    const TYPE_CHARACTER_STRING = 0x1d;
56
    const TYPE_BMP_STRING = 0x1e;
57
58
    /**
59
     * Mapping from universal type tag to implementation class name.
60
     *
61
     * @internal
62
     *
63
     * @var array
64
     */
65
    const MAP_TAG_TO_CLASS = [
66
        self::TYPE_EOC => Primitive\EOC::class,
67
        self::TYPE_BOOLEAN => Primitive\Boolean::class,
68
        self::TYPE_INTEGER => Primitive\Integer::class,
69
        self::TYPE_BIT_STRING => Primitive\BitString::class,
70
        self::TYPE_OCTET_STRING => Primitive\OctetString::class,
71
        self::TYPE_NULL => Primitive\NullType::class,
72
        self::TYPE_OBJECT_IDENTIFIER => Primitive\ObjectIdentifier::class,
73
        self::TYPE_OBJECT_DESCRIPTOR => Primitive\ObjectDescriptor::class,
74
        self::TYPE_REAL => Primitive\Real::class,
75
        self::TYPE_ENUMERATED => Primitive\Enumerated::class,
76
        self::TYPE_UTF8_STRING => Primitive\UTF8String::class,
77
        self::TYPE_RELATIVE_OID => Primitive\RelativeOID::class,
78
        self::TYPE_SEQUENCE => Constructed\Sequence::class,
79
        self::TYPE_SET => Constructed\Set::class,
80
        self::TYPE_NUMERIC_STRING => Primitive\NumericString::class,
81
        self::TYPE_PRINTABLE_STRING => Primitive\PrintableString::class,
82
        self::TYPE_T61_STRING => Primitive\T61String::class,
83
        self::TYPE_VIDEOTEX_STRING => Primitive\VideotexString::class,
84
        self::TYPE_IA5_STRING => Primitive\IA5String::class,
85
        self::TYPE_UTC_TIME => Primitive\UTCTime::class,
86
        self::TYPE_GENERALIZED_TIME => Primitive\GeneralizedTime::class,
87
        self::TYPE_GRAPHIC_STRING => Primitive\GraphicString::class,
88
        self::TYPE_VISIBLE_STRING => Primitive\VisibleString::class,
89
        self::TYPE_GENERAL_STRING => Primitive\GeneralString::class,
90
        self::TYPE_UNIVERSAL_STRING => Primitive\UniversalString::class,
91
        self::TYPE_CHARACTER_STRING => Primitive\CharacterString::class,
92
        self::TYPE_BMP_STRING => Primitive\BMPString::class,
93
    ];
94
95
    /**
96
     * Pseudotype for all string types.
97
     *
98
     * May be used as an expectation parameter.
99
     *
100
     * @var int
101
     */
102
    const TYPE_STRING = -1;
103
104
    /**
105
     * Pseudotype for all time types.
106
     *
107
     * May be used as an expectation parameter.
108
     *
109
     * @var int
110
     */
111
    const TYPE_TIME = -2;
112
113
    /**
114
     * Pseudotype for constructed strings.
115
     *
116
     * May be used as an expectation parameter.
117
     *
118
     * @var int
119
     */
120
    const TYPE_CONSTRUCTED_STRING = -3;
121
122
    /**
123
     * Mapping from universal type tag to human readable name.
124
     *
125
     * @internal
126
     *
127
     * @var array
128
     */
129
    const MAP_TYPE_TO_NAME = [
130
        self::TYPE_EOC => 'EOC',
131
        self::TYPE_BOOLEAN => 'BOOLEAN',
132
        self::TYPE_INTEGER => 'INTEGER',
133
        self::TYPE_BIT_STRING => 'BIT STRING',
134
        self::TYPE_OCTET_STRING => 'OCTET STRING',
135
        self::TYPE_NULL => 'NULL',
136
        self::TYPE_OBJECT_IDENTIFIER => 'OBJECT IDENTIFIER',
137
        self::TYPE_OBJECT_DESCRIPTOR => 'ObjectDescriptor',
138
        self::TYPE_EXTERNAL => 'EXTERNAL',
139
        self::TYPE_REAL => 'REAL',
140
        self::TYPE_ENUMERATED => 'ENUMERATED',
141
        self::TYPE_EMBEDDED_PDV => 'EMBEDDED PDV',
142
        self::TYPE_UTF8_STRING => 'UTF8String',
143
        self::TYPE_RELATIVE_OID => 'RELATIVE-OID',
144
        self::TYPE_SEQUENCE => 'SEQUENCE',
145
        self::TYPE_SET => 'SET',
146
        self::TYPE_NUMERIC_STRING => 'NumericString',
147
        self::TYPE_PRINTABLE_STRING => 'PrintableString',
148
        self::TYPE_T61_STRING => 'T61String',
149
        self::TYPE_VIDEOTEX_STRING => 'VideotexString',
150
        self::TYPE_IA5_STRING => 'IA5String',
151
        self::TYPE_UTC_TIME => 'UTCTime',
152
        self::TYPE_GENERALIZED_TIME => 'GeneralizedTime',
153
        self::TYPE_GRAPHIC_STRING => 'GraphicString',
154
        self::TYPE_VISIBLE_STRING => 'VisibleString',
155
        self::TYPE_GENERAL_STRING => 'GeneralString',
156
        self::TYPE_UNIVERSAL_STRING => 'UniversalString',
157
        self::TYPE_CHARACTER_STRING => 'CHARACTER STRING',
158
        self::TYPE_BMP_STRING => 'BMPString',
159
        self::TYPE_STRING => 'Any String',
160
        self::TYPE_TIME => 'Any Time',
161
        self::TYPE_CONSTRUCTED_STRING => 'Constructed String',
162
    ];
163
164
    /**
165
     * Element's type tag.
166
     *
167
     * @var int
168
     */
169
    protected $_typeTag;
170
171
    /**
172
     * Whether type shall be encoded with indefinite length.
173
     *
174
     * @var bool
175
     */
176
    protected $_indefiniteLength = false;
177
178
    /**
179
     * {@inheritdoc}
180
     */
181
    abstract public function typeClass(): int;
182
183
    /**
184
     * {@inheritdoc}
185
     */
186
    abstract public function isConstructed(): bool;
187
188
    /**
189
     * Decode element from DER data.
190
     *
191
     * @param string   $data   DER encoded data
192
     * @param null|int $offset Reference to the variable that contains offset
193
     *                         into the data where to start parsing.
194
     *                         Variable is updated to the offset next to the
195
     *                         parsed element. If null, start from offset 0.
196
     *
197
     * @throws DecodeException           If decoding fails
198
     * @throws \UnexpectedValueException If called in the context of an expected
199
     *                                   type, but decoding yields another type
200
     */
201 273
    public static function fromDER(string $data, ?int &$offset = null): ElementBase
202
    {
203 273
        $idx = $offset ?? 0;
204
        // decode identifier
205 273
        $identifier = Identifier::fromDER($data, $idx);
206
        // determine class that implements type specific decoding
207 273
        $cls = self::_determineImplClass($identifier);
208
        // decode remaining element
209 272
        $element = $cls::_decodeFromDER($identifier, $data, $idx);
210
        // if called in the context of a concrete class, check
211
        // that decoded type matches the type of a calling class
212 234
        $called_class = get_called_class();
213 234
        if (self::class !== $called_class) {
214 205
            if (!$element instanceof $called_class) {
215 2
                throw new \UnexpectedValueException(
216 2
                    sprintf('%s expected, got %s.', $called_class, get_class($element)));
217
            }
218
        }
219
        // update offset for the caller
220 232
        if (isset($offset)) {
221 41
            $offset = $idx;
222
        }
223 232
        return $element;
224
    }
225
226
    /**
227
     * {@inheritdoc}
228
     */
229 183
    public function toDER(): string
230
    {
231 183
        $identifier = new Identifier($this->typeClass(),
232 183
            $this->isConstructed() ? Identifier::CONSTRUCTED : Identifier::PRIMITIVE,
233 183
            $this->_typeTag);
234 183
        $content = $this->_encodedContentDER();
235 181
        if ($this->_indefiniteLength) {
236 21
            $length = new Length(0, true);
237 21
            $eoc = new Primitive\EOC();
238 21
            return $identifier->toDER() . $length->toDER() . $content . $eoc->toDER();
239
        }
240 181
        $length = new Length(strlen($content));
241 181
        return $identifier->toDER() . $length->toDER() . $content;
242
    }
243
244
    /**
245
     * {@inheritdoc}
246
     */
247 128
    public function tag(): int
248
    {
249 128
        return $this->_typeTag;
250
    }
251
252
    /**
253
     * {@inheritdoc}
254
     */
255 42
    public function isType(int $tag): bool
256
    {
257
        // if element is context specific
258 42
        if (Identifier::CLASS_CONTEXT_SPECIFIC === $this->typeClass()) {
259 2
            return false;
260
        }
261
        // negative tags identify an abstract pseudotype
262 40
        if ($tag < 0) {
263 5
            return $this->_isPseudoType($tag);
264
        }
265 35
        return $this->_isConcreteType($tag);
266
    }
267
268
    /**
269
     * {@inheritdoc}
270
     */
271 10
    public function expectType(int $tag): ElementBase
272
    {
273 10
        if (!$this->isType($tag)) {
274 3
            throw new \UnexpectedValueException(
275 3
                sprintf('%s expected, got %s.', self::tagToName($tag),
276 3
                    $this->_typeDescriptorString()));
277
        }
278 7
        return $this;
279
    }
280
281
    /**
282
     * {@inheritdoc}
283
     */
284 20
    public function isTagged(): bool
285
    {
286 20
        return $this instanceof TaggedType;
287
    }
288
289
    /**
290
     * {@inheritdoc}
291
     */
292 13
    public function expectTagged(?int $tag = null): TaggedType
293
    {
294 13
        if (!$this->isTagged()) {
295 1
            throw new \UnexpectedValueException(
296 1
                sprintf('Context specific element expected, got %s.',
297 1
                    Identifier::classToName($this->typeClass())));
298
        }
299 12
        if (isset($tag) && $this->tag() !== $tag) {
300 5
            throw new \UnexpectedValueException(
301 5
                sprintf('Tag %d expected, got %d.', $tag, $this->tag()));
302
        }
303 7
        return $this;
304
    }
305
306
    /**
307
     * Whether element has indefinite length.
308
     */
309 3
    public function hasIndefiniteLength(): bool
310
    {
311 3
        return $this->_indefiniteLength;
312
    }
313
314
    /**
315
     * Get self with indefinite length encoding set.
316
     *
317
     * @param bool $indefinite True for indefinite length, false for definite length
318
     */
319 21
    public function withIndefiniteLength(bool $indefinite = true): self
320
    {
321 21
        $obj = clone $this;
322 21
        $obj->_indefiniteLength = $indefinite;
323 21
        return $obj;
324
    }
325
326
    /**
327
     * {@inheritdoc}
328
     */
329 72
    final public function asElement(): Element
330
    {
331 72
        return $this;
332
    }
333
334
    /**
335
     * Get element decorated with `UnspecifiedType` object.
336
     */
337 51
    public function asUnspecified(): UnspecifiedType
338
    {
339 51
        return new UnspecifiedType($this);
340
    }
341
342
    /**
343
     * Get human readable name for an universal tag.
344
     */
345 46
    public static function tagToName(int $tag): string
346
    {
347 46
        if (!array_key_exists($tag, self::MAP_TYPE_TO_NAME)) {
348 1
            return "TAG {$tag}";
349
        }
350 45
        return self::MAP_TYPE_TO_NAME[$tag];
351
    }
352
353
    /**
354
     * Get the content encoded in DER.
355
     *
356
     * Returns the DER encoded content without identifier and length header octets.
357
     */
358
    abstract protected function _encodedContentDER(): string;
359
360
    /**
361
     * Decode type-specific element from DER.
362
     *
363
     * @param Identifier $identifier Pre-parsed identifier
364
     * @param string     $data       DER data
365
     * @param int        $offset     Offset in data to the next byte after identifier
366
     *
367
     * @throws DecodeException If decoding fails
368
     */
369 1
    protected static function _decodeFromDER(Identifier $identifier,
370
        string $data, int &$offset): ElementBase
371
    {
372 1
        throw new \BadMethodCallException(
373 1
            __METHOD__ . ' must be implemented in derived class.');
374
    }
375
376
    /**
377
     * Determine the class that implements the type.
378
     *
379
     * @return string Class name
380
     */
381 276
    protected static function _determineImplClass(Identifier $identifier): string
382
    {
383 276
        switch ($identifier->typeClass()) {
384
            case Identifier::CLASS_UNIVERSAL:
385 258
                $cls = self::_determineUniversalImplClass($identifier->intTag());
386
                // constructed strings may be present in BER
387 257
                if ($identifier->isConstructed()
388 257
                    && is_subclass_of($cls, StringType::class)) {
389 22
                    $cls = ConstructedString::class;
390
                }
391 257
                return $cls;
392
            case Identifier::CLASS_CONTEXT_SPECIFIC:
393 25
                return ContextSpecificType::class;
394
            case Identifier::CLASS_APPLICATION:
395 3
                return ApplicationType::class;
396
            case Identifier::CLASS_PRIVATE:
397 3
                return PrivateType::class;
398
        }
399 1
        throw new \UnexpectedValueException(sprintf('%s %d not implemented.',
400 1
            Identifier::classToName($identifier->typeClass()), $identifier->tag()));
401
    }
402
403
    /**
404
     * Determine the class that implements an universal type of the given tag.
405
     *
406
     * @throws \UnexpectedValueException
407
     *
408
     * @return string Class name
409
     */
410 267
    protected static function _determineUniversalImplClass(int $tag): string
411
    {
412 267
        if (!array_key_exists($tag, self::MAP_TAG_TO_CLASS)) {
413 1
            throw new \UnexpectedValueException(
414 1
                "Universal tag {$tag} not implemented.");
415
        }
416 266
        return self::MAP_TAG_TO_CLASS[$tag];
417
    }
418
419
    /**
420
     * Get textual description of the type for debugging purposes.
421
     */
422 3
    protected function _typeDescriptorString(): string
423
    {
424 3
        if (Identifier::CLASS_UNIVERSAL === $this->typeClass()) {
425 1
            return self::tagToName($this->_typeTag);
426
        }
427 2
        return sprintf('%s TAG %d', Identifier::classToName($this->typeClass()),
428 2
            $this->_typeTag);
429
    }
430
431
    /**
432
     * Check whether the element is a concrete type of a given tag.
433
     */
434 35
    private function _isConcreteType(int $tag): bool
435
    {
436
        // if tag doesn't match
437 35
        if ($this->tag() !== $tag) {
438 25
            return false;
439
        }
440
        // if type is universal check that instance is of a correct class
441 33
        if (Identifier::CLASS_UNIVERSAL === $this->typeClass()) {
442 33
            $cls = self::_determineUniversalImplClass($tag);
443 33
            if (!$this instanceof $cls) {
444 1
                return false;
445
            }
446
        }
447 32
        return true;
448
    }
449
450
    /**
451
     * Check whether the element is a pseudotype.
452
     */
453 5
    private function _isPseudoType(int $tag): bool
454
    {
455
        switch ($tag) {
456 5
            case self::TYPE_STRING:
457 1
                return $this instanceof StringType;
458 4
            case self::TYPE_TIME:
459 2
                return $this instanceof TimeType;
460 2
            case self::TYPE_CONSTRUCTED_STRING:
461 1
                return $this instanceof ConstructedString;
462
        }
463 1
        return false;
464
    }
465
}
466