Failed Conditions
Push — PHPSecLib_Rid ( de2eab...8b51c6 )
by Florent
02:45
created

src/Util/RSA.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
use Base64Url\Base64Url;
15
use Jose\Object\JWKInterface;
16
17
final class RSA
18
{
19
    /**
20
     * Precomputed Zero.
21
     *
22
     * @var \Jose\Util\BigInteger
23
     */
24
    private $zero;
25
26
    /**
27
     * Precomputed One.
28
     *
29
     * @var \Jose\Util\BigInteger
30
     */
31
    private $one;
32
33
    /**
34
     * Modulus (ie. n).
35
     *
36
     * @var \Jose\Util\BigInteger
37
     */
38
    private $modulus;
39
40
    /**
41
     * Modulus length.
42
     *
43
     * @var int
44
     */
45
    private $k;
46
47
    /**
48
     * Exponent (ie. e or d).
49
     *
50
     * @var \Jose\Util\BigInteger
51
     */
52
    private $exponent;
53
54
    /**
55
     * Primes for Chinese Remainder Theorem (ie. p and q).
56
     *
57
     * @var \Jose\Util\BigInteger[]
58
     */
59
    private $primes;
60
61
    /**
62
     * Exponents for Chinese Remainder Theorem (ie. dP and dQ).
63
     *
64
     * @var \Jose\Util\BigInteger[]
65
     */
66
    private $exponents;
67
68
    /**
69
     * Coefficients for Chinese Remainder Theorem (ie. qInv).
70
     *
71
     * @var \Jose\Util\BigInteger[]
72
     */
73
    private $coefficients;
74
75
    /**
76
     * Hash function.
77
     *
78
     * @var \Jose\Util\Hash
79
     */
80
    private $hash;
81
82
    /**
83
     * Hash function for the Mask Generation Function.
84
     *
85
     * @var \Jose\Util\Hash
86
     */
87
    private $mgfHash;
88
89
    /**
90
     * Public Exponent.
91
     *
92
     * @var mixed
93
     */
94
    private $publicExponent = false;
95
96
    /**
97
     * RSA constructor.
98
     */
99
    public function __construct()
100
    {
101
        $this->zero = BigInteger::createFromDecimalString('0');
102
        $this->one = BigInteger::createFromDecimalString('1');
103
104
        $this->hash = Hash::sha1();
105
        $this->mgfHash = Hash::sha1();
106
    }
107
108
    /**
109
     * Loads a public or private key.
110
     *
111
     * @param \Jose\Object\JWKInterface $key
112
     */
113
    public function loadKey(JWKInterface $key)
114
    {
115
        $this->modulus = BigInteger::createFromBinaryString(Base64Url::decode($key->get('n')));
116
        $this->k = strlen($this->modulus->toBytes());
117
118
        if ($key->has('d')) {
119
            $this->exponent = BigInteger::createFromBinaryString(Base64Url::decode($key->get('d')));
120
            $this->publicExponent = BigInteger::createFromBinaryString(Base64Url::decode($key->get('e')));
121
        } else {
122
            $this->exponent = BigInteger::createFromBinaryString(Base64Url::decode($key->get('e')));
123
        }
124
125
        if ($key->has('p') && $key->has('q')) {
126
            $this->primes = [
127
                BigInteger::createFromBinaryString(Base64Url::decode($key->get('p'))),
128
                BigInteger::createFromBinaryString(Base64Url::decode($key->get('q'))),
129
            ];
130
        } else {
131
            $this->primes = [];
132
        }
133
134
        if ($key->has('dp') && $key->has('dq') && $key->has('qi')) {
135
            $this->coefficients = [
136
                BigInteger::createFromBinaryString(Base64Url::decode($key->get('dp'))),
137
                BigInteger::createFromBinaryString(Base64Url::decode($key->get('dq'))),
138
                BigInteger::createFromBinaryString(Base64Url::decode($key->get('qi'))),
139
            ];
140
        } else {
141
            $this->coefficients = [];
142
        }
143
    }
144
145
    /**
146
     * Determines which hashing function should be used.
147
     *
148
     * @param string $hash
149
     */
150
    public function setHash($hash)
151
    {
152
        $this->hash = Hash::$hash();
153
    }
154
155
    /**
156
     * Determines which hashing function should be used for the mask generation function.
157
     *
158
     * @param string $hash
159
     */
160
    public function setMGFHash($hash)
161
    {
162
        $this->mgfHash = Hash::$hash();
163
    }
164
165
    /**
166
     * Integer-to-Octet-String primitive.
167
     *
168
     * @param \Jose\Util\BigInteger $x
169
     * @param int                   $xLen
170
     *
171
     * @return string
172
     */
173
    private function convertIntegerToOctetString($x, $xLen)
174
    {
175
        $x = $x->toBytes();
176
        if (strlen($x) > $xLen) {
177
178
            return false;
179
        }
180
181
        return str_pad($x, $xLen, chr(0), STR_PAD_LEFT);
182
    }
183
184
    /**
185
     * Octet-String-to-Integer primitive.
186
     *
187
     * @param string $x
188
     *
189
     * @return \Jose\Util\BigInteger
190
     */
191
    private function convertOctetStringToInteger($x)
192
    {
193
        return BigInteger::createFromBinaryString($x);
194
    }
195
196
    /**
197
     * Exponentiate with or without Chinese Remainder Theorem.
198
     *
199
     * @param \Jose\Util\BigInteger $x
200
     *
201
     * @return \Jose\Util\BigInteger
202
     */
203
    private function _exponentiate($x)
204
    {
205
        if (empty($this->primes) || empty($this->coefficients) || empty($this->exponents)) {
206
            return $x->modPow($this->exponent, $this->modulus);
207
        }
208
209
        $num_primes = count($this->primes);
210
211
        $smallest = $this->primes[1];
212
        for ($i = 2; $i <= $num_primes; $i++) {
213
            if ($smallest->compare($this->primes[$i]) > 0) {
214
                $smallest = $this->primes[$i];
215
            }
216
        }
217
218
        $one = BigInteger::createFromDecimalString('1');
219
220
        $r = $one->random($one, $smallest->subtract($one));
221
222
        $m_i = [
223
            1 => $this->_blind($x, $r, 1),
224
            2 => $this->_blind($x, $r, 2),
225
        ];
226
        $h = $m_i[1]->subtract($m_i[2]);
227
        $h = $h->multiply($this->coefficients[2]);
228
        list(, $h) = $h->divide($this->primes[1]);
229
        $m = $m_i[2]->add($h->multiply($this->primes[2]));
230
231
        $r = $this->primes[1];
232
        for ($i = 3; $i <= $num_primes; $i++) {
233
            $m_i = $this->_blind($x, $r, $i);
234
235
            $r = $r->multiply($this->primes[$i - 1]);
236
237
            $h = $m_i->subtract($m);
238
            $h = $h->multiply($this->coefficients[$i]);
239
            list(, $h) = $h->divide($this->primes[$i]);
240
241
            $m = $m->add($r->multiply($h));
242
        }
243
244
        return $m;
245
    }
246
247
    /**
248
     * Performs RSA Blinding.
249
     *
250
     * @param \Jose\Util\BigInteger $x
251
     * @param \Jose\Util\BigInteger $r
252
     * @param int                   $i
253
     *
254
     * @return \Jose\Util\BigInteger
255
     */
256
    private function _blind($x, $r, $i)
257
    {
258
        $x = $x->multiply($r->modPow($this->publicExponent, $this->primes[$i]));
259
        $x = $x->modPow($this->exponents[$i], $this->primes[$i]);
260
261
        $r = $r->modInverse($this->primes[$i]);
262
        $x = $x->multiply($r);
263
        list(, $x) = $x->divide($this->primes[$i]);
264
265
        return $x;
266
    }
267
268
    /**
269
     * Performs blinded RSA equality testing.
270
     *
271
     * @param string $x
272
     * @param string $y
273
     *
274
     * @return bool
275
     */
276
    private function _equals($x, $y)
277
    {
278
        if (strlen($x) != strlen($y)) {
279
            return false;
280
        }
281
282
        $result = 0;
283
        for ($i = 0; $i < strlen($x); $i++) {
284
            $result |= ord($x[$i]) ^ ord($y[$i]);
285
        }
286
287
        return $result == 0;
288
    }
289
290
    /**
291
     * RSAEP.
292
     *
293
     * @param \Jose\Util\BigInteger $m
294
     *
295
     * @return \Jose\Util\BigInteger|false
296
     */
297
    private function _rsaep($m)
298
    {
299
        if ($m->compare($this->zero) < 0 || $m->compare($this->modulus) > 0) {
300
301
            return false;
302
        }
303
304
        return $this->_exponentiate($m);
305
    }
306
307
    /**
308
     * RSADP.
309
     *
310
     * @param \Jose\Util\BigInteger $c
311
     *
312
     * @return \Jose\Util\BigInteger|false
313
     */
314
    private function _rsadp($c)
315
    {
316
        if ($c->compare($this->zero) < 0 || $c->compare($this->modulus) > 0) {
317
318
            return false;
319
        }
320
321
        return $this->_exponentiate($c);
322
    }
323
324
    /**
325
     * RSASP1.
326
     *
327
     * @param \Jose\Util\BigInteger $m
328
     *
329
     * @return \Jose\Util\BigInteger|false
330
     */
331
    private function _rsasp1($m)
332
    {
333
        if ($m->compare($this->zero) < 0 || $m->compare($this->modulus) > 0) {
334
335
            return false;
336
        }
337
338
        return $this->_exponentiate($m);
339
    }
340
341
    /**
342
     * RSAVP1.
343
     *
344
     * @param \Jose\Util\BigInteger $s
345
     *
346
     * @return \Jose\Util\BigInteger|false
347
     */
348
    private function _rsavp1($s)
349
    {
350
        if ($s->compare($this->zero) < 0 || $s->compare($this->modulus) > 0) {
351
352
            return false;
353
        }
354
355
        return $this->_exponentiate($s);
356
    }
357
358
    /**
359
     * MGF1.
360
     *
361
     * @param string $mgfSeed
362
     * @param int    $maskLen
363
     *
364
     * @return string
365
     */
366
    private function _mgf1($mgfSeed, $maskLen)
367
    {
368
        // if $maskLen would yield strings larger than 4GB, PKCS#1 suggests a "Mask too long" error be output.
369
370
        $t = '';
371
        $count = ceil($maskLen / $this->mgfHash->getLength());
372
        for ($i = 0; $i < $count; $i++) {
373
            $c = pack('N', $i);
374
            $t .= $this->mgfHash->hash($mgfSeed.$c);
375
        }
376
377
        return substr($t, 0, $maskLen);
378
    }
379
380
    /**
381
     * RSAES-OAEP-ENCRYPT.
382
     *
383
     * @param string $m
384
     * @param string $l
385
     *
386
     * @return string
387
     */
388
    private function _rsaes_oaep_encrypt($m, $l = '')
389
    {
390
        $mLen = strlen($m);
391
392
        // Length checking
393
394
        // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
395
        // be output.
396
397
        if ($mLen > $this->k - 2 * $this->hash->getLength() - 2) {
398
399
            return false;
400
        }
401
402
        // EME-OAEP encoding
403
404
        $lHash = $this->hash->hash($l);
405
        $ps = str_repeat(chr(0), $this->k - $mLen - 2 * $this->hash->getLength() - 2);
406
        $db = $lHash.$ps.chr(1).$m;
407
        $seed = random_bytes($this->hash->getLength());
408
        $dbMask = $this->_mgf1($seed, $this->k - $this->hash->getLength() - 1);
409
        $maskedDB = $db ^ $dbMask;
410
        $seedMask = $this->_mgf1($maskedDB, $this->hash->getLength());
411
        $maskedSeed = $seed ^ $seedMask;
412
        $em = chr(0).$maskedSeed.$maskedDB;
413
414
        // RSA encryption
415
416
        $m = $this->convertOctetStringToInteger($em);
417
        $c = $this->_rsaep($m);
418
        $c = $this->convertIntegerToOctetString($c, $this->k);
0 ignored issues
show
It seems like $c defined by $this->convertIntegerToOctetString($c, $this->k) on line 418 can also be of type false; however, Jose\Util\RSA::convertIntegerToOctetString() does only seem to accept object<Jose\Util\BigInteger>, 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...
419
420
        // Output the ciphertext C
421
422
        return $c;
423
    }
424
425
    /**
426
     * RSAES-OAEP-DECRYPT.
427
     *
428
     * @param string $c
429
     * @param string $l
430
     *
431
     * @return string
432
     */
433
    private function _rsaes_oaep_decrypt($c, $l = '')
434
    {
435
        // Length checking
436
437
        // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
438
        // be output.
439
440
        if (strlen($c) != $this->k || $this->k < 2 * $this->hash->getLength() + 2) {
441
442
            return false;
443
        }
444
445
        // RSA decryption
446
447
        $c = $this->convertOctetStringToInteger($c);
448
        $m = $this->_rsadp($c);
449
        if ($m === false) {
450
451
            return false;
452
        }
453
        $em = $this->convertIntegerToOctetString($m, $this->k);
454
455
        // EME-OAEP decoding
456
457
        $lHash = $this->hash->hash($l);
458
        $maskedSeed = substr($em, 1, $this->hash->getLength());
459
        $maskedDB = substr($em, $this->hash->getLength() + 1);
460
        $seedMask = $this->_mgf1($maskedDB, $this->hash->getLength());
461
        $seed = $maskedSeed ^ $seedMask;
462
        $dbMask = $this->_mgf1($seed, $this->k - $this->hash->getLength() - 1);
463
        $db = $maskedDB ^ $dbMask;
464
        $lHash2 = substr($db, 0, $this->hash->getLength());
465
        $m = substr($db, $this->hash->getLength());
466
        if ($lHash != $lHash2) {
467
468
            return false;
469
        }
470
        $m = ltrim($m, chr(0));
471
        if (ord($m[0]) != 1) {
472
473
            return false;
474
        }
475
476
        // Output the message M
477
478
        return substr($m, 1);
479
    }
480
481
    /**
482
     * EMSA-PSS-ENCODE.
483
     *
484
     * @param string $m
485
     * @param int    $emBits
486
     *
487
     * @return bool
488
     */
489
    private function _emsa_pss_encode($m, $emBits)
490
    {
491
        // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
492
        // be output.
493
494
        $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8)
495
        $sLen = $this->hash->getLength();
496
497
        $mHash = $this->hash->hash($m);
498
        if ($emLen < $this->hash->getLength() + $sLen + 2) {
499
500
            return false;
501
        }
502
503
        $salt = random_bytes($sLen);
504
        $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt;
505
        $h = $this->hash->hash($m2);
506
        $ps = str_repeat(chr(0), $emLen - $sLen - $this->hash->getLength() - 2);
507
        $db = $ps.chr(1).$salt;
508
        $dbMask = $this->_mgf1($h, $emLen - $this->hash->getLength() - 1);
509
        $maskedDB = $db ^ $dbMask;
510
        $maskedDB[0] = ~chr(0xFF << ($emBits & 7)) & $maskedDB[0];
511
        $em = $maskedDB.$h.chr(0xBC);
512
513
        return $em;
514
    }
