Failed Conditions
Push — PHPSecLib_Rid ( 7208d1...fbc2c9 )
by Florent
04:39
created

RSA   D

Complexity

Total Complexity 112

Size/Duplication

Total Lines 1018
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 9
Bugs 1 Features 0
Metric Value
wmc 112
c 9
b 1
f 0
lcom 1
cbo 2
dl 0
loc 1018
rs 4.4378

30 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
F _parseKey() 0 100 22
B loadKey() 0 31 6
A _decodeLength() 0 11 2
A _string_shift() 0 7 1
B setHash() 0 20 5
B setMGFHash() 0 20 5
A setSaltLength() 0 4 1
A _i2osp() 0 10 2
A _os2ip() 0 4 1
C _exponentiate() 0 43 7
A _blind() 0 11 1
A _equals() 0 13 3
A _rsaep() 0 9 3
A _rsadp() 0 9 3
A _rsasp1() 0 9 3
A _rsavp1() 0 9 3
A _mgf1() 0 13 2
B _rsaes_oaep_encrypt() 0 36 2
B _rsaes_oaep_decrypt() 0 48 6
B _emsa_pss_encode() 0 26 3
C _emsa_pss_verify() 0 36 7
A _rsassa_pss_sign() 0 16 1
B _rsassa_pss_verify() 0 29 4
A encrypt() 0 15 3
A decrypt() 0 21 4
A sign() 0 9 3
A verify() 0 8 3
A _extractBER() 0 11 3
A setPublicKey() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like RSA often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use RSA, and based on these observations, apply Extract Interface, too.

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
81
    /**
82
     * Precomputed Zero.
83
     *
84
     * @var \Jose\Util\BigInteger
85
     */
86
    private $zero;
87
88
    /**
89
     * Precomputed One.
90
     *
91
     * @var \Jose\Util\BigInteger
92
     */
93
    private $one;
94
95
    /**
96
     * Modulus (ie. n).
97
     *
98
     * @var \Jose\Util\BigInteger
99
     */
100
    private $modulus;
101
102
    /**
103
     * Modulus length.
104
     *
105
     * @var int
106
     */
107
    private $k;
108
109
    /**
110
     * Exponent (ie. e or d).
111
     *
112
     * @var \Jose\Util\BigInteger
113
     */
114
    private $exponent;
115
116
    /**
117
     * Primes for Chinese Remainder Theorem (ie. p and q).
118
     *
119
     * @var array
120
     */
121
    private $primes;
122
123
    /**
124
     * Exponents for Chinese Remainder Theorem (ie. dP and dQ).
125
     *
126
     * @var array
127
     */
128
    private $exponents;
129
130
    /**
131
     * Coefficients for Chinese Remainder Theorem (ie. qInv).
132
     *
133
     * @var array
134
     */
135
    private $coefficients;
136
137
    /**
138
     * Hash name.
139
     *
140
     * @var string
141
     */
142
    private $hashName;
143
144
    /**
145
     * Hash function.
146
     *
147
     * @var \Jose\Util\Hash
148
     */
149
    private $hash;
150
151
    /**
152
     * Length of hash function output.
153
     *
154
     * @var int
155
     */
156
    private $hLen;
157
158
    /**
159
     * Length of salt.
160
     *
161
     * @var int
162
     */
163
    private $sLen;
164
165
    /**
166
     * Hash function for the Mask Generation Function.
167
     *
168
     * @var \Jose\Util\Hash
169
     */
170
    private $mgfHash;
171
172
    /**
173
     * Length of MGF hash function output.
174
     *
175
     * @var int
176
     */
177
    private $mgfHLen;
178
179
    /**
180
     * Public Exponent.
181
     *
182
     * @var mixed
183
     */
184
    private $publicExponent = false;
185
186
    /**
187
     * RSA constructor.
188
     */
189
    public function __construct()
190
    {
191
        $this->zero = BigInteger::createFromDecimalString('0');
192
        $this->one = BigInteger::createFromDecimalString('1');
193
194
        $this->hash = new Hash('sha1');
195
        $this->hLen = 20;
196
        $this->hashName = 'sha1';
197
        $this->mgfHash = new Hash('sha1');
198
        $this->mgfHLen = 20;
199
    }
