Failed Conditions
Push — master ( d2ad41...5d048e )
by Florent
21:38 queued 15:06
created

src/Component/Signature/Util/RSA.php (4 issues)

Severity

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
declare(strict_types=1);
4
5
/*
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2014-2018 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
/**
21
 * @internal
22
 */
23
class RSA
24
{
25
    /**
26
     * Probabilistic Signature Scheme.
27
     */
28
    public const SIGNATURE_PSS = 1;
29
30
    /**
31
     * Use the PKCS#1.
32
     */
33
    public const SIGNATURE_PKCS1 = 2;
34
35
    /**
36
     * @param BigInteger $x
37
     * @param int        $xLen
38
     *
39
     * @return string
40
     */
41
    private static function convertIntegerToOctetString(BigInteger $x, int $xLen): string
42
    {
43
        $x = $x->toBytes();
44
        if (mb_strlen($x, '8bit') > $xLen) {
45
            throw new \RuntimeException();
46
        }
47
48
        return str_pad($x, $xLen, chr(0), STR_PAD_LEFT);
49
    }
50
51
    /**
52
     * MGF1.
53
     *
54
     * @param string $mgfSeed
55
     * @param int    $maskLen
56
     * @param Hash   $mgfHash
57
     *
58
     * @return string
59
     */
60
    private static function getMGF1(string $mgfSeed, int $maskLen, Hash $mgfHash): string
61
    {
62
        $t = '';
63
        $count = ceil($maskLen / $mgfHash->getLength());
64
        for ($i = 0; $i < $count; $i++) {
65
            $c = pack('N', $i);
66
            $t .= $mgfHash->hash($mgfSeed.$c);
67
        }
68
69
        return mb_substr($t, 0, $maskLen, '8bit');
70
    }
71
72
    /**
73
     * EMSA-PSS-ENCODE.
74
     *
75
     * @param string $message
76
     * @param int    $modulusLength
77
     * @param Hash   $hash
78
     *
79
     * @return string
80
     */
81
    private static function encodeEMSAPSS(string $message, int $modulusLength, Hash $hash): string
82
    {
83
        $emLen = ($modulusLength + 1) >> 3;
84
        $sLen = $hash->getLength();
85
        $mHash = $hash->hash($message);
86
        if ($emLen <= $hash->getLength() + $sLen + 2) {
87
            throw new \RuntimeException();
88
        }
89
        $salt = random_bytes($sLen);
90
        $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt;
91
        $h = $hash->hash($m2);
92
        $ps = str_repeat(chr(0), $emLen - $sLen - $hash->getLength() - 2);
93
        $db = $ps.chr(1).$salt;
94
        $dbMask = self::getMGF1($h, $emLen - $hash->getLength() - 1, $hash);
95
        $maskedDB = $db ^ $dbMask;
96
        $maskedDB[0] = ~chr(0xFF << ($modulusLength & 7)) & $maskedDB[0];
97
        $em = $maskedDB.$h.chr(0xBC);
98
99
        return $em;
100
    }
101
102
    /**
103
     * EMSA-PSS-VERIFY.
104
     *
105
     * @param string $m
106
     * @param string $em
107
     * @param int    $emBits
108
     * @param Hash   $hash
109
     *
110
     * @return bool
111
     */
112
    private static function verifyEMSAPSS(string $m, string $em, int $emBits, Hash $hash): bool
113
    {
114
        $emLen = ($emBits + 1) >> 3;
115
        $sLen = $hash->getLength();
116
        $mHash = $hash->hash($m);
117
        if ($emLen < $hash->getLength() + $sLen + 2) {
118
            throw new \InvalidArgumentException();
119
        }
120
        if ($em[mb_strlen($em, '8bit') - 1] !== chr(0xBC)) {
121
            throw new \InvalidArgumentException();
122
        }
123
        $maskedDB = mb_substr($em, 0, -$hash->getLength() - 1, '8bit');
124
        $h = mb_substr($em, -$hash->getLength() - 1, $hash->getLength(), '8bit');
125
        $temp = chr(0xFF << ($emBits & 7));
126
        if ((~$maskedDB[0] & $temp) !== $temp) {
127
            throw new \InvalidArgumentException();
128
        }
129
        $dbMask = self::getMGF1($h, $emLen - $hash->getLength() - 1, $hash/*MGF*/);
130
        $db = $maskedDB ^ $dbMask;
131
        $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0];
132
        $temp = $emLen - $hash->getLength() - $sLen - 2;
133
        if (mb_substr($db, 0, $temp, '8bit') !== str_repeat(chr(0), $temp)) {
134
            throw new \InvalidArgumentException();
135
        }
136
        if (1 !== ord($db[$temp])) {
137
            throw new \InvalidArgumentException();
138
        }
139
        $salt = mb_substr($db, $temp + 1, null, '8bit'); // should be $sLen long
140
        $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt;
141
        $h2 = $hash->hash($m2);
142
143
        return hash_equals($h, $h2);
144
    }
145
146
    /**
147
     * @param string $m
148
     * @param int    $emBits
149
     * @param Hash   $hash
150
     *
151
     * @return string
152
     */
153
    private static function encodeEMSA15(string $m, int $emBits, Hash $hash): string