515
516
    /**
517
     * EMSA-PSS-VERIFY.
518
     *
519
     * @param string $m
520
     * @param string $em
521
     * @param int    $emBits
522
     *
523
     * @return string
524
     */
525
    private function _emsa_pss_verify($m, $em, $emBits)
526
    {
527
        // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
528
        // be output.
529
530
        $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8);
531
        $sLen = $this->hash->getLength();
532
533
        $mHash = $this->hash->hash($m);
534
        if ($emLen < $this->hash->getLength() + $sLen + 2) {
535
            return false;
536
        }
537
538
        if ($em[strlen($em) - 1] != chr(0xBC)) {
539
            return false;
540
        }
541
542
        $maskedDB = substr($em, 0, -$this->hash->getLength() - 1);
543
        $h = substr($em, -$this->hash->getLength() - 1, $this->hash->getLength());
544
        $temp = chr(0xFF << ($emBits & 7));
545
        if ((~$maskedDB[0] & $temp) != $temp) {
546
            return false;
547
        }
548
        $dbMask = $this->_mgf1($h, $emLen - $this->hash->getLength() - 1);
549
        $db = $maskedDB ^ $dbMask;
550
        $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0];
551
        $temp = $emLen - $this->hash->getLength() - $sLen - 2;
552
        if (substr($db, 0, $temp) != str_repeat(chr(0), $temp) || ord($db[$temp]) != 1) {
553
            return false;
554
        }