200
201
    /**
202
     * Break a public or private key down into its constituant components.
203
     *
204
     * @param string $key
205
     * @param int    $type
206
     *
207
     * @return array
208
     */
209
    private function _parseKey($key, $type)
210
    {
211
        if ($type != self::PUBLIC_FORMAT_RAW && !is_string($key)) {
212
            return false;
213
        }
214
215
        switch ($type) {
216
            case self::PRIVATE_FORMAT_PKCS1:
217
            case self::PRIVATE_FORMAT_PKCS8:
218
            case self::PUBLIC_FORMAT_PKCS1:
219
                $decoded = $this->_extractBER($key);
220
221
                if ($decoded !== false) {
222
                    $key = $decoded;
223
                }
224
225
                $components = [];
226
227
                if (ord($this->_string_shift($key)) != self::ASN1_SEQUENCE) {
228
                    return false;
229
                }
230
                if ($this->_decodeLength($key) != strlen($key)) {
231
                    return false;
232
                }
233
234
                $tag = ord($this->_string_shift($key));
235
236
                if ($tag == self::ASN1_INTEGER && substr($key, 0, 3) == "\x01\x00\x30") {
237
                    $this->_string_shift($key, 3);
238
                    $tag = self::ASN1_SEQUENCE;
239
                }
240
241
                if ($tag == self::ASN1_SEQUENCE) {
242
                    $temp = $this->_string_shift($key, $this->_decodeLength($key));
243
                    if (ord($this->_string_shift($temp)) != self::ASN1_OBJECT) {
244
                        return false;
245
                    }
246
                    $length = $this->_decodeLength($temp);
247
                    switch ($this->_string_shift($temp, $length)) {
248
                        case "\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01": // rsaEncryption
249
                            break;
250
                    }
251
                    $tag = ord($this->_string_shift($key)); // skip over the BIT STRING / OCTET STRING tag
252
                    $this->_decodeLength($key); // skip over the BIT STRING / OCTET STRING length
253
                    if ($tag == self::ASN1_BITSTRING) {
254
                        $this->_string_shift($key);
255
                    }
256
                    if (ord($this->_string_shift($key)) != self::ASN1_SEQUENCE) {
257
                        return false;
258
                    }
259
                    if ($this->_decodeLength($key) != strlen($key)) {
260
                        return false;
261
                    }
262
                    $tag = ord($this->_string_shift($key));
263
                }
264
                if ($tag != self::ASN1_INTEGER) {
265
                    return false;
266
                }
267
268
                $length = $this->_decodeLength($key);
269
                $temp = $this->_string_shift($key, $length);
270
                if (strlen($temp) != 1 || ord($temp) > 2) {
271
                    $components['modulus'] = BigInteger::createFromBinaryString($temp);
272
                    $this->_string_shift($key); // skip over self::ASN1_INTEGER
273
                    $length = $this->_decodeLength($key);
274
                    $components[$type == self::PUBLIC_FORMAT_PKCS1 ? 'publicExponent' : 'privateExponent'] = BigInteger::createFromBinaryString($this->_string_shift($key, $length));
275
276
                    return $components;
277
                }
278
                if (ord($this->_string_shift($key)) != self::ASN1_INTEGER) {
279
                    return false;
280
                }
281
                $length = $this->_decodeLength($key);
282
                $components['modulus'] = BigInteger::createFromBinaryString($this->_string_shift($key, $length));
283
                $this->_string_shift($key);
284
                $length = $this->_decodeLength($key);
285
                $components['publicExponent'] = BigInteger::createFromBinaryString($this->_string_shift($key, $length));
286
                $this->_string_shift($key);
287
                $length = $this->_decodeLength($key);
288
                $components['privateExponent'] = BigInteger::createFromBinaryString($this->_string_shift($key, $length));
289
                $this->_string_shift($key);
290
                $length = $this->_decodeLength($key);
291
                $components['primes'] = [1 => BigInteger::createFromBinaryString($this->_string_shift($key, $length))];
292
                $this->_string_shift($key);
293
                $length = $this->_decodeLength($key);
294
                $components['primes'][] = BigInteger::createFromBinaryString($this->_string_shift($key, $length));
295
                $this->_string_shift($key);
296
                $length = $this->_decodeLength($key);
297
                $components['exponents'] = [1 => BigInteger::createFromBinaryString($this->_string_shift($key, $length))];
298
                $this->_string_shift($key);
299
                $length = $this->_decodeLength($key);
300
                $components['exponents'][] = BigInteger::createFromBinaryString($this->_string_shift($key, $length));
301
                $this->_string_shift($key);
302
                $length = $this->_decodeLength($key);
303
                $components['coefficients'] = [2 => BigInteger::createFromBinaryString($this->_string_shift($key, $length))];
304
305
306
                return $components;
307
        }
308
    }
