Failed Conditions
Push — v7 ( 61ffea...f7e5f1 )
by Florent
04:20
created

RSAKey::initPrivateKey()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 32
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 32
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 27
nc 8
nop 0
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\Core\Util;
15
16
use Assert\Assertion;
17
use Base64Url\Base64Url;
18
use FG\ASN1\Universal\BitString;
19
use FG\ASN1\Universal\Integer;
20
use FG\ASN1\Universal\NullObject;
21
use FG\ASN1\Universal\ObjectIdentifier;
22
use FG\ASN1\Universal\OctetString;
23
use FG\ASN1\Universal\Sequence;
24
use Jose\Component\Core\JWK;
25
26
final class RSAKey
27
{
28
    /**
29
     * @var Sequence
30
     */
31
    private $sequence;
32
33
    /**
34
     * @var array
35
     */
36
    private $values = [];
37
38
    /**
39
     * @var BigInteger
40
     */
41
    private $modulus;
42
43
    /**
44
     * @var int
45
     */
46
    private $modulus_length;
47
48
    /**
49
     * @var BigInteger
50
     */
51
    private $public_exponent;
52
53
    /**
54
     * @var BigInteger|null
55
     */
56
    private $private_exponent = null;
57
58
    /**
59
     * @var BigInteger[]
60
     */
61
    private $primes = [];
62
63
    /**
64
     * @var BigInteger[]
65
     */
66
    private $exponents = [];
67
68
    /**
69
     * @var BigInteger|null
70
     */
71
    private $coefficient = null;
72
73
    /**
74
     * @param JWK $data
75
     */
76
    private function __construct(JWK $data)
77
    {
78
        $this->sequence = new Sequence();
79
        $this->loadJWK($data->all());
80
        $this->populateBigIntegers();
81
    }
82
83
    /**
84
     * @param JWK $jwk
85
     *
86
     * @return RSAKey
87
     */
88
    public static function createFromJWK(JWK $jwk): RSAKey
89
    {
90
        return new self($jwk);
91
    }
92
93
    /**
94
     * @return BigInteger
95
     */
96
    public function getModulus(): BigInteger
97
    {
98
        return $this->modulus;
99
    }
100
101
    /**
102
     * @return bool
103
     */
104
    public function isPrivate(): bool
105
    {
106
        return array_key_exists('d', $this->values);
107
    }
108
109
    /**
110
     * @return string
111
     */
112
    public function toPEM(): string
113
    {
114
        $result = '-----BEGIN '.($this->isPrivate() ? 'RSA PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL;
115
        $result .= chunk_split(base64_encode($this->sequence->getBinary()), 64, PHP_EOL);
116
        $result .= '-----END '.($this->isPrivate() ? 'RSA PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL;
117
118
        return $result;
119
    }
120
121
    /**
122
     * @param array $jwk
123
     */
124
    private function loadJWK(array $jwk)
125
    {
126
        Assertion::keyExists($jwk, 'kty', 'The key parameter "kty" is missing.');
127
        Assertion::eq($jwk['kty'], 'RSA', 'The JWK is not a RSA key');
128
129
        $this->values = $jwk;
130
        if (array_key_exists('d', $jwk)) {
131
            $this->populateCRT();
132
            $this->initPrivateKey();
133
        } else {
134
            $this->initPublicKey();
135
        }
136
    }
137
138
    /**
139
     * This method adds Chinese Remainder Theorem (CRT) parameters if primes 'p' and 'q' are available.
140
     */
141
    private function populateCRT()
142
    {
143
        if (!array_key_exists('p', $this->values) && !array_key_exists('q', $this->values)) {
144
            $d = BigInteger::createFromBinaryString(Base64Url::decode($this->values['d']));
145
            $e = BigInteger::createFromBinaryString(Base64Url::decode($this->values['e']));
146
            $n = BigInteger::createFromBinaryString(Base64Url::decode($this->values['n']));
147
148
            list($p, $q) = $this->findPrimeFactors($d, $e, $n);
149
            $this->values['p'] = Base64Url::encode($p->toBytes());
150
            $this->values['q'] = Base64Url::encode($q->toBytes());
151
        }
152
153
        if (array_key_exists('dp', $this->values) && array_key_exists('dq', $this->values) && array_key_exists('qi', $this->values)) {
154
            return;
155
        }
156
157
        $one = BigInteger::createFromDecimal(1);
158
        $d = BigInteger::createFromBinaryString(Base64Url::decode($this->values['d']));
159
        $p = BigInteger::createFromBinaryString(Base64Url::decode($this->values['p']));
160
        $q = BigInteger::createFromBinaryString(Base64Url::decode($this->values['q']));
161
162
        $this->values['dp'] = Base64Url::encode($d->mod($p->subtract($one))->toBytes());
163
        $this->values['dq'] = Base64Url::encode($d->mod($q->subtract($one))->toBytes());
164
        $this->values['qi'] = Base64Url::encode($q->modInverse($p)->toBytes());
165
    }
166
167
    /**
168
     * @throws \Exception
169
     */
170
    private function initPublicKey()
171
    {
172
        $oid_sequence = new Sequence();
173
        $oid_sequence->addChild(new ObjectIdentifier('1.2.840.113549.1.1.1'));
174
        $oid_sequence->addChild(new NullObject());
175
        $this->sequence->addChild($oid_sequence);
176
177
        $n = new Integer($this->fromBase64ToInteger($this->values['n']));
178
        $e = new Integer($this->fromBase64ToInteger($this->values['e']));
179
180
        $key_sequence = new Sequence();
181
        $key_sequence->addChild($n);
182
        $key_sequence->addChild($e);
183
        $key_bit_string = new BitString(bin2hex($key_sequence->getBinary()));
184
        $this->sequence->addChild($key_bit_string);
185
    }
186
187
    private function initPrivateKey()
188
    {
189
        $this->sequence->addChild(new Integer(0));
190
191
        $oid_sequence = new Sequence();
192
        $oid_sequence->addChild(new ObjectIdentifier('1.2.840.113549.1.1.1'));
193
        $oid_sequence->addChild(new NullObject());
194
        $this->sequence->addChild($oid_sequence);
195
196
        $v = new Integer(0);
197
        $n = new Integer($this->fromBase64ToInteger($this->values['n']));
198
        $e = new Integer($this->fromBase64ToInteger($this->values['e']));
199
        $d = new Integer($this->fromBase64ToInteger($this->values['d']));
200
        $p = new Integer($this->fromBase64ToInteger($this->values['p']));
201
        $q = new Integer($this->fromBase64ToInteger($this->values['q']));
202
        $dp = array_key_exists('dp', $this->values) ? new Integer($this->fromBase64ToInteger($this->values['dp'])) : new Integer(0);
203
        $dq = array_key_exists('dq', $this->values) ? new Integer($this->fromBase64ToInteger($this->values['dq'])) : new Integer(0);
204
        $qi = array_key_exists('qi', $this->values) ? new Integer($this->fromBase64ToInteger($this->values['qi'])) : new Integer(0);
205
206
        $key_sequence = new Sequence();
207
        $key_sequence->addChild($v);
208
        $key_sequence->addChild($n);
209
        $key_sequence->addChild($e);
210
        $key_sequence->addChild($d);
211
        $key_sequence->addChild($p);
212
        $key_sequence->addChild($q);
213
        $key_sequence->addChild($dp);
214
        $key_sequence->addChild($dq);
215
        $key_sequence->addChild($qi);
216
        $key_octet_string = new OctetString(bin2hex($key_sequence->getBinary()));
217
        $this->sequence->addChild($key_octet_string);
218
    }
219
220
    /**
221
     * @param string $value
222
     *
223
     * @return string
224
     */
225
    private function fromBase64ToInteger(string $value): string
226
    {
227
        return gmp_strval(gmp_init(current(unpack('H*', Base64Url::decode($value))), 16), 10);
228
    }
229
230
    private function populateBigIntegers()
231
    {
232
        $this->modulus = $this->convertBase64StringToBigInteger($this->values['n']);
233
        $this->modulus_length = mb_strlen($this->getModulus()->toBytes(), '8bit');
234
        $this->public_exponent = $this->convertBase64StringToBigInteger($this->values['e']);
235
236
        if (true === $this->isPrivate()) {
237
            $this->private_exponent = $this->convertBase64StringToBigInteger($this->values['d']);
238
239
            if (array_key_exists('p', $this->values) && array_key_exists('q', $this->values)) {
240
                $this->primes = [
241
                    $this->convertBase64StringToBigInteger($this->values['p']),
242
                    $this->convertBase64StringToBigInteger($this->values['q']),
243
                ];
244
                $this->exponents = [
245
                    $this->convertBase64StringToBigInteger($this->values['dp']),
246
                    $this->convertBase64StringToBigInteger($this->values['dq']),
247
                ];
248
                $this->coefficient = $this->convertBase64StringToBigInteger($this->values['qi']);
249
            }
250
        }
251
    }
252
253
    /**
254
     * @param string $value
255
     *
256
     * @return BigInteger
257
     */
258
    private function convertBase64StringToBigInteger(string $value): BigInteger
259
    {
260
        return BigInteger::createFromBinaryString(Base64Url::decode($value));
261
    }
262
263
    /**
264
     * @param BigInteger $d
265
     * @param BigInteger $e
266
     * @param BigInteger $n
267
     *
268
     * @return BigInteger[]
269
     */
270
    private function findPrimeFactors(BigInteger $d, BigInteger $e, BigInteger $n): array
271
    {
272
        $zero = BigInteger::createFromDecimal(0);
273
        $one = BigInteger::createFromDecimal(1);
274
        $two = BigInteger::createFromDecimal(2);
275
276
        $k = $d->multiply($e)->subtract($one);
277
278
        if ($k->isEven()) {
279
            $r = $k;
280
            $t = $zero;
281
282
            do {
283
                $r = $r->divide($two);
284
                $t = $t->add($one);
285
            } while ($r->isEven());
286
287
            $found = false;
288
            $y = null;
289
290
            for ($i = 1; $i <= 100; ++$i) {
291
                $g = BigInteger::random($n->subtract($one));
292
                $y = $g->modPow($r, $n);
293
294
                if ($y->equals($one) || $y->equals($n->subtract($one))) {
295
                    continue;
296
                }
297
298
                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...
299
                    $x = $y->modPow($two, $n);
300
301
                    if ($x->equals($one)) {
302
                        $found = true;
303
                        break;
304
                    }
305
306
                    if ($x->equals($n->subtract($one))) {
307
                        continue;
308
                    }
309
310
                    $y = $x;
311
                }
312
313
                $x = $y->modPow($two, $n);
314
                if ($x->equals($one)) {
315
                    $found = true;
316
                    break;
317
                }
318
            }
319
320
            if (true === $found) {
321
                $p = $y->subtract($one)->gcd($n);
322
                $q = $n->divide($p);
323
324
                return [$p, $q];
325
            }
326
        }
327
328
        throw new \InvalidArgumentException('Unable to find prime factors.');
329
    }
330
}
331