Failed Conditions
Push — PHPSecLib_Rid ( 0687d3...7208d1 )
by Florent
02:49
created

RSA::_string_shift()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 1 Features 0
Metric Value
c 5
b 1
f 0
dl 0
loc 7
rs 9.4285
cc 1
eloc 4
nc 1
nop 2
1
<?php
2
3
/*
4
 * The MIT License (MIT)
5
 *
6
 * Copyright (c) 2014-2016 Spomky-Labs
7
 *
8
 * This software may be modified and distributed under the terms
9
 * of the MIT license.  See the LICENSE file for details.
10
 */
11
12
namespace Jose\Util;
13
14
final class RSA
15
{
16
    /**
17
     * ASN1 Integer.
18
     */
19
    const ASN1_INTEGER = 2;
20
21
    /**
22
     * ASN1 Bit String.
23
     */
24
    const ASN1_BITSTRING = 3;
25
26
    /**
27
     * ASN1 Octet String.
28
     */
29
    const ASN1_OCTETSTRING = 4;
30
31
    /**
32
     * ASN1 Object Identifier.
33
     */
34
    const ASN1_OBJECT = 6;
35
36
    /**
37
     * ASN1 Sequence (with the constucted bit set).
38
     */
39
    const ASN1_SEQUENCE = 48;
40
41
    /**
42
     * To use the pure-PHP implementation.
43
     */
44
    const MODE_INTERNAL = 1;
45
46
    /**
47
     * To use the OpenSSL library.
48
     */
49
    const MODE_OPENSSL = 2;
50
51
    /**
52
     * PKCS#1 formatted private key.
53
     */
54
    const PRIVATE_FORMAT_PKCS1 = 0;
55
56
    /**
57
     * PuTTY formatted private key.
58
     */
59
    const PRIVATE_FORMAT_PUTTY = 1;
60
61
    /**
62
     * XML formatted private key.
63
     */
64
    const PRIVATE_FORMAT_XML = 2;
65
66
    /**
67
     * PKCS#8 formatted private key.
68
     */
69
    const PRIVATE_FORMAT_PKCS8 = 8;
70
71
    /**
72
     * Raw public key.
73
     */
74
    const PUBLIC_FORMAT_RAW = 3;
75
76
    /**
77
     * PKCS#1 formatted public key (raw).
78
     */
79
    const PUBLIC_FORMAT_PKCS1 = 4;
80
    const PUBLIC_FORMAT_PKCS1_RAW = 4;
81
82
    /**
83
     * XML formatted public key.
84
     */
85
    const PUBLIC_FORMAT_XML = 5;
86
87
    /**
88
     * OpenSSH formatted public key.
89
     */
90
    const PUBLIC_FORMAT_OPENSSH = 6;
91
92
    /**
93
     * PKCS#1 formatted public key (encapsulated).
94
     */
95
    const PUBLIC_FORMAT_PKCS8 = 7;
96
97
    /**
98
     * Precomputed Zero.
99
     *
100
     * @var \Jose\Util\BigInteger
101
     */
102
    private $zero;
103
104
    /**
105
     * Precomputed One.
106
     *
107
     * @var \Jose\Util\BigInteger
108
     */
109
    private $one;
110
111
    /**
112
     * Modulus (ie. n).
113
     *
114
     * @var \Jose\Util\BigInteger
115
     */
116
    private $modulus;
117
118
    /**
119
     * Modulus length.
120
     *
121
     * @var int
122
     */
123
    private $k;
124
125
    /**
126
     * Exponent (ie. e or d).
127
     *
128
     * @var \Jose\Util\BigInteger
129
     */
130
    private $exponent;
131
132
    /**
133
     * Primes for Chinese Remainder Theorem (ie. p and q).
134
     *
135
     * @var array
136
     */
137
    private $primes;
138
139
    /**
140
     * Exponents for Chinese Remainder Theorem (ie. dP and dQ).
141
     *
142
     * @var array
143
     */
144
    private $exponents;
145
146
    /**
147
     * Coefficients for Chinese Remainder Theorem (ie. qInv).
148
     *
149
     * @var array
150
     */
151
    private $coefficients;
152
153
    /**
154
     * Hash name.
155
     *
156
     * @var string
157
     */
158
    private $hashName;
159
160
    /**
161
     * Hash function.
162
     *
163
     * @var \Jose\Util\Hash
164
     */
165
    private $hash;
166
167
    /**
168
     * Length of hash function output.
169
     *
170
     * @var int
171
     */
172
    private $hLen;
173
174
    /**
175
     * Length of salt.
176
     *
177
     * @var int
178
     */
179
    private $sLen;
180
181
    /**
182
     * Hash function for the Mask Generation Function.
183
     *
184
     * @var \Jose\Util\Hash
185
     */
186
    private $mgfHash;
187
188
    /**
189
     * Length of MGF hash function output.
190
     *
191
     * @var int
192
     */
193
    private $mgfHLen;
194
195
    /**
196
     * Public Exponent.
197
     *
198
     * @var mixed
199
     */
200
    private $publicExponent = false;
201
202
    /**
203
     * Current String.
204
     *
205
     * @var mixed
206
     */
207
    private $current;
208
209
    /**
210
     * OpenSSL configuration file name.
211
     *
212
     * @var mixed
213
     */
214
    private $configFile;
215
216
    /**
217
     * Public key comment field.
218
     *
219
     * @var string
220
     */
221
    private $comment = 'phpseclib-generated-key';
222
223
    /**
224
     * RSA constructor.
225
     */
226
    public function __construct()
227
    {
228
        $this->configFile = dirname(__FILE__).'/../openssl.cnf';
229
230
        if (!defined('CRYPT_RSA_MODE')) {
231
            switch (true) {
232
                // Math/BigInteger's openssl requirements are a little less stringent than Crypt/RSA's. in particular,
233
                // Math/BigInteger doesn't require an openssl.cfg file whereas Crypt/RSA does. so if Math/BigInteger
234
                // can't use OpenSSL it can be pretty trivially assumed, then, that Crypt/RSA can't either.
235
                case defined('MATH_BIGINTEGER_OPENSSL_DISABLE'):
236
                    define('CRYPT_RSA_MODE', self::MODE_INTERNAL);
237
                    break;
238
                case extension_loaded('openssl') && file_exists($this->configFile):
239
                    // some versions of XAMPP have mismatched versions of OpenSSL which causes it not to work
240
                    ob_start();
241
                    @phpinfo();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
242
                    $content = ob_get_contents();
243
                    ob_end_clean();
244
245
                    preg_match_all('#OpenSSL (Header|Library) Version(.*)#im', $content, $matches);
246
247
                    $versions = [];
248
                    if (!empty($matches[1])) {
249
                        for ($i = 0; $i < count($matches[1]); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
Performance Best Practice introduced by
Consider avoiding function calls on each iteration of the for loop.

If you have a function call in the test part of a for loop, this function is executed on each iteration. Often such a function, can be moved to the initialization part and be cached.

// count() is called on each iteration
for ($i=0; $i < count($collection); $i++) { }

// count() is only called once
for ($i=0, $c=count($collection); $i<$c; $i++) { }
Loading history...
250
                            $fullVersion = trim(str_replace('=>', '', strip_tags($matches[2][$i])));
251
252
                            // Remove letter part in OpenSSL version
253
                            if (!preg_match('/(\d+\.\d+\.\d+)/i', $fullVersion, $m)) {
254
                                $versions[$matches[1][$i]] = $fullVersion;
255
                            } else {
256
                                $versions[$matches[1][$i]] = $m[0];
257
                            }
258
                        }
259
                    }
260
261
                    // it doesn't appear that OpenSSL versions were reported upon until PHP 5.3+
262
                    switch (true) {
263
                        case !isset($versions['Header']):
264
                        case !isset($versions['Library']):
265
                        case $versions['Header'] == $versions['Library']:
266
                            define('CRYPT_RSA_MODE', self::MODE_OPENSSL);
267
                            break;
268
                        default:
269
                            define('CRYPT_RSA_MODE', self::MODE_INTERNAL);
270
                            define('MATH_BIGINTEGER_OPENSSL_DISABLE', true);
271
                    }
272
                    break;
273
                default:
274
                    define('CRYPT_RSA_MODE', self::MODE_INTERNAL);
275
            }
276
        }
277
278
        $this->zero = BigInteger::createFromDecimalString('0');
279
        $this->one = BigInteger::createFromDecimalString('1');
280
281
        $this->hash = new Hash('sha1');
282
        $this->hLen = 20;
283
        $this->hashName = 'sha1';
284
        $this->mgfHash = new Hash('sha1');
285
        $this->mgfHLen = 20;
286
    }
287
288
    /**
289
     * Break a public or private key down into its constituant components.
290
     *
291
     * @param string $key
292
     * @param int    $type
293
     *
294
     * @return array
295
     */
296
    private function _parseKey($key, $type)
297
    {
298
        if ($type != self::PUBLIC_FORMAT_RAW && !is_string($key)) {
299
            return false;
300
        }
301
302
        switch ($type) {
303
            case self::PRIVATE_FORMAT_PKCS1:
304
            case self::PRIVATE_FORMAT_PKCS8:
305
            case self::PUBLIC_FORMAT_PKCS1:
306
                $decoded = $this->_extractBER($key);
307
308
                if ($decoded !== false) {
309
                    $key = $decoded;
310
                }
311
312
                $components = [];
313
314
                if (ord($this->_string_shift($key)) != self::ASN1_SEQUENCE) {
315
                    return false;
316
                }
317
                if ($this->_decodeLength($key) != strlen($key)) {
318
                    return false;
319
                }
320
321
                $tag = ord($this->_string_shift($key));
322
323
                if ($tag == self::ASN1_INTEGER && substr($key, 0, 3) == "\x01\x00\x30") {
324
                    $this->_string_shift($key, 3);
325
                    $tag = self::ASN1_SEQUENCE;
326
                }
327
328
                if ($tag == self::ASN1_SEQUENCE) {
329
                    $temp = $this->_string_shift($key, $this->_decodeLength($key));
330
                    if (ord($this->_string_shift($temp)) != self::ASN1_OBJECT) {
331
                        return false;
332
                    }
333
                    $length = $this->_decodeLength($temp);
334
                    switch ($this->_string_shift($temp, $length)) {
335
                        case "\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01": // rsaEncryption
336
                            break;
337
                    }
338
                    $tag = ord($this->_string_shift($key)); // skip over the BIT STRING / OCTET STRING tag
339
                    $this->_decodeLength($key); // skip over the BIT STRING / OCTET STRING length
340
                    if ($tag == self::ASN1_BITSTRING) {
341
                        $this->_string_shift($key);
342
                    }
343
                    if (ord($this->_string_shift($key)) != self::ASN1_SEQUENCE) {
344
                        return false;
345
                    }
346
                    if ($this->_decodeLength($key) != strlen($key)) {
347
                        return false;
348
                    }
349
                    $tag = ord($this->_string_shift($key));
350
                }
351
                if ($tag != self::ASN1_INTEGER) {
352
                    return false;
353
                }
354
355
                $length = $this->_decodeLength($key);
356
                $temp = $this->_string_shift($key, $length);
357
                if (strlen($temp) != 1 || ord($temp) > 2) {
358
                    $components['modulus'] = BigInteger::createFromBinaryString($temp);
359
                    $this->_string_shift($key); // skip over self::ASN1_INTEGER
360
                    $length = $this->_decodeLength($key);
361
                    $components[$type == self::PUBLIC_FORMAT_PKCS1 ? 'publicExponent' : 'privateExponent'] = BigInteger::createFromBinaryString($this->_string_shift($key, $length));
362
363
                    return $components;
364
                }
365
                if (ord($this->_string_shift($key)) != self::ASN1_INTEGER) {
366
                    return false;
367
                }
368
                $length = $this->_decodeLength($key);
369
                $components['modulus'] = BigInteger::createFromBinaryString($this->_string_shift($key, $length));
370
                $this->_string_shift($key);
371
                $length = $this->_decodeLength($key);
372
                $components['publicExponent'] = BigInteger::createFromBinaryString($this->_string_shift($key, $length));
373
                $this->_string_shift($key);
374
                $length = $this->_decodeLength($key);
375
                $components['privateExponent'] = BigInteger::createFromBinaryString($this->_string_shift($key, $length));
376
                $this->_string_shift($key);
377
                $length = $this->_decodeLength($key);
378
                $components['primes'] = [1 => BigInteger::createFromBinaryString($this->_string_shift($key, $length))];
379
                $this->_string_shift($key);
380
                $length = $this->_decodeLength($key);
381
                $components['primes'][] = BigInteger::createFromBinaryString($this->_string_shift($key, $length));
382
                $this->_string_shift($key);
383
                $length = $this->_decodeLength($key);
384
                $components['exponents'] = [1 => BigInteger::createFromBinaryString($this->_string_shift($key, $length))];
385
                $this->_string_shift($key);
386
                $length = $this->_decodeLength($key);
387
                $components['exponents'][] = BigInteger::createFromBinaryString($this->_string_shift($key, $length));
388
                $this->_string_shift($key);
389
                $length = $this->_decodeLength($key);
390
                $components['coefficients'] = [2 => BigInteger::createFromBinaryString($this->_string_shift($key, $length))];
391
392
393
                return $components;
394
        }
395
    }
396
397
    /**
398
     * Data Handler.
399
     *
400
     * Called by xml_set_character_data_handler()
401
     *
402
     * @param resource $parser
403
     * @param string   $data
404
     */
405
    public function _data_handler($parser, $data)
0 ignored issues
show
Unused Code introduced by
The parameter $parser is not used and could be removed.

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

Loading history...
406
    {
407
        if (!isset($this->current) || is_object($this->current)) {
408
            return;
409
        }
410
        $this->current .= trim($data);
411
    }
412
413
    /**
414
     * Loads a public or private key.
415
     *
416
     * @param string $key
417
     * @param bool   $type optional
418
     *
419
     * @return bool
420
     */
421
    public function loadKey($key, $type = false)
422
    {
423
        $components = $this->_parseKey($key, $type);
0 ignored issues
show
Documentation introduced by
$type is of type boolean, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
424
425
        if ($components === false) {
426
            return false;
427
        }
428
429
        if (isset($components['comment']) && $components['comment'] !== false) {
430
            $this->comment = $components['comment'];
431
        }
432
        $this->modulus = $components['modulus'];
433
        $this->k = strlen($this->modulus->toBytes());
434
        $this->exponent = isset($components['privateExponent']) ? $components['privateExponent'] : $components['publicExponent'];
435
        if (isset($components['primes'])) {
436
            $this->primes = $components['primes'];
437
            $this->exponents = $components['exponents'];
438
            $this->coefficients = $components['coefficients'];
439
            $this->publicExponent = $components['publicExponent'];
440
        } else {
441
            $this->primes = [];
442
            $this->exponents = [];
443
            $this->coefficients = [];
444
            $this->publicExponent = false;
445
        }
446
447
        switch (true) {
448
            case strpos($key, '-BEGIN PUBLIC KEY-') !== false:
449
            case strpos($key, '-BEGIN RSA PUBLIC KEY-') !== false:
450
                $this->setPublicKey();
451
        }
452
453
        return true;
454
    }
455
456
    /**
457
     * DER-decode the length.
458
     *
459
     * @param string $string
460
     *
461
     * @return int
462
     */
463
    private function _decodeLength(&$string)
464
    {
465
        $length = ord($this->_string_shift($string));
466
        if ($length & 0x80) { // definite length, long form
467
            $length &= 0x7F;
468
            $temp = $this->_string_shift($string, $length);
469
            list(, $length) = unpack('N', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4));
470
        }
471
472
        return $length;
473
    }