555
        $salt = substr($db, $temp + 1); // should be $sLen long
556
        $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt;
557
        $h2 = $this->hash->hash($m2);
558
559
        return $this->_equals($h, $h2);
560
    }
561
562
    /**
563
     * RSASSA-PSS-SIGN.
564
     *
565
     * @param string $m
566
     *
567
     * @return string
568
     */
569
    private function _rsassa_pss_sign($m)
570
    {
571
        // EMSA-PSS encoding
572
573
        $em = $this->_emsa_pss_encode($m, 8 * $this->k - 1);
574
575
        // RSA signature
576
577
        $m = $this->convertOctetStringToInteger($em);
0 ignored issues
show
It seems like $em defined by $this->_emsa_pss_encode($m, 8 * $this->k - 1) on line 573 can also be of type false; however, Jose\Util\RSA::convertOctetStringToInteger() 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...
578
        $s = $this->_rsasp1($m);
579
        $s = $this->convertIntegerToOctetString($s, $this->k);
0 ignored issues
show
It seems like $s defined by $this->convertIntegerToOctetString($s, $this->k) on line 579 can also be of type false; however, Jose\Util\RSA::convertIntegerToOctetString() does only seem to accept object<Jose\Util\BigInteger>, 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...
580
581
        // Output the signature S
582
583
        return $s;
584
    }
