Failed Conditions
Push — master ( 7fd11e...04be58 )
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 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
     * Operation with primes 'p' and 'q' is appox. 2x faster.
52
     *
53
     * @param \Jose\KeyConverter\RSAKey $key
54
     * @param \Jose\Util\BigInteger     $c
55
     *
56
     * @return \Jose\Util\BigInteger
57
     */
58
    private static function exponentiate(RSAKey $key, BigInteger $c)
59
    {
60
        if ($key->isPublic() || empty($key->getPrimes())) {
61
            return $c->modPow($key->getExponent(), $key->getModulus());
62
        }
63
64
        $p = $key->getPrimes()[0];
65
        $q = $key->getPrimes()[1];
66
        $dP = $key->getExponents()[0];
67
        $dQ = $key->getExponents()[1];
68
        $qInv = $key->getCoefficient();
69
70
        $m1 = $c->modPow($dP, $p);
71
        $m2 = $c->modPow($dQ, $q);
72
        $h = $qInv->multiply($m1->subtract($m2)->add($p))->mod($p);
73
        $m = $m2->add($h->multiply($q));
74
75
        return $m;
76
    }
77
78
    /**
79
     * RSA EP.
80
     *
81
     * @param \Jose\KeyConverter\RSAKey $key
82
     * @param \Jose\Util\BigInteger     $m
83
     *
84
     * @return \Jose\Util\BigInteger|false
85
     */
86
    private static function getRSAEP(RSAKey $key, BigInteger $m)
87
    {
88
        if ($m->compare(BigInteger::createFromDecimal(0)) < 0 || $m->compare($key->getModulus()) > 0) {
89
            return false;
90
        }
91
92
        return self::exponentiate($key, $m);
93
    }
94
95
    /**
96
     * RSA DP.
97
     *
98
     * @param \Jose\KeyConverter\RSAKey $key
99
     * @param \Jose\Util\BigInteger     $c
100
     *
101
     * @return \Jose\Util\BigInteger|false
102
     */
103
    private static function getRSADP(RSAKey $key, BigInteger $c)
104
    {
105
        if ($c->compare(BigInteger::createFromDecimal(0)) < 0 || $c->compare($key->getModulus()) > 0) {
106
            return false;
107
        }
108
109
        return self::exponentiate($key, $c);
110
    }
111
112
    /**
113
     * RSA SP1.
114
     *
115
     * @param \Jose\KeyConverter\RSAKey $key
116
     * @param \Jose\Util\BigInteger     $m
117
     *
118
     * @return \Jose\Util\BigInteger|false
119
     */
120
    private static function getRSASP1(RSAKey $key, BigInteger $m)
121
    {
122
        if ($m->compare(BigInteger::createFromDecimal(0)) < 0 || $m->compare($key->getModulus()) > 0) {
123
            return false;
124
        }
125
126
        return self::exponentiate($key, $m);
127
    }
128
129
    /**
130
     * RSAVP1.
131
     *
132
     * @param \Jose\KeyConverter\RSAKey $key
133
     * @param \Jose\Util\BigInteger     $s
134
     *
135
     * @return \Jose\Util\BigInteger|false
136
     */
137
    private static function getRSAVP1(RSAKey $key, BigInteger $s)
138
    {
139
        if ($s->compare(BigInteger::createFromDecimal(0)) < 0 || $s->compare($key->getModulus()) > 0) {
140
            return false;
141
        }
142
143
        return self::exponentiate($key, $s);
144
    }
145
146
    /**
147
     * MGF1.
148
     *
149
     * @param string          $mgfSeed
150
     * @param int             $maskLen
151
     * @param \Jose\Util\Hash $mgfHash
152
     *
153
     * @return string
154
     */
155
    private static function getMGF1($mgfSeed, $maskLen, Hash $mgfHash)
156
    {
157
        $t = '';
158
        $count = ceil($maskLen / $mgfHash->getLength());
159
        for ($i = 0; $i < $count; $i++) {
160
            $c = pack('N', $i);
161
            $t .= $mgfHash->hash($mgfSeed.$c);
162
        }
163
164
        return mb_substr($t, 0, $maskLen, '8bit');
165
    }
166
167
    /**
168
     * RSAES-OAEP-ENCRYPT.
169
     *
170
     * @param \Jose\KeyConverter\RSAKey $key
171
     * @param string                    $m
172
     * @param \Jose\Util\Hash           $hash
173
     *
174
     * @return string
175
     */
176
    private static function encryptRSAESOAEP(RSAKey $key, $m, Hash $hash)