474
475
    /**
476
     * String Shift.
477
     *
478
     * @param string $string
479
     * @param int    $index
480
     *
481
     * @return string
482
     */
483
    private function _string_shift(&$string, $index = 1)
484
    {
485
        $substr = substr($string, 0, $index);
486
        $string = substr($string, $index);
487
488
        return $substr;
489
    }
490
491
    /**
492
     * Determines which hashing function should be used.
493
     *
494
     * @param string $hash
495
     */
496
    public function setHash($hash)
497
    {
498
        switch ($hash) {
499
            case 'sha1':
500
                $this->hLen = 20;
501
                break;
502
            case 'sha256':
503
                $this->hLen = 32;
504
                break;
505
            case 'sha384':
506
                $this->hLen = 48;
507
                break;
508
            case 'sha512':
509
                $this->hLen = 64;
510
                break;
511
            default:
512
                throw new \InvalidArgumentException('Unsupported hash algorithm.');
513
        }
514
        $this->hash = new Hash($hash);
515
    }
516
517
    /**
518
     * Determines which hashing function should be used for the mask generation function.
519
     *
520
     * @param string $hash
521
     */
522
    public function setMGFHash($hash)
523
    {
524
        switch ($hash) {
525
            case 'sha1':
526
                $this->mgfHLen = 20;
527
                break;
528
            case 'sha256':
529
                $this->mgfHash = 32;
0 ignored issues
show
Documentation Bug introduced by
It seems like 32 of type integer is incompatible with the declared type object<Jose\Util\Hash> of property $mgfHash.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
530
                break;
531
            case 'sha384':
532
                $this->mgfHash = 48;
0 ignored issues
show
Documentation Bug introduced by
It seems like 48 of type integer is incompatible with the declared type object<Jose\Util\Hash> of property $mgfHash.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
533
                break;
534
            case 'sha512':
535
                $this->mgfHash = 64;
0 ignored issues
show
Documentation Bug introduced by
It seems like 64 of type integer is incompatible with the declared type object<Jose\Util\Hash> of property $mgfHash.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
536
                break;
537
            default:
538
                throw new \InvalidArgumentException('Unsupported hash algorithm.');
539
        }
540
        $this->mgfHash = new Hash($hash);
541
    }
