Failed Conditions
Push — v7 ( 2dbae0...d25f5c )
by Florent
01:59
created

RSA   B

Complexity

Total Complexity 38

Size/Duplication

Total Lines 323
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
wmc 38
lcom 1
cbo 3
dl 0
loc 323
rs 8.3999
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A convertIntegerToOctetString() 0 9 2
B exponentiate() 0 22 5
A getMGF1() 0 11 2
A encodeEMSAPSS() 0 20 2
B verifyEMSAPSS() 0 33 6
B encodeEMSA15() 0 26 5
A sign() 0 11 3
A signWithPSS() 0 11 2
A signWithPKCS15() 0 11 2
A verify() 0 11 3
A verifyWithPSS() 0 15 3
A verifyWithPKCS15() 0 14 3
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2014-2017 Spomky-Labs
9
 *
10
 * This software may be modified and distributed under the terms
11
 * of the MIT license.  See the LICENSE file for details.
12
 */
13
14
namespace Jose\Component\Signature\Util;
15
16
use Jose\Component\Core\Util\BigInteger;
17
use Jose\Component\Core\Util\Hash;
18
use Jose\Component\Core\Util\RSAKey;
19
20
final class RSA
21
{
22
    /**
23
     * Probabilistic Signature Scheme.
24
     */
25
    public const SIGNATURE_PSS = 1;
26
27
    /**
28
     * Use the PKCS#1.
29
     */
30
    public const SIGNATURE_PKCS1 = 2;
31
32
    /**
33
     * @param BigInteger $x
34
     * @param int        $xLen
35
     *
36
     * @return string
37
     */
38
    private static function convertIntegerToOctetString(BigInteger $x, int $xLen): string
39
    {
40
        $x = $x->toBytes();
41
        if (mb_strlen($x, '8bit') > $xLen) {
42
            throw new \RuntimeException();
43
        }
44
45
        return str_pad($x, $xLen, chr(0), STR_PAD_LEFT);
46
    }
47
48
    /**
49
     * Exponentiate with or without Chinese Remainder Theorem.
50
     * Operation with primes 'p' and 'q' is appox. 2x faster.
51
     *
52
     * @param RSAKey     $key
53
     * @param BigInteger $c
54
     *
55
     * @return BigInteger
56
     */
57
    private static function exponentiate(RSAKey $key, BigInteger $c): BigInteger
58
    {
59
        if ($c->compare(BigInteger::createFromDecimal(0)) < 0 || $c->compare($key->getModulus()) > 0) {
60
            throw new \RuntimeException();
61
        }
62
        if ($key->isPublic() || empty($key->getPrimes())) {
63
            return $c->modPow($key->getExponent(), $key->getModulus());
64
        }
65
66
        $p = $key->getPrimes()[0];
67
        $q = $key->getPrimes()[1];
68
        $dP = $key->getExponents()[0];
69
        $dQ = $key->getExponents()[1];
70
        $qInv = $key->getCoefficient();
71
72
        $m1 = $c->modPow($dP, $p);
73
        $m2 = $c->modPow($dQ, $q);
74
        $h = $qInv->multiply($m1->subtract($m2)->add($p))->mod($p);
75
        $m = $m2->add($h->multiply($q));
76
77
        return $m;
78
    }
79
80
    /**
81
     * MGF1.
82
     *
83
     * @param string $mgfSeed
84
     * @param int    $maskLen
85
     * @param Hash   $mgfHash
86
     *
87
     * @return string
88
     */
89
    private static function getMGF1(string $mgfSeed, int $maskLen, Hash $mgfHash): string
90
    {
91
        $t = '';
92
        $count = ceil($maskLen / $mgfHash->getLength());
93
        for ($i = 0; $i < $count; ++$i) {
94
            $c = pack('N', $i);
95
            $t .= $mgfHash->hash($mgfSeed.$c);
96
        }
97
98
        return mb_substr($t, 0, $maskLen, '8bit');
99
    }
100
101
    /**
102
     * EMSA-PSS-ENCODE.
103
     *
104
     * @param string $message
105
     * @param int    $modulusLength
106
     * @param Hash   $hash
107
     *
108
     * @return string
109
     */
110
    private static function encodeEMSAPSS(string $message, int $modulusLength, Hash $hash): string
111
    {
112
        $emLen = ($modulusLength + 1) >> 3;
113
        $sLen = $hash->getLength();
114
        $mHash = $hash->hash($message);
115
        if ($emLen <= $hash->getLength() + $sLen + 2) {
116
            throw new \RuntimeException();
117
        }
118
        $salt = random_bytes($sLen);
119
        $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt;
120
        $h = $hash->hash($m2);
121
        $ps = str_repeat(chr(0), $emLen - $sLen - $hash->getLength() - 2);
122
        $db = $ps.chr(1).$salt;
123
        $dbMask = self::getMGF1($h, $emLen - $hash->getLength() - 1, $hash);
124
        $maskedDB = $db ^ $dbMask;
125
        $maskedDB[0] = ~chr(0xFF << ($modulusLength & 7)) & $maskedDB[0];
126
        $em = $maskedDB.$h.chr(0xBC);
127
128
        return $em;
129
    }
130
131
    /**
132
     * EMSA-PSS-VERIFY.
133
     *
134
     * @param string $m
135
     * @param string $em
136
     * @param int    $emBits
137
     * @param Hash   $hash
138
     *
139
     * @return bool
140
     */
141
    private static function verifyEMSAPSS(string $m, string $em, int $emBits, Hash $hash): bool
142
    {
143
        $emLen = ($emBits + 1) >> 3;
144
        $sLen = $hash->getLength();
145
        $mHash = $hash->hash($m);
146
        if ($emLen < $hash->getLength() + $sLen + 2) {
147
            throw new \InvalidArgumentException();
148
        }
149
        if ($em[mb_strlen($em, '8bit') - 1] !== chr(0xBC)) {
150
            throw new \InvalidArgumentException();
151
        }
152
        $maskedDB = mb_substr($em, 0, -$hash->getLength() - 1, '8bit');
153
        $h = mb_substr($em, -$hash->getLength() - 1, $hash->getLength(), '8bit');
154
        $temp = chr(0xFF << ($emBits & 7));
155
        if ((~$maskedDB[0] & $temp) !== $temp) {
156
            throw new \InvalidArgumentException();
157
        }
158
        $dbMask = self::getMGF1($h, $emLen - $hash->getLength() - 1, $hash/*MGF*/);
159
        $db = $maskedDB ^ $dbMask;
160
        $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0];
161
        $temp = $emLen - $hash->getLength() - $sLen - 2;
162
        if (mb_substr($db, 0, $temp, '8bit') !== str_repeat(chr(0), $temp)) {
163
            throw new \InvalidArgumentException();
164
        }
165
        if (1 !== ord($db[$temp])) {
166
            throw new \InvalidArgumentException();
167
        }
168
        $salt = mb_substr($db, $temp + 1, null, '8bit'); // should be $sLen long
169
        $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt;
170
        $h2 = $hash->hash($m2);
171
172
        return hash_equals($h, $h2);
173
    }