309
310
    /**
311
     * Loads a public or private key.
312
     *
313
     * @param string $key
314
     * @param bool   $type optional
315
     *
316
     * @return bool
317
     */
318
    public function loadKey($key, $type = false)
319
    {
320
        $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...
321
322
        if ($components === false) {
323
            return false;
324
        }
325
326
        $this->modulus = $components['modulus'];
327
        $this->k = strlen($this->modulus->toBytes());
328
        $this->exponent = isset($components['privateExponent']) ? $components['privateExponent'] : $components['publicExponent'];
329
        if (isset($components['primes'])) {
330
            $this->primes = $components['primes'];
331
            $this->exponents = $components['exponents'];
332
            $this->coefficients = $components['coefficients'];
333
            $this->publicExponent = $components['publicExponent'];
334
        } else {
335
            $this->primes = [];
336
            $this->exponents = [];
337
            $this->coefficients = [];
338
            $this->publicExponent = false;
339
        }
340
341
        switch (true) {
342
            case strpos($key, '-BEGIN PUBLIC KEY-') !== false:
343
            case strpos($key, '-BEGIN RSA PUBLIC KEY-') !== false:
344
                $this->setPublicKey();
345
        }
346
347
        return true;
348
    }
349
350
    /**
351
     * DER-decode the length.
352
     *
353
     * @param string $string
354
     *
355
     * @return int
356
     */
357
    private function _decodeLength(&$string)
358
    {
359
        $length = ord($this->_string_shift($string));
360
        if ($length & 0x80) { // definite length, long form
361
            $length &= 0x7F;
362
            $temp = $this->_string_shift($string, $length);
363
            list(, $length) = unpack('N', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4));
364
        }
365
366
        return $length;
367
    }
368
369
    /**
370
     * String Shift.
371
     *
372
     * @param string $string
373
     * @param int    $index
374
     *
375
     * @return string
376
     */
377
    private function _string_shift(&$string, $index = 1)
378
    {
379
        $substr = substr($string, 0, $index);
380
        $string = substr($string, $index);
381
382
        return $substr;
383
    }
384
385
    /**
386
     * Determines which hashing function should be used.
387
     *
388
     * @param string $hash
389
     */
390
    public function setHash($hash)
391
    {
392
        switch ($hash) {
393
            case 'sha1':
394
                $this->hLen = 20;
395
                break;
396
            case 'sha256':
397
                $this->hLen = 32;
398
                break;
399
            case 'sha384':
400
                $this->hLen = 48;
401
                break;
402
            case 'sha512':
403
                $this->hLen = 64;
404
                break;
405
            default:
406
                throw new \InvalidArgumentException('Unsupported hash algorithm.');
407
        }
408
        $this->hash = new Hash($hash);
409
    }
410
411
    /**
412
     * Determines which hashing function should be used for the mask generation function.
413
     *
414
     * @param string $hash
415
     */
416
    public function setMGFHash($hash)
417
    {
418
        switch ($hash) {
419
            case 'sha1':
420
                $this->mgfHLen = 20;
421
                break;
422
            case 'sha256':
423
                $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...
424
                break;
425
            case 'sha384':
426
                $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...
427
                break;
428
            case 'sha512':
429
                $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...
430
                break;
431
            default:
432
                throw new \InvalidArgumentException('Unsupported hash algorithm.');
433
        }
434
        $this->mgfHash = new Hash($hash);
435
    }
436
437
    /**
438
     * Determines the salt length.
439
     *
440
     * @param int $sLen
441
     */
442
    public function setSaltLength($sLen)
443
    {
444
        $this->sLen = $sLen;
445
    }
446
447
    /**
448
     * Integer-to-Octet-String primitive.
449
     *
450
     * @param \Jose\Util\BigInteger $x
451
     * @param int                   $xLen
452
     *
453
     * @return string
454
     */