542
543
    /**
544
     * Determines the salt length.
545
     *
546
     * @param int $sLen
547
     */
548
    public function setSaltLength($sLen)
549
    {
550
        $this->sLen = $sLen;
551
    }
552
553
    /**
554
     * Integer-to-Octet-String primitive.
555
     *
556
     * @param \Jose\Util\BigInteger $x
557
     * @param int                   $xLen
558
     *
559
     * @return string
560
     */
561
    private function _i2osp($x, $xLen)
562
    {
563
        $x = $x->toBytes();
564
        if (strlen($x) > $xLen) {
565
            user_error('Integer too large');
566
567
            return false;
568
        }
569
570
        return str_pad($x, $xLen, chr(0), STR_PAD_LEFT);
571
    }
572
573
    /**
574
     * Octet-String-to-Integer primitive.
575
     *
576
     * @param string $x
577
     *
578
     * @return \Jose\Util\BigInteger
579
     */
580
    private function _os2ip($x)
581
    {
582
        return BigInteger::createFromBinaryString($x);
583
    }
584
585
    /**
586
     * Exponentiate with or without Chinese Remainder Theorem.
587
     *
588
     * @param \Jose\Util\BigInteger $x
589
     *
590
     * @return \Jose\Util\BigInteger
591
     */
592
    private function _exponentiate($x)