154
    {
155
        $h = $hash->hash($m);
156
        switch ($hash->name()) {
157
            case 'sha256':
158
                $t = "\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20";
159
160
                break;
161
            case 'sha384':
162
                $t = "\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30";
163
164
                break;
165
            case 'sha512':
166
                $t = "\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40";
167
168
                break;
169
            default:
170
                throw new \InvalidArgumentException();
171
        }
172
        $t .= $h;
173
        $tLen = mb_strlen($t, '8bit');
174
        if ($emBits < $tLen + 11) {
175
            throw new \RuntimeException();
176
        }
177
        $ps = str_repeat(chr(0xFF), $emBits - $tLen - 3);
178
        $em2 = "\0\1$ps\0$t";
179
180
        return $em2;
181
    }
182
183
    /**
184
     * @param RSAKey $key
185
     * @param string $message
186
     * @param string $hash
187
     * @param int    $mode
188
     *
189
     * @return string
190
     */
191
    public static function sign(RSAKey $key, string $message, string $hash, int $mode): string
192
    {
193
        switch ($mode) {
194
            case self::SIGNATURE_PSS:
195
                return self::signWithPSS($key, $message, $hash);
196
            case self::SIGNATURE_PKCS1:
197
                return self::signWithPKCS15($key, $message, $hash);
198
            default:
199
                throw new \InvalidArgumentException('Unsupported mode.');
200
        }
201
    }
202
203
    /**
204
     * Create a signature.
205
     *
206
     * @param RSAKey $key
207
     * @param string $message
208
     * @param string $hash
209
     *
210
     * @return string
211
     */
212
    public static function signWithPSS(RSAKey $key, string $message, string $hash): string
213
    {
214
        $em = self::encodeEMSAPSS($message, 8 * $key->getModulusLength() - 1, Hash::$hash());
215
        $message = BigInteger::createFromBinaryString($em);
216
        $signature = RSAKey::exponentiate($key, $message);
0 ignored issues
show
$key is of type object<Jose\Component\Core\Util\RSAKey>, but the function expects a object<self>.

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...
217
218
        return self::convertIntegerToOctetString($signature, $key->getModulusLength());
219
    }
220
221
    /**
222
     * Create a signature.
223
     *
224
     * @param RSAKey $key
225
     * @param string $message
226
     * @param string $hash
227
     *
228
     * @return string
229
     */
230
    public static function signWithPKCS15(RSAKey $key, string $message, string $hash): string
231
    {
232
        $em = self::encodeEMSA15($message, $key->getModulusLength(), Hash::$hash());
233
        $message = BigInteger::createFromBinaryString($em);
234
        $signature = RSAKey::exponentiate($key, $message);
0 ignored issues
show
$key is of type object<Jose\Component\Core\Util\RSAKey>, but the function expects a object<self>.

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...
235
236
        return self::convertIntegerToOctetString($signature, $key->getModulusLength());
237
    }
238
239
    /**
240
     * @param RSAKey $key
241
     * @param string $message
242
     * @param string $signature
243
     * @param string $hash
244
     * @param int    $mode
245
     *
246
     * @return bool
247
     */
248
    public static function verify(RSAKey $key, string $message, string $signature, string $hash, int $mode): bool
249
    {
250
        switch ($mode) {
251
            case self::SIGNATURE_PSS:
252
                return self::verifyWithPSS($key, $message, $signature, $hash);
253
            case self::SIGNATURE_PKCS1:
254
                return self::verifyWithPKCS15($key, $message, $signature, $hash);
255
            default:
256
                throw new \InvalidArgumentException('Unsupported mode.');
257
        }
258
    }
259
260
    /**
261
     * Verifies a signature.
262
     *
263
     * @param RSAKey $key
264
     * @param string $message
265
     * @param string $signature
266
     * @param string $hash
267
     *
268
     * @return bool
269
     */
270
    public static function verifyWithPSS(RSAKey $key, string $message, string $signature, string $hash): bool
271
    {
272
        if (mb_strlen($signature, '8bit') !== $key->getModulusLength()) {
273
            throw new \InvalidArgumentException();
274
        }
275
        $s2 = BigInteger::createFromBinaryString($signature);
276
        $m2 = RSAKey::exponentiate($key, $s2);
0 ignored issues
show
$key is of type object<Jose\Component\Core\Util\RSAKey>, but the function expects a object<self>.

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...
277
        $em = self::convertIntegerToOctetString($m2, $key->getModulusLength());
278
        $modBits = 8 * $key->getModulusLength();
279
280
        return self::verifyEMSAPSS($message, $em, $modBits - 1, Hash::$hash());
281
    }
282
283
    /**
284
     * Verifies a signature.
285
     *
286
     * @param RSAKey $key
287
     * @param string $message
288
     * @param string $signature
289
     * @param string $hash
290
     *
291
     * @return bool
292
     */
293
    public static function verifyWithPKCS15(RSAKey $key, string $message, string $signature, string $hash): bool
294
    {
295
        if (mb_strlen($signature, '8bit') !== $key->getModulusLength()) {
296
            throw new \InvalidArgumentException();
297
        }
298
        $signature = BigInteger::createFromBinaryString($signature);
299
        $m2 = RSAKey::exponentiate($key, $signature);
0 ignored issues
show
$key is of type object<Jose\Component\Core\Util\RSAKey>, but the function expects a object<self>.

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...
300
        $em = self::convertIntegerToOctetString($m2, $key->getModulusLength());
301
302
        return hash_equals($em, self::encodeEMSA15($message, $key->getModulusLength(), Hash::$hash()));
303
    }
304
}
305