Encoder::encodeLength()   A
last analyzed

Complexity

Conditions 4
Paths 5

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 9.8312

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 13
c 1
b 0
f 0
dl 0
loc 19
ccs 4
cts 14
cp 0.2857
rs 9.8333
cc 4
nc 5
nop 1
crap 9.8312
1
<?php
2
3
namespace Ocsp\Asn1\Der;
4
5
use DateTime;
6
use DateTimeImmutable;
7
use DateTimeZone;
8
use Ocsp\Asn1\Element;
9
use Ocsp\Asn1\Encoder as EncoderInterface;
10
use Ocsp\Asn1\Tag;
11
use Ocsp\Asn1\TaggableElement;
12
use Ocsp\Exception\Asn1EncodingException;
13
use Ocsp\Service\Math;
14
15
/**
16
 * Encoder from ASN.1 to DER.
17
 */
18
class Encoder implements EncoderInterface
19
{
20
    /**
21
     * {@inheritdoc}
22
     *
23
     * @see \Ocsp\Asn1\Encoder::getEncodingHandle()
24
     */
25
    public function getEncodingHandle()
26
    {
27
        return 'der';
28
    }
29
30
    /**
31
     * {@inheritdoc}
32
     *
33
     * @see \Ocsp\Asn1\Encoder::encodeElement()
34
     */
35 2
    public function encodeElement(Element $element)
36
    {
37 2
        $tag = null;
38 2
        if ($element instanceof TaggableElement) {
39 2
            $tag = $element->getTag();
40
        }
41 2
        if ($tag === null) {
42 2
            return $this->doEncodeElement($element);
43
        }
44
        switch ($tag->getEnvironment()) {
45
            case Tag::ENVIRONMENT_EXPLICIT:
46
                $elementBytes = $this->doEncodeElement($element);
47
48
                return $this->encodeType($tag->getTagID(), $tag->getClass(), true) . $this->encodeLength($elementBytes) . $elementBytes;
49
            case Tag::ENVIRONMENT_IMPLICIT:
50
                return $this->doEncodeElement($element, $tag);
51
            default:
52
                throw Asn1EncodingException::create(sprintf('Invalid ASN.1 tag environment: %s', $tag->getEnvironment()));
53
        }
54
    }
55
56
    /**
57
     * {@inheritdoc}
58
     *
59
     * @see \Ocsp\Asn1\Encoder::encodeInteger()
60
     */
61 2
    public function encodeInteger($value)
62
    {
63 2
        if (is_int($value)) {
64
            if ($value === 0) {
65
                return "\x00";
66
            }
67
            if ($value > 0) {
68
                if (PHP_INT_SIZE === 4 || $value < 0xFFFFFFFF) {
69
                    return ltrim(pack('N', $value), "\x00");
70
                }
71
                if (PHP_VERSION_ID >= 50603) {
72
                    return ltrim(pack('J', $value), "\x00");
73
                }
74
            }
75
            $value = Math::createBigInteger((string) $value);
76 2
        } elseif (is_string($value)) {
77 2
            $value = Math::createBigInteger($value);
78
        }
79
80 2
        return $value->toBytes(true);
81
    }
82
83
    /**
84
     * {@inheritdoc}
85
     *
86
     * @see \Ocsp\Asn1\Encoder::encodeIdentifier()
87
     */
88 2
    public function encodeIdentifier($value)
89
    {
90 2
        $parts = explode('.', $value);
91 2
        $result = chr((int) array_shift($parts) * 40 + (int) array_shift($parts));
92 2
        while (($part = array_shift($parts)) !== null) {
93 2
            $result .= $this->encodeIdentifierPart($part);
94
        }
95
96 2
        return $result;
97
    }
98
99
    /**
100
     * {@inheritdoc}
101
     *
102
     * @see \Ocsp\Asn1\Encoder::encodeOctetString()
103
     */
104 2
    public function encodeOctetString($value)
105
    {
106 2
        return $value;
107
    }
108
109
    /**
110
     * {@inheritdoc}
111
     *
112
     * @see \Ocsp\Asn1\Encoder::encodePrintableString()
113
     */
114 2
    public function encodePrintableString($value)
115
    {
116 2
        return $value;
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     *
122
     * @see \Ocsp\Asn1\Encoder::encodeBitString()
123
     */
124
    public function encodeBitString($bytes, $unusedBitsInLastByte)
125
    {
126
        return chr($unusedBitsInLastByte) . $bytes;
127
    }
128
129
    /**
130
     * {@inheritdoc}
131
     *
132
     * @see \Ocsp\Asn1\Encoder::encodeGeneralizedTime()
133
     */
134
    public function encodeGeneralizedTime(DateTimeImmutable $value)
135
    {
136
        $datetime = new DateTime('now', new DateTimeZone('UTC'));
137
        $datetime->setTimestamp($value->getTimestamp());
138
139
        $result = $datetime->format('YmdHis');
140
        $useconds = ltrim($value->format('u'), '0');
141
        if ($useconds !== '') {
142
            $result .= '.' . $useconds;
143
        }
144
        $result .= 'Z';
145
146
        return $result;
147
    }
148
149
    /**
150
     * @param \Ocsp\Asn1\Element $element
151
     * @param \Ocsp\Asn1\Tag|null $implicitTag
152
     *
153
     * @throws \Ocsp\Exception\Asn1EncodingException when the element or the tag are defined in invalid classes
154
     *
155
     * @return string
156
     */
157 2
    protected function doEncodeElement(Element $element, Tag $implicitTag = null)
158
    {
159 2
        if ($implicitTag === null) {
160 2
            $result = $this->encodeType($element->getTypeID(), $element->getClass(), $element->isConstructed());
161
        } else {
162
            $result = $this->encodeType($implicitTag->getTagID(), $implicitTag->getClass(), $element->isConstructed());
163
        }
164 2
        $elementBytes = $element->getEncodedValue($this);
165
166 2
        return $result . $this->encodeLength($elementBytes) . $elementBytes;
167
    }
168
169
    /**
170
     * Encode a part of the value of an IDENTIFIER element.
171
     *
172
     * @param string $part
173
     *
174
     * @return string
175
     */
176 2
    protected function encodeIdentifierPart($part)
177
    {
178 2
        $part = ltrim($part, '0');
179 2
        if ($part === '') {
180
            return "\x00";
181
        }
182 2
        $bytes = [];
183 2
        if (strlen($part) < strlen(PHP_INT_MAX)) {
184 2
            $int = (int) $part;
185 2
            if ($int <= 127) {
186 2
                return chr($int);
187
            }
188
            $bits = decbin($int);
189
        } else {
190
            $bits = Math::createBigInteger($part)->toBits();
191
        }
192
        do {
193
            array_unshift($bytes, bindec(substr($bits, -7)));
194
            $bits = substr($bits, 0, -7);
195
        } while ($bits !== '' && $bits !== false);
196
        $result = '';
197
        foreach (array_splice($bytes, 0, -1) as $byte) {
198
            $result .= chr(0x80 | $byte);
199
        }
200
        $result .= chr(reset($bytes));
201
202
        return $result;
203
    }
204
205
    /**
206
     * Encode the type ID.
207
     *
208
     * @param int|string|\phpseclib\Math\BigInteger|\phpseclib3\Math\BigInteger $typeID the type ID
0 ignored issues
show
Bug introduced by
The type phpseclib\Math\BigInteger was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
209
     * @param string $class the class (the value of one of the Element::CLASS_... constants)
210
     * @param bool $isConstructed is the element a constructed element?
211
     *
212
     * @throws \Ocsp\Exception\Asn1EncodingException when $class contains an invalid value
213
     *
214
     * @return string
215
     */
216 2
    protected function encodeType($typeID, $class, $isConstructed)
217
    {
218
        switch ($class) {
219 2
            case Element::CLASS_UNIVERSAL:
220 2
                $firstByte = 0b00000000;
221 2
                break;
222
            case Element::CLASS_APPLICATION:
223
                $firstByte = 0b01000000;
224
                break;
225
            case Element::CLASS_CONTEXTSPECIFIC:
226
                $firstByte = 0b10000000;
227
                break;
228
            case Element::CLASS_PRIVATE:
229
                $firstByte = 0b11000000;
230
                break;
231
            default:
232
                throw Asn1EncodingException::create(sprintf('Invalid ASN.1 class: %s', $class));
233
        }
234 2
        if ($isConstructed) {
235 2
            $firstByte |= 0b00100000;
236
        }
237 2
        $typeIDBits = $this->getBits($typeID);
238 2
        if (!isset($typeIDBits[5])) {
239 2
            $typeIDInt = bindec($typeIDBits);
240 2
            if ($typeIDInt <= 30) {
241 2
                return chr($firstByte | $typeIDInt);
242
            }
243
        }
244
        $result = chr($firstByte | 0b00011111);
245
        while (isset($typeIDBits[7])) {
246
            $result .= chr(bindec('1' . substr($typeIDBits, -7)));
0 ignored issues
show
Bug introduced by
It seems like bindec('1' . substr($typeIDBits, -7)) can also be of type double; however, parameter $codepoint of chr() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

246
            $result .= chr(/** @scrutinizer ignore-type */ bindec('1' . substr($typeIDBits, -7)));
Loading history...
247
            $typeIDBits = substr($typeIDBits, 0, -7);
248
        }
249
        $result .= chr(bindec($typeIDBits));
250
251
        return $result;
252
    }
253
254
    /**
255
     * Encode the length of the encoded value of an element.
256
     *
257
     * @param string $encodedElementValue the encoded value of an element
258
     *
259
     * @return string
260
     */
261 2
    protected function encodeLength($encodedElementValue)
262
    {
263 2
        $length = strlen($encodedElementValue);
264 2
        if ($length < 127) {
265 2
            return chr($length);
266
        }
267
        $lengthHex = dechex($length);
268
        $lengthHexLength = strlen($lengthHex);
269
        if (($lengthHexLength % 2) !== 0) {
270
            $lengthHex = '0' . $lengthHex;
271
            $lengthHexLength++;
272
        }
273
        $lengthNumBytes = strlen($lengthHex) >> 1;
274
        $result = chr($lengthNumBytes | 0x80);
275
        for ($index = 0; $index < $lengthHexLength; $index += 2) {
276
            $result .= chr(hexdec($lengthHex[$index] . $lengthHex[$index + 1]));
0 ignored issues
show
Bug introduced by
It seems like hexdec($lengthHex[$index...$lengthHex[$index + 1]) can also be of type double; however, parameter $codepoint of chr() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

276
            $result .= chr(/** @scrutinizer ignore-type */ hexdec($lengthHex[$index] . $lengthHex[$index + 1]));
Loading history...
277
        }
278
279
        return $result;
280
    }
281
282
    /**
283
     * Get the bits representing a number.
284
     *
285
     * @param int|string|\phpseclib\Math\BigInteger|\phpseclib3\Math\BigInteger $number
286
     *
287
     * @return string
288
     */
289 2
    protected function getBits($number)
290
    {
291 2
        if (is_int($number)) {
292 2
            return decbin($number);
293
        }
294
        if (is_string($number)) {
295
            $number = ltrim($number, '0');
296
            if ($number === '') {
297
                return '0';
298
            }
299
            if (strlen($number) < strlen((string) PHP_INT_MAX)) {
300
                return decbin((int) $number);
301
            }
302
            $number = Math::createBigInteger($number);
303
        }
304
305
        return $number->toBits(true);
306
    }
307
}
308