593
    {
594
        if (empty($this->primes) || empty($this->coefficients) || empty($this->exponents)) {
595
            return $x->modPow($this->exponent, $this->modulus);
0 ignored issues
show
Bug Compatibility introduced by
The expression $x->modPow($this->exponent, $this->modulus); of type Jose\Util\BigInteger|boolean adds the type boolean to the return on line 595 which is incompatible with the return type documented by Jose\Util\RSA::_exponentiate of type Jose\Util\BigInteger.
Loading history...
596
        }
597
598
        $num_primes = count($this->primes);
599
600
        if (defined('CRYPT_RSA_DISABLE_BLINDING')) {
601
            $m_i = [
602
                1 => $x->modPow($this->exponents[1], $this->primes[1]),
603
                2 => $x->modPow($this->exponents[2], $this->primes[2]),
604
            ];
605
            $h = $m_i[1]->subtract($m_i[2]);
0 ignored issues
show
Bug introduced by
It seems like $m_i[2] can also be of type boolean; however, Jose\Util\BigInteger::subtract() does only seem to accept object<Jose\Util\BigInteger>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
606
            $h = $h->multiply($this->coefficients[2]);
607
            list(, $h) = $h->divide($this->primes[1]);
608
            $m = $m_i[2]->add($h->multiply($this->primes[2]));
609
610
            $r = $this->primes[1];
611
            for ($i = 3; $i <= $num_primes; $i++) {
612
                $m_i = $x->modPow($this->exponents[$i], $this->primes[$i]);
613
614
                $r = $r->multiply($this->primes[$i - 1]);
615
616
                $h = $m_i->subtract($m);
617
                $h = $h->multiply($this->coefficients[$i]);
618
                list(, $h) = $h->divide($this->primes[$i]);
619
620
                $m = $m->add($r->multiply($h));
621
            }
622
        } else {
623
            $smallest = $this->primes[1];
624
            for ($i = 2; $i <= $num_primes; $i++) {
625
                if ($smallest->compare($this->primes[$i]) > 0) {
626
                    $smallest = $this->primes[$i];
627
                }
628
            }
629
630
            $one = BigInteger::createFromDecimalString('1');
631
632
            $r = $one->random($one, $smallest->subtract($one));
633
634
            $m_i = [
635
                1 => $this->_blind($x, $r, 1),
636
                2 => $this->_blind($x, $r, 2),
637
            ];
638
            $h = $m_i[1]->subtract($m_i[2]);
639
            $h = $h->multiply($this->coefficients[2]);
640
            list(, $h) = $h->divide($this->primes[1]);
641
            $m = $m_i[2]->add($h->multiply($this->primes[2]));
642
643
            $r = $this->primes[1];
644
            for ($i = 3; $i <= $num_primes; $i++) {
645
                $m_i = $this->_blind($x, $r, $i);
646
647
                $r = $r->multiply($this->primes[$i - 1]);
648
649
                $h = $m_i->subtract($m);
650
                $h = $h->multiply($this->coefficients[$i]);
651
                list(, $h) = $h->divide($this->primes[$i]);
652
653
                $m = $m->add($r->multiply($h));
654
            }
655
        }
656
657
        return $m;
658
    }
