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.
Test Failed
Branch php70 (fbdf5b)
by Joni
02:55
created

Element::_decodeFromDER()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 3
1
<?php
2
declare(strict_types = 1);
3
4
namespace ASN1;
5
6
use ASN1\Component\Identifier;
7
use ASN1\Component\Length;
8
use ASN1\Exception\DecodeException;
9
use ASN1\Feature\ElementBase;
10
use ASN1\Type\Constructed;
11
use ASN1\Type\Constructed\ConstructedString;
12
use ASN1\Type\Primitive;
13
use ASN1\Type\PrimitiveString;
14
use ASN1\Type\StringType;
15
use ASN1\Type\TaggedType;
16
use ASN1\Type\TimeType;
17
use ASN1\Type\UnspecifiedType;
18
use ASN1\Type\Tagged\ApplicationType;
19
use ASN1\Type\Tagged\ContextSpecificType;
20
use ASN1\Type\Tagged\PrivateType;
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 = [ /* @formatter:off */
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
        /* @formatter:on */
94
    ];
95
    
96
    /**
97
     * Pseudotype for all string types.
98
     *
99
     * May be used as an expectation parameter.
100
     *
101
     * @var int
102
     */
103
    const TYPE_STRING = -1;
104
    
105
    /**
106
     * Pseudotype for all time types.
107
     *
108
     * May be used as an expectation parameter.
109
     *
110
     * @var int
111
     */
112
    const TYPE_TIME = -2;
113
    
114
    /**
115
     * Mapping from universal type tag to human readable name.
116
     *
117
     * @internal
118
     *
119
     * @var array
120
     */
121
    const MAP_TYPE_TO_NAME = [ /* @formatter:off */
122
        self::TYPE_EOC => "EOC",
123
        self::TYPE_BOOLEAN => "BOOLEAN",
124
        self::TYPE_INTEGER => "INTEGER",
125
        self::TYPE_BIT_STRING => "BIT STRING",
126
        self::TYPE_OCTET_STRING => "OCTET STRING",
127
        self::TYPE_NULL => "NULL",
128
        self::TYPE_OBJECT_IDENTIFIER => "OBJECT IDENTIFIER",
129
        self::TYPE_OBJECT_DESCRIPTOR => "ObjectDescriptor",
130
        self::TYPE_EXTERNAL => "EXTERNAL",
131
        self::TYPE_REAL => "REAL",
132
        self::TYPE_ENUMERATED => "ENUMERATED",
133
        self::TYPE_EMBEDDED_PDV => "EMBEDDED PDV",
134
        self::TYPE_UTF8_STRING => "UTF8String",
135
        self::TYPE_RELATIVE_OID => "RELATIVE-OID",
136
        self::TYPE_SEQUENCE => "SEQUENCE",
137
        self::TYPE_SET => "SET",
138
        self::TYPE_NUMERIC_STRING => "NumericString",
139
        self::TYPE_PRINTABLE_STRING => "PrintableString",
140
        self::TYPE_T61_STRING => "T61String",
141
        self::TYPE_VIDEOTEX_STRING => "VideotexString",
142
        self::TYPE_IA5_STRING => "IA5String",
143
        self::TYPE_UTC_TIME => "UTCTime",
144
        self::TYPE_GENERALIZED_TIME => "GeneralizedTime",
145
        self::TYPE_GRAPHIC_STRING => "GraphicString",
146
        self::TYPE_VISIBLE_STRING => "VisibleString",
147
        self::TYPE_GENERAL_STRING => "GeneralString",
148
        self::TYPE_UNIVERSAL_STRING => "UniversalString",
149
        self::TYPE_CHARACTER_STRING => "CHARACTER STRING",
150
        self::TYPE_BMP_STRING => "BMPString",
151
        self::TYPE_STRING => "Any String",
152
        self::TYPE_TIME => "Any Time"
153
        /* @formatter:on */
154
    ];
155
    
156
    /**
157
     * Element's type tag.
158
     *
159
     * @var int
160
     */
161
    protected $_typeTag;
162
    
163
    /**
164
     * Whether type shall be encoded with indefinite length.
165
     *
166
     * @var bool
167
     */
168
    protected $_indefiniteLength = false;
169
    
170
    /**
171
     *
172
     * @see \ASN1\Feature\ElementBase::typeClass()
173
     * @return int
174
     */
175
    abstract public function typeClass(): int;
176
    
177
    /**
178
     *
179
     * @see \ASN1\Feature\ElementBase::isConstructed()
180
     * @return bool
181
     */
182
    abstract public function isConstructed(): bool;
183
    
184
    /**
185
     * Get the content encoded in DER.
186
     *
187
     * Returns the DER encoded content without identifier and length header
188
     * octets.
189
     *
190
     * @return string
191
     */
192
    abstract protected function _encodedContentDER(): string;
193
    
194
    /**
195
     * Decode type-specific element from DER.
196
     *
197
     * @param Identifier $identifier Pre-parsed identifier
198
     * @param string $data DER data
199
     * @param int $offset Offset in data to the next byte after identifier
200
     * @throws DecodeException If decoding fails
201
     * @return ElementBase
202
     */
