RSAKey::optimize()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2014-2019 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 InvalidArgumentException;
18
use Jose\Component\Core\JWK;
19
use Jose\Component\Core\Util\BigInteger;
20
use RuntimeException;
21
22
/**
23
 * @internal
24
 */
25
class RSAKey
26
{
27
    /**
28
     * @var array
29
     */
30
    private $values = [];
31
32
    /**
33
     * RSAKey constructor.
34
     */
35
    private function __construct(array $data)
36
    {
37
        $this->loadJWK($data);
38
    }
39
40
    /**
41
     * @return RSAKey
42
     */
43
    public static function createFromKeyDetails(array $details): self
44
    {
45
        $values = ['kty' => 'RSA'];
46
        $keys = [
47
            'n' => 'n',
48
            'e' => 'e',
49
            'd' => 'd',
50
            'p' => 'p',
51
            'q' => 'q',
52
            'dp' => 'dmp1',
53
            'dq' => 'dmq1',
54
            'qi' => 'iqmp',
55
        ];
56
        foreach ($details as $key => $value) {
57
            if (\in_array($key, $keys, true)) {
58
                $value = Base64Url::encode($value);
59
                $values[array_search($key, $keys, true)] = $value;
60
            }
61
        }
62
63
        return new self($values);
64
    }
65
66
    /**
67
     * @throws RuntimeException         if the extension OpenSSL is not available
68
     * @throws InvalidArgumentException if the key cannot be loaded
69
     *
70
     * @return RSAKey
71
     */
72
    public static function createFromPEM(string $pem): self
73
    {
74
        if (!\extension_loaded('openssl')) {
75
            throw new RuntimeException('Please install the OpenSSL extension');
76
        }
77
        $res = openssl_pkey_get_private($pem);
78
        if (false === $res) {
79
            $res = openssl_pkey_get_public($pem);
80
        }
81
        if (false === $res) {
82
            throw new InvalidArgumentException('Unable to load the key.');
83
        }
84
85
        $details = openssl_pkey_get_details($res);
86
        openssl_free_key($res);
87
        if (!\is_array($details) || !isset($details['rsa'])) {
88
            throw new InvalidArgumentException('Unable to load the key.');
89
        }
90
91
        return self::createFromKeyDetails($details['rsa']);
92
    }
93
94
    /**
95
     * @return RSAKey
96
     */
97
    public static function createFromJWK(JWK $jwk): self
98
    {
99
        return new self($jwk->all());
100
    }
101
102
    public function isPublic(): bool
103
    {
104
        return !\array_key_exists('d', $this->values);
105
    }
106
107
    /**
108
     * @param RSAKey $private
109
     *
110
     * @return RSAKey
111
     */
112
    public static function toPublic(self $private): self
113
    {
114
        $data = $private->toArray();
115
        $keys = ['p', 'd', 'q', 'dp', 'dq', 'qi'];
116
        foreach ($keys as $key) {
117
            if (\array_key_exists($key, $data)) {
118
                unset($data[$key]);
119
            }
120
        }
121
122
        return new self($data);
123
    }
124
125
    public function toArray(): array
126
    {
127
        return $this->values;
128
    }
129
130
    public function toJwk(): JWK
131
    {
132
        return new JWK($this->values);
133
    }
134
135
    /**
136
     * This method will try to add Chinese Remainder Theorem (CRT) parameters.
137
     * With those primes, the decryption process is really fast.
138
     */
139
    public function optimize(): void
140
    {
141
        if (\array_key_exists('d', $this->values)) {
142
            $this->populateCRT();
143
        }
144
    }
145
146
    /**
147
     * @throws InvalidArgumentException if the key is invalid or not an RSA key
148
     */
149
    private function loadJWK(array $jwk): void
150
    {
151
        if (!\array_key_exists('kty', $jwk)) {
152
            throw new InvalidArgumentException('The key parameter "kty" is missing.');
153
        }
154
        if ('RSA' !== $jwk['kty']) {
155
            throw new InvalidArgumentException('The JWK is not a RSA key.');
156
        }
157
158
        $this->values = $jwk;
159
    }
160
161
    /**
162
     * This method adds Chinese Remainder Theorem (CRT) parameters if primes 'p' and 'q' are available.
163
     * If 'p' and 'q' are missing, they are computed and added to the key data.
164
     */
165
    private function populateCRT(): void
166
    {
167
        if (!\array_key_exists('p', $this->values) && !\array_key_exists('q', $this->values)) {
168
            $d = BigInteger::createFromBinaryString(Base64Url::decode($this->values['d']));
169
            $e = BigInteger::createFromBinaryString(Base64Url::decode($this->values['e']));
170
            $n = BigInteger::createFromBinaryString(Base64Url::decode($this->values['n']));
171
172
            list($p, $q) = $this->findPrimeFactors($d, $e, $n);
173
            $this->values['p'] = Base64Url::encode($p->toBytes());
174
            $this->values['q'] = Base64Url::encode($q->toBytes());
175
        }
176
177
        if (\array_key_exists('dp', $this->values) && \array_key_exists('dq', $this->values) && \array_key_exists('qi', $this->values)) {
178
            return;
179
        }
180
181
        $one = BigInteger::createFromDecimal(1);
182
        $d = BigInteger::createFromBinaryString(Base64Url::decode($this->values['d']));
183
        $p = BigInteger::createFromBinaryString(Base64Url::decode($this->values['p']));
184
        $q = BigInteger::createFromBinaryString(Base64Url::decode($this->values['q']));
185
186
        $this->values['dp'] = Base64Url::encode($d->mod($p->subtract($one))->toBytes());
187
        $this->values['dq'] = Base64Url::encode($d->mod($q->subtract($one))->toBytes());
188
        $this->values['qi'] = Base64Url::encode($q->modInverse($p)->toBytes());
189
    }
190
191
    /**
192
     * @throws RuntimeException if the prime factors cannot be found
193
     *
194
     * @return BigInteger[]
195
     */
196
    private function findPrimeFactors(BigInteger $d, BigInteger $e, BigInteger $n): array
197
    {
198
        $zero = BigInteger::createFromDecimal(0);
199
        $one = BigInteger::createFromDecimal(1);
200
        $two = BigInteger::createFromDecimal(2);
201
202
        $k = $d->multiply($e)->subtract($one);
0 ignored issues
show
Documentation introduced by
$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...
203
204
        if ($k->isEven()) {
205
            $r = $k;
206
            $t = $zero;
207
208
            do {
209
                $r = $r->divide($two);
210
                $t = $t->add($one);
211
            } while ($r->isEven());
212
213
            $found = false;
214
            $y = null;
215
216
            for ($i = 1; $i <= 100; ++$i) {
217
                $g = BigInteger::random($n->subtract($one));
218
                $y = $g->modPow($r, $n);
219
220
                if ($y->equals($one) || $y->equals($n->subtract($one))) {
221
                    continue;
222
                }
223
224
                for ($j = $one; $j->lowerThan($t->subtract($one)); $j = $j->add($one)) {
225
                    $x = $y->modPow($two, $n);
226
227
                    if ($x->equals($one)) {
228
                        $found = true;
229
230
                        break;
231
                    }
232
233
                    if ($x->equals($n->subtract($one))) {
234
                        continue;
235
                    }
236
237
                    $y = $x;
238
                }
239
240
                $x = $y->modPow($two, $n);
241
                if ($x->equals($one)) {
242
                    $found = true;
243
244
                    break;
245
                }
246
            }
247
            if (null === $y) {
248
                throw new InvalidArgumentException('Unable to find prime factors.');
249
            }
250
            if (true === $found) {
251
                $p = $y->subtract($one)->gcd($n);
252
                $q = $n->divide($p);
253
254
                return [$p, $q];
255
            }
256
        }
257
258
        throw new InvalidArgumentException('Unable to find prime factors.');
259
    }
260
}
261