585
586
    /**
587
     * RSASSA-PSS-VERIFY.
588
     *
589
     * @param string $m
590
     * @param string $s
591
     *
592
     * @return string
593
     */
594
    private function _rsassa_pss_verify($m, $s)
595
    {
596
        // Length checking
597
598
        if (strlen($s) != $this->k) {
599
600
            return false;
601
        }
602
603
        // RSA verification
604
605
        $modBits = 8 * $this->k;
606
607
        $s2 = $this->convertOctetStringToInteger($s);
608
        $m2 = $this->_rsavp1($s2);
609
        if ($m2 === false) {
610
611
            return false;
612
        }
613
        $em = $this->convertIntegerToOctetString($m2, $modBits >> 3);
614
        if ($em === false) {
615
616
            return false;
617
        }
618
619
        // EMSA-PSS verification
620
621
        return $this->_emsa_pss_verify($m, $em, $modBits - 1);
622
    }
623
624
    /**
625
     * Encryption.
626
     *
627
     * Both self::ENCRYPTION_OAEP and self::ENCRYPTION_PKCS1 both place limits on how long $plaintext can be.
628
     * If $plaintext exceeds those limits it will be broken up so that it does and the resultant ciphertext's will
629
     * be concatenated together.
630
     *
631
     * @see self::decrypt()
632
     *
633
     * @param string $plaintext
634
     *
635
     * @return string
636
     */