174
175
    /**
176
     * @param string $m
177
     * @param int $emBits
178
     * @param Hash $hash
179
     *
180
     * @return string
181
     */
182
    private static function encodeEMSA15(string $m, int $emBits, Hash $hash): string
183
    {
184
        $h = $hash->hash($m);
185
        switch ($hash->name()) {
186
            case 'sha256':
187
                $t = "\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20";
188
                break;
189
            case 'sha384':
190
                $t = "\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30";
191
                break;
192
            case 'sha512':
193
                $t = "\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40";
194
                break;
195
            default:
196
                throw new \InvalidArgumentException();
197
        }
198
        $t.= $h;
199
        $tLen = mb_strlen($t, '8bit');
200
        if ($emBits < $tLen + 11) {
201
            throw new \RuntimeException();
202
        }
203
        $ps = str_repeat(chr(0xFF), $emBits - $tLen - 3);
204
        $em2 = "\0\1$ps\0$t";
205
206
        return $em2;
207
    }
208
209
    /**
210
     * @param RSAKey $key
211
     * @param string $message
212
     * @param string $hash
213
     * @param int $mode
214
     *
215
     * @return string
216
     */
217
    public static function sign(RSAKey $key, string $message, string $hash, int $mode): string
218
    {
219
        switch ($mode) {
220
            case self::SIGNATURE_PSS:
221
                return self::signWithPSS($key, $message, $hash);
222
            case self::SIGNATURE_PKCS1:
223
                return self::signWithPKCS15($key, $message, $hash);
224
            default:
225
                throw new \InvalidArgumentException('Unsupported mode.');
226
        }
227
    }