455
    private function _i2osp($x, $xLen)
456
    {
457
        $x = $x->toBytes();
458
        if (strlen($x) > $xLen) {
459
460
            return false;
461
        }
462
463
        return str_pad($x, $xLen, chr(0), STR_PAD_LEFT);
464
    }
465
466
    /**
467
     * Octet-String-to-Integer primitive.
468
     *
469
     * @param string $x
470
     *
471
     * @return \Jose\Util\BigInteger
472
     */
473
    private function _os2ip($x)
474
    {
475
        return BigInteger::createFromBinaryString($x);
476
    }
477
478
    /**
479
     * Exponentiate with or without Chinese Remainder Theorem.
480
     *
481
     * @param \Jose\Util\BigInteger $x
482
     *
483
     * @return \Jose\Util\BigInteger
484
     */
485
    private function _exponentiate($x)
486
    {
487
        if (empty($this->primes) || empty($this->coefficients) || empty($this->exponents)) {
488
            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 488 which is incompatible with the return type documented by Jose\Util\RSA::_exponentiate of type Jose\Util\BigInteger.
Loading history...
489
        }
490
491
        $num_primes = count($this->primes);
492
493
        $smallest = $this->primes[1];
494
        for ($i = 2; $i <= $num_primes; $i++) {
495
            if ($smallest->compare($this->primes[$i]) > 0) {
496
                $smallest = $this->primes[$i];
497
            }
498
        }
499
500
        $one = BigInteger::createFromDecimalString('1');
501
502
        $r = $one->random($one, $smallest->subtract($one));
503
504
        $m_i = [
505
            1 => $this->_blind($x, $r, 1),
506
            2 => $this->_blind($x, $r, 2),
507
        ];
508
        $h = $m_i[1]->subtract($m_i[2]);
509
        $h = $h->multiply($this->coefficients[2]);
510
        list(, $h) = $h->divide($this->primes[1]);
511
        $m = $m_i[2]->add($h->multiply($this->primes[2]));
512
513
        $r = $this->primes[1];
514
        for ($i = 3; $i <= $num_primes; $i++) {
515
            $m_i = $this->_blind($x, $r, $i);
516
517
            $r = $r->multiply($this->primes[$i - 1]);
518
519
            $h = $m_i->subtract($m);
520
            $h = $h->multiply($this->coefficients[$i]);
521
            list(, $h) = $h->divide($this->primes[$i]);
522
523
            $m = $m->add($r->multiply($h));
524
        }
525
526
        return $m;
527
    }
528
529
    /**
530
     * Performs RSA Blinding.
531
     *
532
     * @param \Jose\Util\BigInteger $x
533
     * @param \Jose\Util\BigInteger $r
534
     * @param int                   $i
535
     *
536
     * @return \Jose\Util\BigInteger
537
     */
538
    private function _blind($x, $r, $i)
539
    {
540
        $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...
541
        $x = $x->modPow($this->exponents[$i], $this->primes[$i]);
542
543
        $r = $r->modInverse($this->primes[$i]);
544
        $x = $x->multiply($r);
0 ignored issues
show
Bug introduced by
It seems like $r defined by $r->modInverse($this->primes[$i]) on line 543 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...
545
        list(, $x) = $x->divide($this->primes[$i]);
546
547
        return $x;
548
    }
549
550
    /**
551
     * Performs blinded RSA equality testing.
552
     *
553
     * @param string $x
554
     * @param string $y
555
     *
556
     * @return bool
557
     */
558
    private function _equals($x, $y)
559
    {
560
        if (strlen($x) != strlen($y)) {
561
            return false;
562
        }
563
564
        $result = 0;
565
        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...
566
            $result |= ord($x[$i]) ^ ord($y[$i]);
567
        }
568
569
        return $result == 0;
570
    }
571
572
    /**
573
     * RSAEP.
574
     *
575
     * @param \Jose\Util\BigInteger $m
576
     *
577
     * @return \Jose\Util\BigInteger
578
     */
579
    private function _rsaep($m)
580
    {
581
        if ($m->compare($this->zero) < 0 || $m->compare($this->modulus) > 0) {
582
583
            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...
584
        }
585
586
        return $this->_exponentiate($m);
587
    }