177
    {
178
        $mLen = mb_strlen($m, '8bit');
179
        $lHash = $hash->hash('');
180
        $ps = str_repeat(chr(0), $key->getModulusLength() - $mLen - 2 * $hash->getLength() - 2);
181
        $db = $lHash.$ps.chr(1).$m;
182
        $seed = random_bytes($hash->getLength());
183
        $dbMask = self::getMGF1($seed, $key->getModulusLength() - $hash->getLength() - 1, $hash/*MGF*/);
184
        $maskedDB = $db ^ $dbMask;
185
        $seedMask = self::getMGF1($maskedDB, $hash->getLength(), $hash/*MGF*/);
186
        $maskedSeed = $seed ^ $seedMask;
187
        $em = chr(0).$maskedSeed.$maskedDB;
188
189
        $m = self::convertOctetStringToInteger($em);
190
        $c = self::getRSAEP($key, $m);
191
        $c = self::convertIntegerToOctetString($c, $key->getModulusLength());
192
193
        return $c;
194
    }
195
196
    /**
197
     * RSAES-OAEP-DECRYPT.
198
     *
199
     * @param \Jose\KeyConverter\RSAKey $key
200
     * @param string                    $c
201
     * @param \Jose\Util\Hash           $hash
202
     *
203
     * @return string
204
     */
205
    private static function getRSAESOAEP(RSAKey $key, $c, Hash $hash)
206
    {
207
        $c = self::convertOctetStringToInteger($c);
208
        $m = self::getRSADP($key, $c);
209
        Assertion::isInstanceOf($m, BigInteger::class);
210
        $em = self::convertIntegerToOctetString($m, $key->getModulusLength());
0 ignored issues
show
It seems like $m defined by self::getRSADP($key, $c) on line 208 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...
211
        $lHash = $hash->hash('');
212
        $maskedSeed = mb_substr($em, 1, $hash->getLength(), '8bit');
213
        $maskedDB = mb_substr($em, $hash->getLength() + 1, null, '8bit');
214
        $seedMask = self::getMGF1($maskedDB, $hash->getLength(), $hash/*MGF*/);
215
        $seed = $maskedSeed ^ $seedMask;
216
        $dbMask = self::getMGF1($seed, $key->getModulusLength() - $hash->getLength() - 1, $hash/*MGF*/);
217
        $db = $maskedDB ^ $dbMask;
218
        $lHash2 = mb_substr($db, 0, $hash->getLength(), '8bit');
219
        $m = mb_substr($db, $hash->getLength(), null, '8bit');
220
        Assertion::eq($lHash, $lHash2);
221
        $m = ltrim($m, chr(0));
222
        Assertion::eq(ord($m[0]), 1);
223
224
        return mb_substr($m, 1, null, '8bit');
225
    }
226
227
    /**
228
     * EMSA-PSS-ENCODE.
229
     *
230
     * @param string          $m
231
     * @param int             $emBits
232
     * @param \Jose\Util\Hash $hash
233
     *
234
     * @return string|bool
235
     */
236
    private static function encodeEMSAPSS($m, $emBits, Hash $hash)
237
    {
238
        $emLen = ($emBits + 1) >> 3;
239
        $sLen = $hash->getLength();
240
        $mHash = $hash->hash($m);
241
        Assertion::greaterThan($emLen, $hash->getLength() + $sLen + 2);
242
        $salt = random_bytes($sLen);
243
        $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt;
244
        $h = $hash->hash($m2);
245
        $ps = str_repeat(chr(0), $emLen - $sLen - $hash->getLength() - 2);
246
        $db = $ps.chr(1).$salt;
247
        $dbMask = self::getMGF1($h, $emLen - $hash->getLength() - 1, $hash/*MGF*/);
248
        $maskedDB = $db ^ $dbMask;
249
        $maskedDB[0] = ~chr(0xFF << ($emBits & 7)) & $maskedDB[0];
250
        $em = $maskedDB.$h.chr(0xBC);
251
252
        return $em;
253
    }
254
255
    /**
256
     * EMSA-PSS-VERIFY.
257
     *
258
     * @param string          $m
259
     * @param string          $em
260
     * @param int             $emBits
261
     * @param \Jose\Util\Hash $hash
262
     *
263
     * @return string
264
     */
265
    private static function verifyEMSAPSS($m, $em, $emBits, Hash $hash)
266
    {
267
        $emLen = ($emBits + 1) >> 3;
268
        $sLen = $hash->getLength();
269
        $mHash = $hash->hash($m);
270
        Assertion::greaterThan($emLen, $hash->getLength() + $sLen + 2);
271
        Assertion::eq($em[mb_strlen($em, '8bit') - 1], chr(0xBC));
272
        $maskedDB = mb_substr($em, 0, -$hash->getLength() - 1, '8bit');
273
        $h = mb_substr($em, -$hash->getLength() - 1, $hash->getLength(), '8bit');
274
        $temp = chr(0xFF << ($emBits & 7));
275
        Assertion::eq(~$maskedDB[0] & $temp, $temp);
276
        $dbMask = self::getMGF1($h, $emLen - $hash->getLength() - 1, $hash/*MGF*/);
277
        $db = $maskedDB ^ $dbMask;
278
        $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0];