203
    protected static function _decodeFromDER(Identifier $identifier,
204
        string $data, int &$offset): ElementBase
205
    {
206
        throw new \BadMethodCallException(
207
            __METHOD__ . " must be implemented in derived class.");
208
    }
209
    
210
    /**
211
     * Decode element from DER data.
212
     *
213
     * @param string $data DER encoded data
214
     * @param int|null $offset Reference to the variable that contains offset
215
     *        into the data where to start parsing. Variable is updated to
216
     *        the offset next to the parsed element. If null, start from offset
217
     *        0.
218
     * @throws DecodeException If decoding fails
219
     * @throws \UnexpectedValueException If called in the context of an expected
220
     *         type, but decoding yields another type
221
     * @return ElementBase
222
     */
223
    public static function fromDER(string $data, int &$offset = null): ElementBase
224
    {
225
        $idx = $offset ?? 0;
226
        // decode identifier
227
        $identifier = Identifier::fromDER($data, $idx);
228
        // determine class that implements type specific decoding
229
        $cls = self::_determineImplClass($identifier);
230
        try {
231
            // decode remaining element
232
            $element = $cls::_decodeFromDER($identifier, $data, $idx);
233
        } catch (\LogicException $e) {
234
            // rethrow as a RuntimeException for unified exception handling
235
            throw new DecodeException(
236
                sprintf("Error while decoding %s.",
237
                    self::tagToName($identifier->intTag())), 0, $e);
238
        }
239
        // if called in the context of a concrete class, check
240
        // that decoded type matches the type of a calling class
241
        $called_class = get_called_class();
242
        if (self::class != $called_class) {
243
            if (!$element instanceof $called_class) {
244
                throw new \UnexpectedValueException(
245
                    sprintf("%s expected, got %s.", $called_class,
246
                        get_class($element)));
247
            }
248
        }
249
        // update offset for the caller
250
        if (isset($offset)) {
251
            $offset = $idx;
252
        }
253
        return $element;
254
    }
255
    
256
    /**
257
     *
258
     * @see \ASN1\Feature\Encodable::toDER()
259
     * @return string
260
     */
261
    public function toDER(): string
262
    {
263
        $identifier = new Identifier($this->typeClass(),
264
            $this->isConstructed() ? Identifier::CONSTRUCTED : Identifier::PRIMITIVE,
265
            $this->_typeTag);
266
        $content = $this->_encodedContentDER();
267
        if ($this->_indefiniteLength) {
268
            $length = new Length(0, true);
269
            $eoc = new Primitive\EOC();
270
            return $identifier->toDER() . $length->toDER() . $content .
271
                $eoc->toDER();
272
        } else {
273
            $length = new Length(strlen($content));
274
            return $identifier->toDER() . $length->toDER() . $content;
275
        }
276
    }
277
    
278
    /**
279
     *
280
     * @see \ASN1\Feature\ElementBase::tag()
281
     * @return int
282
     */
283
    public function tag(): int
284
    {
285
        return $this->_typeTag;
286
    }
287
    
288
    /**
289
     *
290
     * @see \ASN1\Feature\ElementBase::isType()
291
     * @return bool
292
     */
293
    public function isType(int $tag): bool
294
    {
295
        // if element is context specific
296
        if ($this->typeClass() == Identifier::CLASS_CONTEXT_SPECIFIC) {
297
            return false;
298
        }
299
        // negative tags identify an abstract pseudotype
300
        if ($tag < 0) {
301
            return $this->_isPseudoType($tag);
302
        }
303
        return $this->_isConcreteType($tag);
304
    }
305
    
306
    /**
307
     *
308
     * @see \ASN1\Feature\ElementBase::expectType()
309
     * @return ElementBase
310
     */
311
    public function expectType(int $tag): ElementBase
312
    {
313
        if (!$this->isType($tag)) {
314
            throw new \UnexpectedValueException(
315
                sprintf("%s expected, got %s.", self::tagToName($tag),
316
                    $this->_typeDescriptorString()));
317
        }
318
        return $this;
319
    }
320
    
321
    /**
322
     * Check whether the element is a concrete type of a given tag.
323
     *
324
     * @param int $tag
325
     * @return bool
326
     */
327
    private function _isConcreteType(int $tag): bool
328
    {
329
        // if tag doesn't match
330
        if ($this->tag() != $tag) {
331
            return false;
332
        }
333
        // if type is universal check that instance is of a correct class
334
        if ($this->typeClass() == Identifier::CLASS_UNIVERSAL) {
335
            $cls = self::_determineUniversalImplClass($tag);
336
            if (!$this instanceof $cls) {
337
                return false;
338
            }
339
        }
340
        return true;
341
    }
342
    
343
    /**
344
     * Check whether the element is a pseudotype.
345
     *
346
     * @param int $tag
347
     * @return bool
348
     */