659
660
    /**
661
     * Performs RSA Blinding.
662
     *
663
     * @param \Jose\Util\BigInteger $x
664
     * @param \Jose\Util\BigInteger $r
665
     * @param int                   $i
666
     *
667
     * @return \Jose\Util\BigInteger
668
     */
669
    private function _blind($x, $r, $i)
670
    {
671
        $x = $x->multiply($r->modPow($this->publicExponent, $this->primes[$i]));
0 ignored issues
show
Bug introduced by
It seems like $r->modPow($this->public...ent, $this->primes[$i]) targeting Jose\Util\BigInteger::modPow() can also be of type boolean; however, Jose\Util\BigInteger::multiply() does only seem to accept object<Jose\Util\BigInteger>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
672
        $x = $x->modPow($this->exponents[$i], $this->primes[$i]);
673
674
        $r = $r->modInverse($this->primes[$i]);
675
        $x = $x->multiply($r);
0 ignored issues
show
Bug introduced by
It seems like $r defined by $r->modInverse($this->primes[$i]) on line 674 can also be of type boolean; however, Jose\Util\BigInteger::multiply() does only seem to accept object<Jose\Util\BigInteger>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
676
        list(, $x) = $x->divide($this->primes[$i]);
677
678
        return $x;
679
    }
680
681
    /**
682
     * Performs blinded RSA equality testing.
683
     *
684
     * @param string $x
685
     * @param string $y
686
     *
687
     * @return bool
688
     */
689
    private function _equals($x, $y)
690
    {
691
        if (strlen($x) != strlen($y)) {
692
            return false;
693
        }
694
695
        $result = 0;
696
        for ($i = 0; $i < strlen($x); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
Consider avoiding function calls on each iteration of the for loop.

If you have a function call in the test part of a for loop, this function is executed on each iteration. Often such a function, can be moved to the initialization part and be cached.

// count() is called on each iteration
for ($i=0; $i < count($collection); $i++) { }

// count() is only called once
for ($i=0, $c=count($collection); $i<$c; $i++) { }
Loading history...
697
            $result |= ord($x[$i]) ^ ord($y[$i]);
698
        }
699
700
        return $result == 0;
701
    }