228
229
    /**
230
     * Create a signature.
231
     *
232
     * @param RSAKey $key
233
     * @param string $message
234
     * @param string $hash
235
     *
236
     * @return string
237
     */
238
    public static function signWithPSS(RSAKey $key, string $message, string $hash): string
239
    {
240
        if (!in_array($hash, ['sha256', 'sha384', 'sha512'])) {
241
            throw new \InvalidArgumentException();
242
        }
243
        $em = self::encodeEMSAPSS($message, 8 * $key->getModulusLength() - 1, Hash::$hash());
244
        $message = BigInteger::createFromBinaryString($em);
245
        $signature = self::exponentiate($key, $message);
246
247
        return self::convertIntegerToOctetString($signature, $key->getModulusLength());
248
    }
249
250
    /**
251
     * Create a signature.
252
     *
253
     * @param RSAKey $key
254
     * @param string $message
255
     * @param string $hash
256
     *
257
     * @return string
258
     */
259
    public static function signWithPKCS15(RSAKey $key, string $message, string $hash): string
260
    {
261
        if (!in_array($hash, ['sha256', 'sha384', 'sha512'])) {
262
            throw new \InvalidArgumentException();
263
        }
264
        $em = self::encodeEMSA15($message, $key->getModulusLength(), Hash::$hash());
265
        $message = BigInteger::createFromBinaryString($em);
266
        $signature = self::exponentiate($key, $message);
267
268
        return self::convertIntegerToOctetString($signature, $key->getModulusLength());
269
    }
270
271
    /**
272
     * @param RSAKey $key
273
     * @param string $message
274
     * @param string $signature
275
     * @param string $hash
276
     * @param int $mode
277
     *
278
     * @return bool
279
     */
280
    public static function verify(RSAKey $key, string $message, string $signature, string $hash, int $mode): bool
281
    {
282
        switch ($mode) {
283
            case self::SIGNATURE_PSS:
284
                return self::verifyWithPSS($key, $message, $signature, $hash);
285
            case self::SIGNATURE_PKCS1:
286
                return self::verifyWithPKCS15($key, $message, $signature, $hash);
287
            default:
288
                throw new \InvalidArgumentException('Unsupported mode.');
289
        }
290
    }
291
292
    /**
293
     * Verifies a signature.
294
     *
295
     * @param RSAKey $key
296
     * @param string $message
297
     * @param string $signature
298
     * @param string $hash
299
     *
300
     * @return bool
301
     */
302
    public static function verifyWithPSS(RSAKey $key, string $message, string $signature, string $hash): bool
303
    {
304
        if (!in_array($hash, ['sha256', 'sha384', 'sha512'])) {
305
            throw new \InvalidArgumentException();
306
        }
307
        if (mb_strlen($signature, '8bit') !== $key->getModulusLength()) {
308
            throw new \InvalidArgumentException();
309
        }
310
        $s2 = BigInteger::createFromBinaryString($signature);
311
        $m2 = self::exponentiate($key, $s2);
312
        $em = self::convertIntegerToOctetString($m2, $key->getModulusLength());
313
        $modBits = 8 * $key->getModulusLength();
314
315
        return self::verifyEMSAPSS($message, $em, $modBits - 1, Hash::$hash());
316
    }
317
318
    /**
319
     * Verifies a signature.
320
     *
321
     * @param RSAKey $key
322
     * @param string $message
323
     * @param string $signature
324
     * @param string $hash
325
     *
326
     * @return bool
327
     */
328
    public static function verifyWithPKCS15(RSAKey $key, string $message, string $signature, string $hash): bool
329
    {
330
        if (!in_array($hash, ['sha256', 'sha384', 'sha512'])) {
331
            throw new \InvalidArgumentException();
332
        }
333
        if (mb_strlen($signature, '8bit') !== $key->getModulusLength()) {
334
            throw new \InvalidArgumentException();
335
        }
336
        $signature = BigInteger::createFromBinaryString($signature);
337
        $m2 = self::exponentiate($key, $signature);
338
        $em = self::convertIntegerToOctetString($m2, $key->getModulusLength());
339
340
        return hash_equals($em, self::encodeEMSA15($message, $key->getModulusLength(), Hash::$hash()));
341
    }
342
}
343