Failed Conditions
Push — master ( 28d61e...98c33a )
by Florent
03:29
created

Component/KeyManagement/KeyConverter/RSAKey.php (2 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
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\KeyManagement\KeyConverter;
15
16
use Base64Url\Base64Url;
17
use Jose\Component\Core\JWK;
18
use Jose\Component\Core\Util\BigInteger;
19
20
/**
21
 * @internal
22
 */
23
class RSAKey
24
{
25
    /**
26
     * @var array
27
     */
28
    private $values = [];
29
30
    /**
31
     * RSAKey constructor.
32
     *
33
     * @param array $data
34
     */
35
    private function __construct(array $data)
36
    {
37
        $this->loadJWK($data);
38
    }
39
40
    /**
41
     * @param string $pem
42
     *
43
     * @return RSAKey
44
     */
45
    public static function createFromPEM(string $pem): self
46
    {
47
        $data = self::loadPEM($pem);
48
49
        return new self($data);
50
    }
51
52
    /**
53
     * @param JWK $jwk
54
     *
55
     * @return RSAKey
56
     */
57
    public static function createFromJWK(JWK $jwk): self
58
    {
59
        return new self($jwk->all());
60
    }
61
62
    /**
63
     * @param string $data
64
     *
65
     * @return array
66
     */
67
    private static function loadPEM(string $data): array
68
    {
69
        $res = openssl_pkey_get_private($data);
70
        if (false === $res) {
71
            $res = openssl_pkey_get_public($data);
72
        }
73
        if (false === $res) {
74
            throw new \InvalidArgumentException('Unable to load the key.');
75
        }
76
77
        $details = openssl_pkey_get_details($res);
78
        if (!array_key_exists('rsa', $details)) {
79
            throw new \InvalidArgumentException('Unable to load the key.');
80
        }
81
82
        $values = ['kty' => 'RSA'];
83
        $keys = [
84
            'n'  => 'n',
85
            'e'  => 'e',
86
            'd'  => 'd',
87
            'p'  => 'p',
88
            'q'  => 'q',
89
            'dp' => 'dmp1',
90
            'dq' => 'dmq1',
91
            'qi' => 'iqmp',
92
        ];
93
        foreach ($details['rsa'] as $key => $value) {
94
            if (in_array($key, $keys)) {
95
                $value = Base64Url::encode($value);
96
                $values[array_search($key, $keys)] = $value;
97
            }
98
        }
99
100
        return $values;
101
    }
102
103
    /**
104
     * @return bool
105
     */
106
    public function isPublic(): bool
107
    {
108
        return !array_key_exists('d', $this->values);
109
    }
110
111
    /**
112
     * @param RSAKey $private
113
     *
114
     * @return RSAKey
115
     */
116
    public static function toPublic(self $private): self
117
    {
118
        $data = $private->toArray();
119
        $keys = ['p', 'd', 'q', 'dp', 'dq', 'qi'];
120
        foreach ($keys as $key) {
121
            if (array_key_exists($key, $data)) {
122
                unset($data[$key]);
123
            }
124
        }
125
126
        return new self($data);
127
    }
128
129
    /**
130
     * @return array
131
     */
132
    public function toArray(): array
133
    {
134
        return $this->values;
135
    }
136
137
    /**
138
     * @param array $jwk
139
     */
140
    private function loadJWK(array $jwk)
141
    {
142
        if (!array_key_exists('kty', $jwk)) {
143
            throw new \InvalidArgumentException('The key parameter "kty" is missing.');
144
        }
145
        if ('RSA' !== $jwk['kty']) {
146
            throw new \InvalidArgumentException('The JWK is not a RSA key.');
147
        }
148
149
        $this->values = $jwk;
150
    }
151
152
    /**
153
     * @return JWK
154
     */
155
    public function toJwk(): JWK
156
    {
157
        return JWK::create($this->values);
158
    }
159
160
    /**
161
     * This method will try to add Chinese Remainder Theorem (CRT) parameters.
162
     * With those primes, the decryption process is really fast.
163
     */
164
    public function optimize()
165
    {
166
        if (array_key_exists('d', $this->values)) {
167
            $this->populateCRT();
168
        }
169
    }
170
171
    /**
172
     * This method adds Chinese Remainder Theorem (CRT) parameters if primes 'p' and 'q' are available.
173
     */
174
    private function populateCRT()
175
    {
176
        if (!array_key_exists('p', $this->values) && !array_key_exists('q', $this->values)) {
177
            $d = BigInteger::createFromBinaryString(Base64Url::decode($this->values['d']));
178
            $e = BigInteger::createFromBinaryString(Base64Url::decode($this->values['e']));
179
            $n = BigInteger::createFromBinaryString(Base64Url::decode($this->values['n']));
180
181
            list($p, $q) = $this->findPrimeFactors($d, $e, $n);
182
            $this->values['p'] = Base64Url::encode($p->toBytes());
183
            $this->values['q'] = Base64Url::encode($q->toBytes());
184
        }
185
186
        if (array_key_exists('dp', $this->values) && array_key_exists('dq', $this->values) && array_key_exists('qi', $this->values)) {
187
            return;
188
        }
189
190
        $one = BigInteger::createFromDecimal(1);
191
        $d = BigInteger::createFromBinaryString(Base64Url::decode($this->values['d']));
192
        $p = BigInteger::createFromBinaryString(Base64Url::decode($this->values['p']));
193
        $q = BigInteger::createFromBinaryString(Base64Url::decode($this->values['q']));
194
195
        $this->values['dp'] = Base64Url::encode($d->mod($p->subtract($one))->toBytes());
196
        $this->values['dq'] = Base64Url::encode($d->mod($q->subtract($one))->toBytes());
197
        $this->values['qi'] = Base64Url::encode($q->modInverse($p)->toBytes());
198
    }
199
200
    /**
201
     * @param BigInteger $d
202
     * @param BigInteger $e
203
     * @param BigInteger $n
204
     *
205
     * @return BigInteger[]
206
     */
207
    private function findPrimeFactors(BigInteger $d, BigInteger $e, BigInteger $n): array
208
    {
209
        $zero = BigInteger::createFromDecimal(0);
210
        $one = BigInteger::createFromDecimal(1);
211
        $two = BigInteger::createFromDecimal(2);
212
213
        $k = $d->multiply($e)->subtract($one);
0 ignored issues
show
$e is of type object<Jose\Component\Core\Util\BigInteger>, 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...
214
215
        if ($k->isEven()) {
216
            $r = $k;
217
            $t = $zero;
218
219
            do {
220
                $r = $r->divide($two);
221
                $t = $t->add($one);
222
            } while ($r->isEven());
223
224
            $found = false;
225
            $y = null;
226
227
            for ($i = 1; $i <= 100; $i++) {
228
                $g = BigInteger::random($n->subtract($one));
229
                $y = $g->modPow($r, $n);
230
231
                if ($y->equals($one) || $y->equals($n->subtract($one))) {
232
                    continue;
233
                }
234
235
                for ($j = $one; $j->lowerThan($t->subtract($one)); $j = $j->add($one)) {
0 ignored issues
show
Performance Best Practice introduced by
Consider avoiding function calls on each iteration of the for loop.

If you have a function call in the test part of a for loop, this function is executed on each iteration. Often such a function, can be moved to the initialization part and be cached.

// count() is called on each iteration
for ($i=0; $i < count($collection); $i++) { }

// count() is only called once
for ($i=0, $c=count($collection); $i<$c; $i++) { }
Loading history...
236
                    $x = $y->modPow($two, $n);
237
238
                    if ($x->equals($one)) {
239
                        $found = true;
240
241
                        break;
242
                    }
243
244
                    if ($x->equals($n->subtract($one))) {
245
                        continue;
246
                    }
247
248
                    $y = $x;
249
                }
250
251
                $x = $y->modPow($two, $n);
252
                if ($x->equals($one)) {
253
                    $found = true;
254
255
                    break;
256
                }
257
            }
258
259
            if (true === $found) {
260
                $p = $y->subtract($one)->gcd($n);
261
                $q = $n->divide($p);
262
263
                return [$p, $q];
264
            }
265
        }
266
267
        throw new \InvalidArgumentException('Unable to find prime factors.');
268
    }
269
}
270