588
589
    /**
590
     * RSADP.
591
     *
592
     * @param \Jose\Util\BigInteger $c
593
     *
594
     * @return \Jose\Util\BigInteger
595
     */
596
    private function _rsadp($c)
597
    {
598
        if ($c->compare($this->zero) < 0 || $c->compare($this->modulus) > 0) {
599
600
            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...
601
        }
602
603
        return $this->_exponentiate($c);
604
    }
605
606
    /**
607
     * RSASP1.
608
     *
609
     * @param \Jose\Util\BigInteger $m
610
     *
611
     * @return \Jose\Util\BigInteger
612
     */
613
    private function _rsasp1($m)
614
    {
615
        if ($m->compare($this->zero) < 0 || $m->compare($this->modulus) > 0) {
616
617
            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...
618
        }
619
620
        return $this->_exponentiate($m);
621
    }
622
623
    /**
624
     * RSAVP1.
625
     *
626
     * @param \Jose\Util\BigInteger $s
627
     *
628
     * @return \Jose\Util\BigInteger
629
     */
630
    private function _rsavp1($s)
631
    {
632
        if ($s->compare($this->zero) < 0 || $s->compare($this->modulus) > 0) {
633
634
            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...
635
        }
636
637
        return $this->_exponentiate($s);
638
    }
639
640
    /**
641
     * MGF1.
642
     *
643
     * @param string $mgfSeed
644
     * @param int    $maskLen
645
     *
646
     * @return string
647
     */
648
    private function _mgf1($mgfSeed, $maskLen)
649
    {
650
        // if $maskLen would yield strings larger than 4GB, PKCS#1 suggests a "Mask too long" error be output.
651
652
        $t = '';
653
        $count = ceil($maskLen / $this->mgfHLen);
654
        for ($i = 0; $i < $count; $i++) {
655
            $c = pack('N', $i);
656
            $t .= $this->mgfHash->hash($mgfSeed.$c);
657
        }
658
659
        return substr($t, 0, $maskLen);
660
    }
661
662
    /**
663
     * RSAES-OAEP-ENCRYPT.
664
     *
665
     * @param string $m
666
     * @param string $l
667
     *
668
     * @return string
669
     */
670
    private function _rsaes_oaep_encrypt($m, $l = '')
671
    {
672
        $mLen = strlen($m);
673
674
        // Length checking
675
676
        // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
677
        // be output.
678
679
        if ($mLen > $this->k - 2 * $this->hLen - 2) {
680
681
            return false;
682
        }
683
684
        // EME-OAEP encoding
685
686
        $lHash = $this->hash->hash($l);
687
        $ps = str_repeat(chr(0), $this->k - $mLen - 2 * $this->hLen - 2);
688
        $db = $lHash.$ps.chr(1).$m;
689
        $seed = random_bytes($this->hLen);
690
        $dbMask = $this->_mgf1($seed, $this->k - $this->hLen - 1);
691
        $maskedDB = $db ^ $dbMask;
692
        $seedMask = $this->_mgf1($maskedDB, $this->hLen);
693
        $maskedSeed = $seed ^ $seedMask;
694
        $em = chr(0).$maskedSeed.$maskedDB;
695
696
        // RSA encryption
697
698
        $m = $this->_os2ip($em);
699
        $c = $this->_rsaep($m);
700
        $c = $this->_i2osp($c, $this->k);
701
702
        // Output the ciphertext C
703
704
        return $c;
705
    }
706
707
    /**
708
     * RSAES-OAEP-DECRYPT.
709
     *
710
     * @param string $c
711
     * @param string $l
712
     *
713
     * @return string
714
     */
715
    private function _rsaes_oaep_decrypt($c, $l = '')