702
703
    /**
704
     * RSAEP.
705
     *
706
     * @param \Jose\Util\BigInteger $m
707
     *
708
     * @return \Jose\Util\BigInteger
709
     */
710
    private function _rsaep($m)
711
    {
712
        if ($m->compare($this->zero) < 0 || $m->compare($this->modulus) > 0) {
713
            user_error('Message representative out of range');
714
715
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Jose\Util\RSA::_rsaep of type Jose\Util\BigInteger.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
716
        }
717
718
        return $this->_exponentiate($m);
719
    }
720
721
    /**
722
     * RSADP.
723
     *
724
     * @param \Jose\Util\BigInteger $c
725
     *
726
     * @return \Jose\Util\BigInteger
727
     */
728
    private function _rsadp($c)
729
    {
730
        if ($c->compare($this->zero) < 0 || $c->compare($this->modulus) > 0) {
731
            user_error('Ciphertext representative out of range');
732
733
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Jose\Util\RSA::_rsadp of type Jose\Util\BigInteger.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
734
        }
735
736
        return $this->_exponentiate($c);
737
    }
738
739
    /**
740
     * RSASP1.
741
     *
742
     * @param \Jose\Util\BigInteger $m
743
     *
744
     * @return \Jose\Util\BigInteger
745
     */
746
    private function _rsasp1($m)
747
    {
748
        if ($m->compare($this->zero) < 0 || $m->compare($this->modulus) > 0) {
749
            user_error('Message representative out of range');
750
751
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Jose\Util\RSA::_rsasp1 of type Jose\Util\BigInteger.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
752
        }
753
754
        return $this->_exponentiate($m);
755
    }
756
757
    /**
758
     * RSAVP1.
759
     *
760
     * @param \Jose\Util\BigInteger $s
761
     *
762
     * @return \Jose\Util\BigInteger
763
     */
764
    private function _rsavp1($s)
765
    {
766
        if ($s->compare($this->zero) < 0 || $s->compare($this->modulus) > 0) {
767
            user_error('Signature representative out of range');
768
769
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Jose\Util\RSA::_rsavp1 of type Jose\Util\BigInteger.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
770
        }
771
772
        return $this->_exponentiate($s);
773
    }
774
775
    /**
776
     * MGF1.
777
     *
778
     * @param string $mgfSeed
779
     * @param int    $maskLen
780
     *
781
     * @return string
782
     */
783
    private function _mgf1($mgfSeed, $maskLen)
784
    {
785
        // if $maskLen would yield strings larger than 4GB, PKCS#1 suggests a "Mask too long" error be output.
786
787
        $t = '';
788
        $count = ceil($maskLen / $this->mgfHLen);
789
        for ($i = 0; $i < $count; $i++) {
790
            $c = pack('N', $i);
791
            $t .= $this->mgfHash->hash($mgfSeed.$c);
792
        }
793
794
        return substr($t, 0, $maskLen);
795
    }
796
797
    /**
798
     * RSAES-OAEP-ENCRYPT.
799
     *
800
     * @param string $m
801
     * @param string $l
802
     *
803
     * @return string
804
     */
805
    private function _rsaes_oaep_encrypt($m, $l = '')
806
    {
807
        $mLen = strlen($m);
808
809
        // Length checking
810
811
        // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
812
        // be output.
813
814
        if ($mLen > $this->k - 2 * $this->hLen - 2) {
815
            user_error('Message too long');
816
817
            return false;
818
        }
819
820
        // EME-OAEP encoding
821
822
        $lHash = $this->hash->hash($l);
823
        $ps = str_repeat(chr(0), $this->k - $mLen - 2 * $this->hLen - 2);
824
        $db = $lHash.$ps.chr(1).$m;
825
        $seed = random_bytes($this->hLen);
826
        $dbMask = $this->_mgf1($seed, $this->k - $this->hLen - 1);
827
        $maskedDB = $db ^ $dbMask;
828
        $seedMask = $this->_mgf1($maskedDB, $this->hLen);
829
        $maskedSeed = $seed ^ $seedMask;
830
        $em = chr(0).$maskedSeed.$maskedDB;
831
832
        // RSA encryption
833
834
        $m = $this->_os2ip($em);
835
        $c = $this->_rsaep($m);
836
        $c = $this->_i2osp($c, $this->k);
837
838
        // Output the ciphertext C
839
840
        return $c;
841
    }
842
843
    /**
844
     * RSAES-OAEP-DECRYPT.
845
     *
846
     * @param string $c
847
     * @param string $l
848
     *
849
     * @return string
850
     */
851
    private function _rsaes_oaep_decrypt($c, $l = '')