637
    public function encrypt($plaintext)
638
    {
639
        $length = $this->k - 2 * $this->hash->getLength() - 2;
640
        if ($length <= 0) {
641
            return false;
642
        }
643
644
        $plaintext = str_split($plaintext, $length);
645
        $ciphertext = '';
646
        foreach ($plaintext as $m) {
647
            $ciphertext .= $this->_rsaes_oaep_encrypt($m);
648
        }
649
650
        return $ciphertext;
651
    }
652
653
    /**
654
     * Decryption.
655
     *
656
     * @param string $ciphertext
657
     *
658
     * @return string
659
     */
660
    public function decrypt($ciphertext)
661
    {
662
        if ($this->k <= 0) {
663
            return false;
664
        }
665
666
        $ciphertext = str_split($ciphertext, $this->k);
667
        $ciphertext[count($ciphertext) - 1] = str_pad($ciphertext[count($ciphertext) - 1], $this->k, chr(0), STR_PAD_LEFT);
668
669
        $plaintext = '';
670
671
        foreach ($ciphertext as $c) {
672
            $temp = $this->_rsaes_oaep_decrypt($c);
673
            if ($temp === false) {
674
                return false;
675
            }
676
            $plaintext .= $temp;
677
        }
678
679
        return $plaintext;
680
    }
681
682
    /**
683
     * Create a signature.
684
     *
685
     * @param string $message
686
     *
687
     * @return string
688
     */
689
    public function sign($message)
690
    {
691
        if (empty($this->modulus) || empty($this->exponent)) {
692
            return false;
693
        }
694
695
696
        return $this->_rsassa_pss_sign($message);
697
    }
698
699
    /**
700
     * Verifies a signature.
701
     *
702
     * @param string $message
703
     * @param string $signature
704
     *
705
     * @return bool
706
     */
707
    public function verify($message, $signature)
708
    {
709
        if (empty($this->modulus) || empty($this->exponent)) {
710
            return false;
711
        }
712
713
        return $this->_rsassa_pss_verify($message, $signature);
714
    }
715
}
716