Failed Conditions
Push — PHPSecLib_Rid ( fca9fd...3a3eb8 )
by Florent
03:04
created

src/Util/RSA.php (11 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 Assert\Assertion;
15
use Jose\KeyConverter\RSAKey;
16
17
final class RSA
18
{
19
    /**
20
     * Integer-to-Octet-String primitive.
21
     *
22
     * @param \Jose\Util\BigInteger $x
23
     * @param int                   $xLen
24
     *
25
     * @return string
26
     */
27
    private static function convertIntegerToOctetString($x, $xLen)
28
    {
29
        $x = $x->toBytes();
30
        if (strlen($x) > $xLen) {
31
            return false;
32
        }
33
34
        return str_pad($x, $xLen, chr(0), STR_PAD_LEFT);
35
    }
36
37
    /**
38
     * Octet-String-to-Integer primitive.
39
     *
40
     * @param string $x
41
     *
42
     * @return \Jose\Util\BigInteger
43
     */
44
    private static function convertOctetStringToInteger($x)
45
    {
46
        return BigInteger::createFromBinaryString($x);
47
    }
48
49
    /**
50
     * Exponentiate with or without Chinese Remainder Theorem.
51
     *
52
     * @param \Jose\KeyConverter\RSAKey $key
53
     * @param \Jose\Util\BigInteger     $x
54
     *
55
     * @return \Jose\Util\BigInteger
56
     */
57
    private static function _exponentiate(RSAKey $key, $x)
58
    {
59
        return $x->modPow($key->getExponent(), $key->getModulus());
0 ignored issues
show
Bug Compatibility introduced by
The expression $x->modPow($key->getExpo...), $key->getModulus()); of type Jose\Util\BigInteger|boolean adds the type boolean to the return on line 59 which is incompatible with the return type documented by Jose\Util\RSA::_exponentiate of type Jose\Util\BigInteger.
Loading history...
60
    }
61
62
    /**
63
     * RSAEP.
64
     *
65
     * @param \Jose\KeyConverter\RSAKey $key
66
     * @param \Jose\Util\BigInteger     $m
67
     *
68
     * @return \Jose\Util\BigInteger|false
69
     */
70
    private static function _rsaep(RSAKey $key, BigInteger $m)
71
    {
72
        if ($m->compare(BigInteger::createFromDecimalString('0')) < 0 || $m->compare($key->getModulus()) > 0) {
73
            return false;
74
        }
75
76
        return self::_exponentiate($key, $m);
77
    }
78
79
    /**
80
     * RSADP.
81
     *
82
     * @param \Jose\KeyConverter\RSAKey $key
83
     * @param \Jose\Util\BigInteger     $c
84
     *
85
     * @return \Jose\Util\BigInteger|false
86
     */
87
    private static function _rsadp(RSAKey $key, BigInteger $c)
88
    {
89
        if ($c->compare(BigInteger::createFromDecimalString('0')) < 0 || $c->compare($key->getModulus()) > 0) {
90
            return false;
91
        }
92
93
        return self::_exponentiate($key, $c);
94
    }
95
96
    /**
97
     * RSASP1.
98
     *
99
     * @param \Jose\KeyConverter\RSAKey $key
100
     * @param \Jose\Util\BigInteger     $m
101
     *
102
     * @return \Jose\Util\BigInteger|false
103
     */
104
    private static function _rsasp1(RSAKey $key, BigInteger $m)
105
    {
106
        if ($m->compare(BigInteger::createFromDecimalString('0')) < 0 || $m->compare($key->getModulus()) > 0) {
107
            return false;
108
        }
109
110
        return self::_exponentiate($key, $m);
111
    }
112
113
    /**
114
     * RSAVP1.
115
     *
116
     * @param \Jose\KeyConverter\RSAKey $key
117
     * @param \Jose\Util\BigInteger     $s
118
     *
119
     * @return \Jose\Util\BigInteger|false
120
     */
121
    private static function _rsavp1(RSAKey $key, BigInteger $s)
122
    {
123
        if ($s->compare(BigInteger::createFromDecimalString('0')) < 0 || $s->compare($key->getModulus()) > 0) {
124
            return false;
125
        }
126
127
        return self::_exponentiate($key, $s);
128
    }
129
130
    /**
131
     * MGF1.
132
     *
133
     * @param string          $mgfSeed
134
     * @param int             $maskLen
135
     * @param \Jose\Util\Hash $mgfHash
136
     *
137
     * @return string
138
     */
139
    private static function _mgf1($mgfSeed, $maskLen, Hash $mgfHash)
140
    {
141
        $t = '';
142
        $count = ceil($maskLen / $mgfHash->getLength());
143
        for ($i = 0; $i < $count; $i++) {
144
            $c = pack('N', $i);
145
            $t .= $mgfHash->hash($mgfSeed.$c);
146
        }
147
148
        return substr($t, 0, $maskLen);
149
    }
150
151
    /**
152
     * RSAES-OAEP-ENCRYPT.
153
     *
154
     * @param \Jose\KeyConverter\RSAKey $key
155
     * @param string                    $m
156
     * @param \Jose\Util\Hash           $hash
157
     *
158
     * @return string
159
     */
160
    private static function _rsaes_oaep_encrypt(RSAKey $key, $m, Hash $hash)
161
    {
162
        $mLen = strlen($m);
163
        $lHash = $hash->hash('');
164
        $ps = str_repeat(chr(0), $key->getModulusLength() - $mLen - 2 * $hash->getLength() - 2);
165
        $db = $lHash.$ps.chr(1).$m;
166
        $seed = random_bytes($hash->getLength());
167
        $dbMask = self::_mgf1($seed, $key->getModulusLength() - $hash->getLength() - 1, $hash/*MGF*/);
168
        $maskedDB = $db ^ $dbMask;
169
        $seedMask = self::_mgf1($maskedDB, $hash->getLength(), $hash/*MGF*/);
170
        $maskedSeed = $seed ^ $seedMask;
171
        $em = chr(0).$maskedSeed.$maskedDB;
172
173
        $m = self::convertOctetStringToInteger($em);
174
        $c = self::_rsaep($key, $m);
175
        $c = self::convertIntegerToOctetString($c, $key->getModulusLength());
0 ignored issues
show
It seems like $c defined by self::convertIntegerToOc...ey->getModulusLength()) on line 175 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...
176
177
        return $c;
178
    }