716
    {
717
        // Length checking
718
719
        // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
720
        // be output.
721
722
        if (strlen($c) != $this->k || $this->k < 2 * $this->hLen + 2) {
723
724
            return false;
725
        }
726
727
        // RSA decryption
728
729
        $c = $this->_os2ip($c);
730
        $m = $this->_rsadp($c);
731
        if ($m === false) {
732
733
            return false;
734
        }
735
        $em = $this->_i2osp($m, $this->k);
736
737
        // EME-OAEP decoding
738
739
        $lHash = $this->hash->hash($l);
740
        $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...
741
        $maskedSeed = substr($em, 1, $this->hLen);
742
        $maskedDB = substr($em, $this->hLen + 1);
743
        $seedMask = $this->_mgf1($maskedDB, $this->hLen);
744
        $seed = $maskedSeed ^ $seedMask;
745
        $dbMask = $this->_mgf1($seed, $this->k - $this->hLen - 1);
746
        $db = $maskedDB ^ $dbMask;
747
        $lHash2 = substr($db, 0, $this->hLen);
748
        $m = substr($db, $this->hLen);
749
        if ($lHash != $lHash2) {
750
751
            return false;
752
        }
753
        $m = ltrim($m, chr(0));
754
        if (ord($m[0]) != 1) {
755
756
            return false;
757
        }
758
759
        // Output the message M
760
761
        return substr($m, 1);
762
    }
763
764
    /**
765
     * EMSA-PSS-ENCODE.
766
     *
767
     * @param string $m
768
     * @param int    $emBits
769
     *
770
     * @return bool
771
     */
772
    private function _emsa_pss_encode($m, $emBits)
773
    {
774
        // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
775
        // be output.
776
777
        $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8)
778
        $sLen = $this->sLen ? $this->sLen : $this->hLen;
779
780
        $mHash = $this->hash->hash($m);
781
        if ($emLen < $this->hLen + $sLen + 2) {
782
783
            return false;
784
        }
785
786
        $salt = random_bytes($sLen);
787
        $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt;
788
        $h = $this->hash->hash($m2);
789
        $ps = str_repeat(chr(0), $emLen - $sLen - $this->hLen - 2);
790
        $db = $ps.chr(1).$salt;
791
        $dbMask = $this->_mgf1($h, $emLen - $this->hLen - 1);
792
        $maskedDB = $db ^ $dbMask;
793
        $maskedDB[0] = ~chr(0xFF << ($emBits & 7)) & $maskedDB[0];
794
        $em = $maskedDB.$h.chr(0xBC);
795
796
        return $em;
797
    }
798
799
    /**
800
     * EMSA-PSS-VERIFY.
801
     *
802
     * @param string $m
803
     * @param string $em
804
     * @param int    $emBits
805
     *
806
     * @return string
807
     */
808
    private function _emsa_pss_verify($m, $em, $emBits)
809
    {
810
        // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
811
        // be output.
812
813
        $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8);
814
        $sLen = $this->sLen ? $this->sLen : $this->hLen;
815
816
        $mHash = $this->hash->hash($m);
817
        if ($emLen < $this->hLen + $sLen + 2) {
818
            return false;
819
        }
820
821
        if ($em[strlen($em) - 1] != chr(0xBC)) {
822
            return false;
823
        }
824
825
        $maskedDB = substr($em, 0, -$this->hLen - 1);
826
        $h = substr($em, -$this->hLen - 1, $this->hLen);
827
        $temp = chr(0xFF << ($emBits & 7));
828
        if ((~$maskedDB[0] & $temp) != $temp) {
829
            return false;
830
        }
831
        $dbMask = $this->_mgf1($h, $emLen - $this->hLen - 1);
832
        $db = $maskedDB ^ $dbMask;
833
        $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0];
834
        $temp = $emLen - $this->hLen - $sLen - 2;
835
        if (substr($db, 0, $temp) != str_repeat(chr(0), $temp) || ord($db[$temp]) != 1) {
836
            return false;
837
        }
838
        $salt = substr($db, $temp + 1); // should be $sLen long
839
        $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt;
840
        $h2 = $this->hash->hash($m2);
841
842
        return $this->_equals($h, $h2);
843
    }
844
845
    /**
846
     * RSASSA-PSS-SIGN.
847
     *
848
     * @param string $m
849
     *
850
     * @return string
851
     */
852
    private function _rsassa_pss_sign($m)
853
    {
854
        // EMSA-PSS encoding
855
856
        $em = $this->_emsa_pss_encode($m, 8 * $this->k - 1);
857
858
        // RSA signature
859
860
        $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 856 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...
861
        $s = $this->_rsasp1($m);
862
        $s = $this->_i2osp($s, $this->k);
863
864
        // Output the signature S
865
866
        return $s;
867
    }
868
869
    /**
870
     * RSASSA-PSS-VERIFY.
871
     *
872
     * @param string $m
873
     * @param string $s
874
     *
875
     * @return string
876
     */