279
        $temp = $emLen - $hash->getLength() - $sLen - 2;
280
        Assertion::eq(mb_substr($db, 0, $temp, '8bit'), str_repeat(chr(0), $temp));
281
        Assertion::eq(ord($db[$temp]), 1);
282
        $salt = mb_substr($db, $temp + 1, null, '8bit'); // should be $sLen long
283
        $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt;
284
        $h2 = $hash->hash($m2);
285
286
        return hash_equals($h, $h2);
287
    }
288
289
    /**
290
     * Encryption.
291
     *
292
     * @param \Jose\KeyConverter\RSAKey $key
293
     * @param string                    $plaintext
294
     * @param string                    $hash_algorithm
295
     *
296
     * @return string
297
     */
298
    public static function encrypt(RSAKey $key, $plaintext, $hash_algorithm)
299
    {
300
        /**
301
         * @var Hash
302
         */
303
        $hash = Hash::$hash_algorithm();
304
        $length = $key->getModulusLength() - 2 * $hash->getLength() - 2;
305
        Assertion::greaterThan($length, 0);
306
        $plaintext = str_split($plaintext, $length);
307
        $ciphertext = '';
308
        foreach ($plaintext as $m) {
309
            $ciphertext .= self::encryptRSAESOAEP($key, $m, $hash);
310
        }
311
312
        return $ciphertext;
313
    }
314
315
    /**
316
     * Decryption.
317
     *
318
     * @param \Jose\KeyConverter\RSAKey $key
319
     * @param string                    $ciphertext
320
     * @param string                    $hash_algorithm
321
     *
322
     * @return string
323
     */
324
    public static function decrypt(RSAKey $key, $ciphertext, $hash_algorithm)
325
    {
326
        Assertion::greaterThan($key->getModulusLength(), 0);
327
        $hash = Hash::$hash_algorithm();
328
        $ciphertext = str_split($ciphertext, $key->getModulusLength());
329
        $ciphertext[count($ciphertext) - 1] = str_pad($ciphertext[count($ciphertext) - 1], $key->getModulusLength(), chr(0), STR_PAD_LEFT);
330
        $plaintext = '';
331
        foreach ($ciphertext as $c) {
332
            $temp = self::getRSAESOAEP($key, $c, $hash);
333
            $plaintext .= $temp;
334
        }
335
336
        return $plaintext;
337
    }
338
339
    /**
340
     * Create a signature.
341
     *
342
     * @param \Jose\KeyConverter\RSAKey $key
343
     * @param string                    $message
344
     * @param string                    $hash
345
     *
346
     * @return string
347
     */
348
    public static function sign(RSAKey $key, $message, $hash)
349
    {
350
        Assertion::string($message);
351
        Assertion::string($hash);
352
        Assertion::inArray($hash, ['sha256', 'sha384', 'sha512']);
353
        $em = self::encodeEMSAPSS($message, 8 * $key->getModulusLength() - 1, Hash::$hash());
354
        Assertion::string($em);
355
        $message = self::convertOctetStringToInteger($em);
356
        $signature = self::getRSASP1($key, $message);
357
        Assertion::isInstanceOf($signature, BigInteger::class);
358
359
        return self::convertIntegerToOctetString($signature, $key->getModulusLength());
0 ignored issues
show
It seems like $signature defined by self::getRSASP1($key, $message) on line 356 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...
360
    }
361
362
    /**
363
     * Verifies a signature.
364
     *
365
     * @param \Jose\KeyConverter\RSAKey $key
366
     * @param string                    $message
367
     * @param string                    $signature
368
     * @param string                    $hash
369
     *
370
     * @return bool
371
     */
372
    public static function verify(RSAKey $key, $message, $signature, $hash)
373
    {
374
        Assertion::string($message);
375
        Assertion::string($signature);
376
        Assertion::string($hash);
377
        Assertion::inArray($hash, ['sha256', 'sha384', 'sha512']);
378
        Assertion::eq(strlen($signature), $key->getModulusLength());
379
        $modBits = 8 * $key->getModulusLength();
380
        $s2 = self::convertOctetStringToInteger($signature);
381
        $m2 = self::getRSAVP1($key, $s2);
382
        Assertion::isInstanceOf($m2, BigInteger::class);
383
        $em = self::convertIntegerToOctetString($m2, $modBits >> 3);
0 ignored issues
show
It seems like $m2 defined by self::getRSAVP1($key, $s2) on line 381 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...
384
385
        return self::verifyEMSAPSS($message, $em, $modBits - 1, Hash::$hash());
386
    }
387
}
388