349
    private function _isPseudoType(int $tag): bool
350
    {
351
        switch ($tag) {
352
            case self::TYPE_STRING:
353
                return $this instanceof StringType;
354
            case self::TYPE_TIME:
355
                return $this instanceof TimeType;
356
        }
357
        return false;
358
    }
359
    
360
    /**
361
     *
362
     * @see \ASN1\Feature\ElementBase::isTagged()
363
     * @return bool
364
     */
365
    public function isTagged(): bool
366
    {
367
        return $this instanceof TaggedType;
368
    }
369
    
370
    /**
371
     *
372
     * @see \ASN1\Feature\ElementBase::expectTagged()
373
     * @return TaggedType
374
     */
375
    public function expectTagged($tag = null): TaggedType
376
    {
377
        if (!$this->isTagged()) {
378
            throw new \UnexpectedValueException(
379
                sprintf("Context specific element expected, got %s.",
380
                    Identifier::classToName($this->typeClass())));
381
        }
382
        if (isset($tag) && $this->tag() != $tag) {
383
            throw new \UnexpectedValueException(
384
                sprintf("Tag %d expected, got %d.", $tag, $this->tag()));
385
        }
386
        return $this;
387
    }
388
    
389
    /**
390
     * Whether element has indefinite length.
391
     *
392
     * @return bool
393
     */
394
    public function hasIndefiniteLength(): bool
395
    {
396
        return $this->_indefiniteLength;
397
    }
398
    
399
    /**
400
     * Get self with indefinite length encoding set.
401
     *
402
     * @param bool $indefinite True for indefinite length, false for definite
403
     *        length
404
     * @return self
405
     */
406
    public function withIndefiniteLength(bool $indefinite = true): self
407
    {
408
        $obj = clone $this;
409
        $obj->_indefiniteLength = $indefinite;
410
        return $obj;
411
    }
412
    
413
    /**
414
     *
415
     * @see \ASN1\Feature\ElementBase::asElement()
416
     * @return Element
417
     */
418
    final public function asElement(): Element
419
    {
420
        return $this;
421
    }
422
    
423
    /**
424
     * Get element decorated with UnspecifiedType object.
425
     *
426
     * @return UnspecifiedType
427
     */
428
    public function asUnspecified(): UnspecifiedType
429
    {
430
        return new UnspecifiedType($this);
431
    }
432
    
433
    /**
434
     * Determine the class that implements the type.
435
     *
436
     * @param Identifier $identifier
437
     * @return string Class name
438
     */
439
    protected static function _determineImplClass(Identifier $identifier): string
440
    {
441
        switch ($identifier->typeClass()) {
442
            case Identifier::CLASS_UNIVERSAL:
443
                $cls = self::_determineUniversalImplClass($identifier->intTag());
444
                // constructed strings may be present in BER
445
                if ($identifier->isConstructed() &&
446
                    is_subclass_of($cls, PrimitiveString::class)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \ASN1\Type\PrimitiveString::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
447
                    $cls = ConstructedString::class;
448
                }
449
                return $cls;
450
            case Identifier::CLASS_CONTEXT_SPECIFIC:
451
                return ContextSpecificType::class;
452
            case Identifier::CLASS_APPLICATION:
453
                return ApplicationType::class;
454
            case Identifier::CLASS_PRIVATE:
455
                return PrivateType::class;
456
        }
457
        throw new \UnexpectedValueException(
458
            sprintf("%s %d not implemented.",
459
                Identifier::classToName($identifier->typeClass()),
460
                $identifier->tag()));
461
    }
462
    
463
    /**
464
     * Determine the class that implements an universal type of the given tag.
465
     *
466
     * @param int $tag
467
     * @throws \UnexpectedValueException
468
     * @return string Class name
469
     */
470
    protected static function _determineUniversalImplClass(int $tag): string
471
    {
472
        if (!array_key_exists($tag, self::MAP_TAG_TO_CLASS)) {
473
            throw new \UnexpectedValueException(
474
                "Universal tag $tag not implemented.");
475
        }
476
        return self::MAP_TAG_TO_CLASS[$tag];
477
    }
478
    
479
    /**
480
     * Get textual description of the type for debugging purposes.
481
     *
482
     * @return string
483
     */
484
    protected function _typeDescriptorString(): string
485
    {
486
        if ($this->typeClass() == Identifier::CLASS_UNIVERSAL) {
487
            return self::tagToName($this->_typeTag);
488
        }
489
        return sprintf("%s TAG %d", Identifier::classToName($this->typeClass()),
490
            $this->_typeTag);
491
    }
492
    
493
    /**
494
     * Get human readable name for an universal tag.
495
     *
496
     * @param int $tag
497
     * @return string
498
     */
499
    public static function tagToName(int $tag): string
500
    {
501
        if (!array_key_exists($tag, self::MAP_TYPE_TO_NAME)) {
502
            return "TAG $tag";
503
        }
504
        return self::MAP_TYPE_TO_NAME[$tag];
505
    }
506
}
507