179
180
    /**
181
     * RSAES-OAEP-DECRYPT.
182
     *
183
     * @param \Jose\KeyConverter\RSAKey $key
184
     * @param string                    $c
185
     * @param \Jose\Util\Hash           $hash
186
     *
187
     * @return string
188
     */
189
    private static function _rsaes_oaep_decrypt(RSAKey $key, $c, Hash $hash)
190
    {
191
        $c = self::convertOctetStringToInteger($c);
192
        $m = self::_rsadp($key, $c);
193
194
        Assertion::isInstanceOf($m, BigInteger::class);
195
196
        $em = self::convertIntegerToOctetString($m, $key->getModulusLength());
0 ignored issues
show
It seems like $m defined by self::_rsadp($key, $c) on line 192 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...
197
198
        $lHash = $hash->hash('');
199
        $maskedSeed = substr($em, 1, $hash->getLength());
200
        $maskedDB = substr($em, $hash->getLength() + 1);
201
        $seedMask = self::_mgf1($maskedDB, $hash->getLength(), $hash/*MGF*/);
202
        $seed = $maskedSeed ^ $seedMask;
203
        $dbMask = self::_mgf1($seed, $key->getModulusLength() - $hash->getLength() - 1, $hash/*MGF*/);
204
        $db = $maskedDB ^ $dbMask;
205
        $lHash2 = substr($db, 0, $hash->getLength());
206
        $m = substr($db, $hash->getLength());
207
208
        Assertion::eq($lHash, $lHash2);
209
210
        $m = ltrim($m, chr(0));
211
212
        Assertion::eq(ord($m[0]), 1);
213
214
        return substr($m, 1);
215
    }