852
    {
853
        // Length checking
854
855
        // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
856
        // be output.
857
858
        if (strlen($c) != $this->k || $this->k < 2 * $this->hLen + 2) {
859
            user_error('Decryption error');
860
861
            return false;
862
        }
863
864
        // RSA decryption
865
866
        $c = $this->_os2ip($c);
867
        $m = $this->_rsadp($c);
868
        if ($m === false) {
869
            user_error('Decryption error');
870
871
            return false;
872
        }
873
        $em = $this->_i2osp($m, $this->k);
874
875
        // EME-OAEP decoding
876
877
        $lHash = $this->hash->hash($l);
878
        $y = ord($em[0]);
0 ignored issues
show
Unused Code introduced by
$y is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
879
        $maskedSeed = substr($em, 1, $this->hLen);
880
        $maskedDB = substr($em, $this->hLen + 1);
881
        $seedMask = $this->_mgf1($maskedDB, $this->hLen);
882
        $seed = $maskedSeed ^ $seedMask;
883
        $dbMask = $this->_mgf1($seed, $this->k - $this->hLen - 1);
884
        $db = $maskedDB ^ $dbMask;
885
        $lHash2 = substr($db, 0, $this->hLen);
886
        $m = substr($db, $this->hLen);
887
        if ($lHash != $lHash2) {
888
            user_error('Decryption error');
889
890
            return false;
891
        }
892
        $m = ltrim($m, chr(0));
893
        if (ord($m[0]) != 1) {
894
            user_error('Decryption error');
895
896
            return false;
897
        }
898
899
        // Output the message M
900
901
        return substr($m, 1);
902
    }
903
904
    /**
905
     * EMSA-PSS-ENCODE.
906
     *
907
     * @param string $m
908
     * @param int    $emBits
909
     *
910
     * @return bool
911
     */
912
    private function _emsa_pss_encode($m, $emBits)
913
    {
914
        // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
915
        // be output.
916
917
        $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8)
918
        $sLen = $this->sLen ? $this->sLen : $this->hLen;
919
920
        $mHash = $this->hash->hash($m);
921
        if ($emLen < $this->hLen + $sLen + 2) {
922
            user_error('Encoding error');
923
924
            return false;
925
        }
926
927
        $salt = random_bytes($sLen);
928
        $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt;
929
        $h = $this->hash->hash($m2);
930
        $ps = str_repeat(chr(0), $emLen - $sLen - $this->hLen - 2);
931
        $db = $ps.chr(1).$salt;
932
        $dbMask = $this->_mgf1($h, $emLen - $this->hLen - 1);
933
        $maskedDB = $db ^ $dbMask;
934
        $maskedDB[0] = ~chr(0xFF << ($emBits & 7)) & $maskedDB[0];
935
        $em = $maskedDB.$h.chr(0xBC);
936
937
        return $em;
938
    }
939
940
    /**
941
     * EMSA-PSS-VERIFY.
942
     *
943
     * @param string $m
944
     * @param string $em
945
     * @param int    $emBits
946
     *
947
     * @return string
948
     */
949
    private function _emsa_pss_verify($m, $em, $emBits)
950
    {
951
        // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
952
        // be output.
953
954
        $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8);
955
        $sLen = $this->sLen ? $this->sLen : $this->hLen;
956
957
        $mHash = $this->hash->hash($m);
958
        if ($emLen < $this->hLen + $sLen + 2) {
959
            return false;
960
        }
961
962
        if ($em[strlen($em) - 1] != chr(0xBC)) {
963
            return false;
964
        }
965
966
        $maskedDB = substr($em, 0, -$this->hLen - 1);
967
        $h = substr($em, -$this->hLen - 1, $this->hLen);
968
        $temp = chr(0xFF << ($emBits & 7));
969
        if ((~$maskedDB[0] & $temp) != $temp) {
970
            return false;
971
        }
972
        $dbMask = $this->_mgf1($h, $emLen - $this->hLen - 1);
973
        $db = $maskedDB ^ $dbMask;
974
        $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0];
975
        $temp = $emLen - $this->hLen - $sLen - 2;
976
        if (substr($db, 0, $temp) != str_repeat(chr(0), $temp) || ord($db[$temp]) != 1) {
977
            return false;
978
        }
979
        $salt = substr($db, $temp + 1); // should be $sLen long
980
        $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt;
981
        $h2 = $this->hash->hash($m2);
982
983
        return $this->_equals($h, $h2);
984
    }
985
986
    /**
987
     * RSASSA-PSS-SIGN.
988
     *
989
     * @param string $m
990
     *
991
     * @return string
992
     */
993
    private function _rsassa_pss_sign($m)
994
    {
995
        // EMSA-PSS encoding
996
997
        $em = $this->_emsa_pss_encode($m, 8 * $this->k - 1);
998
999
        // RSA signature
1000
1001
        $m = $this->_os2ip($em);
0 ignored issues
show
Security Bug introduced by
It seems like $em defined by $this->_emsa_pss_encode($m, 8 * $this->k - 1) on line 997 can also be of type false; however, Jose\Util\RSA::_os2ip() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1002
        $s = $this->_rsasp1($m);
1003
        $s = $this->_i2osp($s, $this->k);
1004
1005
        // Output the signature S
1006
1007
        return $s;
1008
    }