877
    private function _rsassa_pss_verify($m, $s)
878
    {
879
        // Length checking
880
881
        if (strlen($s) != $this->k) {
882
883
            return false;
884
        }
885
886
        // RSA verification
887
888
        $modBits = 8 * $this->k;
889
890
        $s2 = $this->_os2ip($s);
891
        $m2 = $this->_rsavp1($s2);
892
        if ($m2 === false) {
893
894
            return false;
895
        }
896
        $em = $this->_i2osp($m2, $modBits >> 3);
897
        if ($em === false) {
898
899
            return false;
900
        }
901
902
        // EMSA-PSS verification
903
904
        return $this->_emsa_pss_verify($m, $em, $modBits - 1);
905
    }
906
907
    /**
908
     * Encryption.
909
     *
910
     * Both self::ENCRYPTION_OAEP and self::ENCRYPTION_PKCS1 both place limits on how long $plaintext can be.
911
     * If $plaintext exceeds those limits it will be broken up so that it does and the resultant ciphertext's will
912
     * be concatenated together.
913
     *
914
     * @see self::decrypt()
915
     *
916
     * @param string $plaintext
917
     *
918
     * @return string
919
     */
920
    public function encrypt($plaintext)
921
    {
922
        $length = $this->k - 2 * $this->hLen - 2;
923
        if ($length <= 0) {
924
            return false;
925
        }
926
927
        $plaintext = str_split($plaintext, $length);
928
        $ciphertext = '';
929
        foreach ($plaintext as $m) {
930
            $ciphertext .= $this->_rsaes_oaep_encrypt($m);
931
        }
932
933
        return $ciphertext;
934
    }
935
936
    /**
937
     * Decryption.
938
     *
939
     * @param string $ciphertext
940
     *
941
     * @return string
942
     */
943
    public function decrypt($ciphertext)
944
    {
945
        if ($this->k <= 0) {
946
            return false;
947
        }
948
949
        $ciphertext = str_split($ciphertext, $this->k);
950
        $ciphertext[count($ciphertext) - 1] = str_pad($ciphertext[count($ciphertext) - 1], $this->k, chr(0), STR_PAD_LEFT);
951
952
        $plaintext = '';
953
954
        foreach ($ciphertext as $c) {
955
            $temp = $this->_rsaes_oaep_decrypt($c);
956
            if ($temp === false) {
957
                return false;
958
            }
959
            $plaintext .= $temp;
960
        }
961
962
        return $plaintext;
963
    }
964
965
    /**
966
     * Create a signature.
967
     *
968
     * @param string $message
969
     *
970
     * @return string
971
     */
972
    public function sign($message)
973
    {
974
        if (empty($this->modulus) || empty($this->exponent)) {
975
            return false;
976
        }
977
978
979
        return $this->_rsassa_pss_sign($message);
980
    }
981
982
    /**
983
     * Verifies a signature.
984
     *
985
     * @param string $message
986
     * @param string $signature
987
     *
988
     * @return bool
989
     */
990
    public function verify($message, $signature)
991
    {
992
        if (empty($this->modulus) || empty($this->exponent)) {
993
            return false;
994
        }
995
996
        return $this->_rsassa_pss_verify($message, $signature);
997
    }
998
999
    /**
1000
     * Extract raw BER from Base64 encoding.
1001
     *
1002
     * @param string $str
1003
     *
1004
     * @return string
1005
     */
1006
    private function _extractBER($str)
1007
    {
1008
        $temp = preg_replace('#.*?^-+[^-]+-+[\r\n ]*$#ms', '', $str, 1);
1009
        // remove the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- stuff
1010
        $temp = preg_replace('#-+[^-]+-+#', '', $temp);
1011
        // remove new lines
1012
        $temp = str_replace(["\r", "\n", ' '], '', $temp);
1013
        $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false;
1014
1015
        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...
1016
    }
1017
1018
    /**
1019
     * Defines the public key.
1020
     *
1021
     * @return bool
1022
     */
1023
    private function setPublicKey()
1024
    {
1025
        if (!empty($this->modulus)) {
1026
            $this->publicExponent = $this->exponent;
1027
1028
            return true;
1029
        }
1030
    }
1031
}
1032