216
217
    /**
218
     * EMSA-PSS-ENCODE.
219
     *
220
     * @param string          $m
221
     * @param int             $emBits
222
     * @param \Jose\Util\Hash $hash
223
     *
224
     * @return string|bool
225
     */
226
    private static function _emsa_pss_encode($m, $emBits, Hash $hash)
227
    {
228
        // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
229
        // be output.
230
231
        $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8)
232
        $sLen = $hash->getLength();
233
234
        $mHash = $hash->hash($m);
235
        if ($emLen < $hash->getLength() + $sLen + 2) {
236
            return false;
237
        }
238
239
        $salt = random_bytes($sLen);
240
        $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt;
241
        $h = $hash->hash($m2);
242
        $ps = str_repeat(chr(0), $emLen - $sLen - $hash->getLength() - 2);
243
        $db = $ps.chr(1).$salt;
244
        $dbMask = self::_mgf1($h, $emLen - $hash->getLength() - 1, $hash/*MGF*/);
245
        $maskedDB = $db ^ $dbMask;
246
        $maskedDB[0] = ~chr(0xFF << ($emBits & 7)) & $maskedDB[0];
247
        $em = $maskedDB.$h.chr(0xBC);
248
249
        return $em;
250
    }
251
252
    /**
253
     * EMSA-PSS-VERIFY.
254
     *
255
     * @param string                    $m
256
     * @param string                    $em
257
     * @param int                       $emBits
258
     * @param \Jose\Util\Hash           $hash
259
     *
260
     * @return string
261
     */
262
    private static function _emsa_pss_verify($m, $em, $emBits, Hash $hash)
263
    {
264
        // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
265
        // be output.
266
267
        $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8);
268
        $sLen = $hash->getLength();
269
270
        $mHash = $hash->hash($m);
271
        if ($emLen < $hash->getLength() + $sLen + 2) {
272
            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::_emsa_pss_verify of type string.

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...
273
        }
274
275
        if ($em[strlen($em) - 1] != chr(0xBC)) {
276
            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::_emsa_pss_verify of type string.

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...
277
        }
278
279
        $maskedDB = substr($em, 0, -$hash->getLength() - 1);
280
        $h = substr($em, -$hash->getLength() - 1, $hash->getLength());
281
        $temp = chr(0xFF << ($emBits & 7));
282
        if ((~$maskedDB[0] & $temp) != $temp) {
283
            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::_emsa_pss_verify of type string.

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...
284
        }
285
        $dbMask = self::_mgf1($h, $emLen - $hash->getLength() - 1, $hash/*MGF*/);
286
        $db = $maskedDB ^ $dbMask;
287
        $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0];
288
        $temp = $emLen - $hash->getLength() - $sLen - 2;
289
        if (substr($db, 0, $temp) != str_repeat(chr(0), $temp) || ord($db[$temp]) != 1) {
290
            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::_emsa_pss_verify of type string.

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...
291
        }
292
        $salt = substr($db, $temp + 1); // should be $sLen long
293
        $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt;
294
        $h2 = $hash->hash($m2);
295
296
        return hash_equals($h, $h2);
297
    }
298
299
    /**
300
     * Encryption.
301
     *
302
     * @param \Jose\KeyConverter\RSAKey $key
303
     * @param string                    $plaintext
304
     * @param string                    $hash_algorithm
305
     *
306
     * @return string
307
     */
308
    public static function encrypt(RSAKey $key, $plaintext, $hash_algorithm)
309
    {
310
        $hash = Hash::$hash_algorithm();
311
        $length = $key->getModulusLength() - 2 * $hash->getLength() - 2;
312
313
        Assertion::greaterThan($length, 0);
314
315
        $plaintext = str_split($plaintext, $length);
316
        $ciphertext = '';
317
        foreach ($plaintext as $m) {
318
            $ciphertext .= self::_rsaes_oaep_encrypt($key, $m, $hash);
319
        }
320
321
        return $ciphertext;
322
    }
