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.
Completed
Push — master ( 9e3709...6a1987 )
by Joni
03:09
created

Element   B

Complexity

Total Complexity 39

Size/Duplication

Total Lines 437
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 39
lcom 1
cbo 4
dl 0
loc 437
ccs 99
cts 99
cp 1
rs 8.2857
c 0
b 0
f 0

19 Methods

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