1009
1010
    /**
1011
     * RSASSA-PSS-VERIFY.
1012
     *
1013
     * @param string $m
1014
     * @param string $s
1015
     *
1016
     * @return string
1017
     */
1018
    private function _rsassa_pss_verify($m, $s)
1019
    {
1020
        // Length checking
1021
1022
        if (strlen($s) != $this->k) {
1023
            user_error('Invalid signature');
1024
1025
            return false;
1026
        }
1027
1028
        // RSA verification
1029
1030
        $modBits = 8 * $this->k;
1031
1032
        $s2 = $this->_os2ip($s);
1033
        $m2 = $this->_rsavp1($s2);
1034
        if ($m2 === false) {
1035
            user_error('Invalid signature');
1036
1037
            return false;
1038
        }
1039
        $em = $this->_i2osp($m2, $modBits >> 3);
1040
        if ($em === false) {
1041
            user_error('Invalid signature');
1042
1043
            return false;
1044
        }
1045
1046
        // EMSA-PSS verification
1047
1048
        return $this->_emsa_pss_verify($m, $em, $modBits - 1);
1049
    }
1050
1051
    /**
1052
     * Encryption.
1053
     *
1054
     * Both self::ENCRYPTION_OAEP and self::ENCRYPTION_PKCS1 both place limits on how long $plaintext can be.
1055
     * If $plaintext exceeds those limits it will be broken up so that it does and the resultant ciphertext's will
1056
     * be concatenated together.
1057
     *
1058
     * @see self::decrypt()
1059
     *
1060
     * @param string $plaintext
1061
     *
1062
     * @return string
1063
     */
1064
    public function encrypt($plaintext)
1065
    {
1066
        $length = $this->k - 2 * $this->hLen - 2;
1067
        if ($length <= 0) {
1068
            return false;
1069
        }
1070
1071
        $plaintext = str_split($plaintext, $length);
1072
        $ciphertext = '';
1073
        foreach ($plaintext as $m) {
1074
            $ciphertext .= $this->_rsaes_oaep_encrypt($m);
1075
        }
1076
1077
        return $ciphertext;
1078
    }
1079
1080
    /**
1081
     * Decryption.
1082
     *
1083
     * @param string $ciphertext
1084
     *
1085
     * @return string
1086
     */
1087
    public function decrypt($ciphertext)
1088
    {
1089
        if ($this->k <= 0) {
1090
            return false;
1091
        }
1092
1093
        $ciphertext = str_split($ciphertext, $this->k);
1094
        $ciphertext[count($ciphertext) - 1] = str_pad($ciphertext[count($ciphertext) - 1], $this->k, chr(0), STR_PAD_LEFT);
1095
1096
        $plaintext = '';
1097
1098
        foreach ($ciphertext as $c) {
1099
            $temp = $this->_rsaes_oaep_decrypt($c);
1100
            if ($temp === false) {
1101
                return false;
1102
            }
1103
            $plaintext .= $temp;
1104
        }
1105
1106
        return $plaintext;
1107
    }
1108
1109
    /**
1110
     * Create a signature.
1111
     *
1112
     * @param string $message
1113
     *
1114
     * @return string
1115
     */
1116
    public function sign($message)
1117
    {
1118
        if (empty($this->modulus) || empty($this->exponent)) {
1119
            return false;
1120
        }
1121
1122
1123
        return $this->_rsassa_pss_sign($message);
1124
    }
1125
1126
    /**
1127
     * Verifies a signature.
1128
     *
1129
     * @param string $message
1130
     * @param string $signature
1131
     *
1132
     * @return bool
1133
     */
1134
    public function verify($message, $signature)
1135
    {
1136
        if (empty($this->modulus) || empty($this->exponent)) {
1137
            return false;
1138
        }
1139
1140
        return $this->_rsassa_pss_verify($message, $signature);
1141
    }
1142
1143
    /**
1144
     * Extract raw BER from Base64 encoding.
1145
     *
1146
     * @param string $str
1147
     *
1148
     * @return string
1149
     */
1150
    private function _extractBER($str)
1151
    {
1152
        $temp = preg_replace('#.*?^-+[^-]+-+[\r\n ]*$#ms', '', $str, 1);
1153
        // remove the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- stuff
1154
        $temp = preg_replace('#-+[^-]+-+#', '', $temp);
1155
        // remove new lines
1156
        $temp = str_replace(["\r", "\n", ' '], '', $temp);
1157
        $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false;
1158
1159
        return $temp != false ? $temp : $str;
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $temp of type string|false against false; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
1160
    }
1161
1162
    /**
1163
     * Defines the public key.
1164
     *
1165
     * @return bool
1166
     */
1167
    private function setPublicKey()
1168
    {
1169
        if (!empty($this->modulus)) {
1170
            $this->publicExponent = $this->exponent;
1171
1172
            return true;
1173
        }
1174
    }
1175
}
1176