323
324
    /**
325
     * Decryption.
326
     *
327
     * @param \Jose\KeyConverter\RSAKey $key
328
     * @param string                    $ciphertext
329
     * @param string                    $hash_algorithm
330
     *
331
     * @return string
332
     */
333
    public static function decrypt(RSAKey $key, $ciphertext, $hash_algorithm)
334
    {
335
        Assertion::greaterThan($key->getModulusLength(), 0);
336
337
        $hash = Hash::$hash_algorithm();
338
339
        $ciphertext = str_split($ciphertext, $key->getModulusLength());
340
        $ciphertext[count($ciphertext) - 1] = str_pad($ciphertext[count($ciphertext) - 1], $key->getModulusLength(), chr(0), STR_PAD_LEFT);
341
342
        $plaintext = '';
343
344
        foreach ($ciphertext as $c) {
345
            $temp = self::_rsaes_oaep_decrypt($key, $c, $hash);
346
            if ($temp === false) {
347
                return false;
348
            }
349
            $plaintext .= $temp;
350
        }
351
352
        return $plaintext;
353
    }
354
355
    /**
356
     * Create a signature.
357
     *
358
     * @param \Jose\KeyConverter\RSAKey $key
359
     * @param string                    $message
360
     * @param string                    $hash
361
     *
362
     * @return string
363
     */
364
    public static function sign(RSAKey $key, $message, $hash)
365
    {
366
        Assertion::string($message);
367
        Assertion::string($hash);
368
        Assertion::inArray($hash, ['sha256', 'sha384', 'sha512']);
369
370
        $em = self::_emsa_pss_encode($message, 8 * $key->getModulusLength() - 1, Hash::$hash());
371
372
        Assertion::string($em);
373
374
        $message = self::convertOctetStringToInteger($em);
0 ignored issues
show
It seems like $em defined by self::_emsa_pss_encode($...ose\Util\Hash::$hash()) on line 370 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...
375
        $signature = self::_rsasp1($key, $message);
376
377
        Assertion::isInstanceOf($signature, BigInteger::class);
378
379
        $signature = self::convertIntegerToOctetString($signature, $key->getModulusLength());
0 ignored issues
show
It seems like $signature defined by self::convertIntegerToOc...ey->getModulusLength()) on line 379 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...
380
381
        return $signature;
382
    }
383
384
    /**
385
     * Verifies a signature.
386
     *
387
     * @param \Jose\KeyConverter\RSAKey $key
388
     * @param string                    $message
389
     * @param string                    $signature
390
     * @param string                    $hash
391
     *
392
     * @return bool
393
     */
394
    public static function verify(RSAKey $key, $message, $signature, $hash)
395
    {
396
        Assertion::string($message);
397
        Assertion::string($signature);
398
        Assertion::string($hash);
399
        Assertion::inArray($hash, ['sha256', 'sha384', 'sha512']);
400
        Assertion::eq(strlen($signature), $key->getModulusLength());
401
402
        $modBits = 8 * $key->getModulusLength();
403
404
        $s2 = self::convertOctetStringToInteger($signature);
405
        $m2 = self::_rsavp1($key, $s2);
406
407
        Assertion::isInstanceOf($m2, BigInteger::class);
408
409
        $em = self::convertIntegerToOctetString($m2, $modBits >> 3);
0 ignored issues
show
It seems like $m2 defined by self::_rsavp1($key, $s2) on line 405 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...
410
411
        return self::_emsa_pss_verify($message, $em, $modBits - 1, Hash::$hash());
0 ignored issues
show
It seems like $em defined by self::convertIntegerToOc...ing($m2, $modBits >> 3) on line 409 can also be of type false; however, Jose\Util\RSA::_emsa_pss_verify() 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...
412
    }
413
}
414