Failed Conditions
Pull Request — master (#159)
by
unknown
07:54
created

RSAKey::calculatePAndQ()   D

Complexity

Conditions 10
Paths 12

Size

Total Lines 96
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 96
rs 4.9494
c 0
b 0
f 0
cc 10
eloc 42
nc 12
nop 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * The MIT License (MIT)
5
 *
6
 * Copyright (c) 2014-2016 Spomky-Labs
7
 *
8
 * This software may be modified and distributed under the terms
9
 * of the MIT license.  See the LICENSE file for details.
10
 */
11
12
namespace Jose\KeyConverter;
13
14
use Assert\Assertion;
15
use Base64Url\Base64Url;
16
use FG\ASN1\Universal\BitString;
17
use FG\ASN1\Universal\Integer;
18
use FG\ASN1\Universal\NullObject;
19
use FG\ASN1\Universal\ObjectIdentifier;
20
use FG\ASN1\Universal\OctetString;
21
use FG\ASN1\Universal\Sequence;
22
use Jose\Object\JWKInterface;
23
use Jose\Util\BigInteger;
24
25
final class RSAKey extends Sequence
26
{
27
    /**
28
     * @var array
29
     */
30
    private $values = [];
31
32
    /**
33
     * @var \Jose\Util\BigInteger
34
     */
35
    private $modulus;
36
37
    /**
38
     * @var int
39
     */
40
    private $modulus_length;
41
42
    /**
43
     * @var \Jose\Util\BigInteger
44
     */
45
    private $public_exponent;
46
47
    /**
48
     * @var \Jose\Util\BigInteger|null
49
     */
50
    private $private_exponent = null;
51
52
    /**
53
     * @var \Jose\Util\BigInteger[]
54
     */
55
    private $primes = [];
56
57
    /**
58
     * @var \Jose\Util\BigInteger[]
59
     */
60
    private $exponents = [];
61
62
    /**
63
     * @var \Jose\Util\BigInteger|null
64
     */
65
    private $coefficient = null;
66
67
    /**
68
     * @param \Jose\Object\JWKInterface|string|array $data
69
     */
70
    public function __construct($data)
71
    {
72
        parent::__construct();
73
74
        if ($data instanceof JWKInterface) {
75
            $this->loadJWK($data->getAll());
76
        } elseif (is_array($data)) {
77
            $this->loadJWK($data);
78
        } elseif (is_string($data)) {
79
            $this->loadPEM($data);
80
        } else {
81
            throw new \InvalidArgumentException('Unsupported input');
82
        }
83
84
        $this->populateBigIntegers();
85
    }
86
87
    /**
88
     * @return bool
89
     */
90
    public function isPublic()
91
    {
92
        return !$this->isPrivate();
93
    }
94
95
    /**
96
     * @return bool
97
     */
98
    public function isPrivate()
99
    {
100
        return array_key_exists('d', $this->values);
101
    }
102
103
    /**
104
     * @return \Jose\Util\BigInteger
105
     */
106
    public function getModulus()
107
    {
108
        return $this->modulus;
109
    }
110
111
    /**
112
     * @return int
113
     */
114
    public function getModulusLength()
115
    {
116
        return $this->modulus_length;
117
    }
118
119
    /**
120
     * @return \Jose\Util\BigInteger
121
     */
122
    public function getExponent()
123
    {
124
        $d = $this->getPrivateExponent();
125
        if (null !== $d) {
126
            return $d;
127
        }
128
129
        return $this->getPublicExponent();
130
    }
131
132
    /**
133
     * @return \Jose\Util\BigInteger
134
     */
135
    public function getPublicExponent()
136
    {
137
        return $this->public_exponent;
138
    }
139
140
    /**
141
     * @return \Jose\Util\BigInteger
142
     */
143
    public function getPrivateExponent()
144
    {
145
        return $this->private_exponent;
146
    }
147
148
    /**
149
     * @return \Jose\Util\BigInteger[]
150
     */
151
    public function getPrimes()
152
    {
153
        return $this->primes;
154
    }
155
156
    /**
157
     * @return \Jose\Util\BigInteger[]
158
     */
159
    public function getExponents()
160
    {
161
        return $this->exponents;
162
    }
163
164
    /**
165
     * @return \Jose\Util\BigInteger|null
166
     */
167
    public function getCoefficient()
168
    {
169
        return $this->coefficient;
170
    }
171
172
    /**
173
     * @param \Jose\KeyConverter\RSAKey $private
174
     *
175
     * @return \Jose\KeyConverter\RSAKey
176
     */
177
    public static function toPublic(RSAKey $private)
178
    {
179
        $data = $private->toArray();
180
        $keys = ['p', 'd', 'q', 'dp', 'dq', 'qi'];
181
        foreach ($keys as $key) {
182
            if (array_key_exists($key, $data)) {
183
                unset($data[$key]);
184
            }
185
        }
186
187
        return new self($data);
188
    }
189
190
    public function __toString()
191
    {
192
        return $this->toPEM();
193
    }
194
195
    /**
196
     * @return array
197
     */
198
    public function toArray()
199
    {
200
        return $this->values;
201
    }
202
203
    /**
204
     * @return string
205
     */
206
    public function toDER()
207
    {
208
        return $this->getBinary();
209
    }
210
211
    /**
212
     * @return string
213
     */
214
    public function toPEM()
215
    {
216
        $result = '-----BEGIN '.($this->isPrivate() ? 'RSA PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL;
217
        $result .= chunk_split(base64_encode($this->getBinary()), 64, PHP_EOL);
218
        $result .= '-----END '.($this->isPrivate() ? 'RSA PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL;
219
220
        return $result;
221
    }
222
223
    /**
224
     * @param string $data
225
     *
226
     * @throws \Exception
227
     * @throws \FG\ASN1\Exception\ParserException
228
     *
229
     * @return array
230
     */
231
    private function loadPEM($data)
232
    {
233
        $res = openssl_pkey_get_private($data);
234
        if (false === $res) {
235
            $res = openssl_pkey_get_public($data);
236
        }
237
        Assertion::false(false === $res, 'Unable to load the key');
238
239
        $details = openssl_pkey_get_details($res);
240
        Assertion::keyExists($details, 'rsa', 'Unable to load the key');
241
242
        $this->values['kty'] = 'RSA';
243
        $keys = [
244
            'n'  => 'n',
245
            'e'  => 'e',
246
            'd'  => 'd',
247
            'p'  => 'p',
248
            'q'  => 'q',
249
            'dp' => 'dmp1',
250
            'dq' => 'dmq1',
251
            'qi' => 'iqmp',
252
        ];
253
        foreach ($details['rsa'] as $key => $value) {
254
            if (in_array($key, $keys)) {
255
                $value = Base64Url::encode($value);
256
                $this->values[array_search($key, $keys)] = $value;
257
            }
258
        }
259
    }
260
261
    /**
262
     * @param array $jwk
263
     */
264
    private function loadJWK(array $jwk)
265
    {
266
        Assertion::keyExists($jwk, 'kty', 'The key parameter "kty" is missing.');
267
        Assertion::eq($jwk['kty'], 'RSA', 'The JWK is not a RSA key');
268
269
        $this->values = $jwk;
270
        if (array_key_exists('d', $jwk)) {
271
            $this->populateCRT();
272
            $this->initPrivateKey();
273
        } else {
274
            $this->initPublicKey();
275
        }
276
    }
277
278
    /**
279
     * This method adds Chinese Remainder Theorem (CRT) parameters if primes 'p' and 'q' are available.
280
     */
281
    private function populateCRT()
282
    {
283
        if (!array_key_exists('p', $this->values) || !array_key_exists('q', $this->values)) {
284
            $this->calculatePAndQ();
285
        }
286
287
        if (array_key_exists('dp', $this->values) && array_key_exists('dq', $this->values) && array_key_exists('qi', $this->values)) {
288
            return;
289
        }
290
291
        $one = BigInteger::createFromDecimal(1);
292
        $d = BigInteger::createFromBinaryString(Base64Url::decode($this->values['d']));
293
        $p = BigInteger::createFromBinaryString(Base64Url::decode($this->values['p']));
294
        $q = BigInteger::createFromBinaryString(Base64Url::decode($this->values['q']));
295
296
        $this->values['dp'] = Base64Url::encode($d->mod($p->subtract($one))->toBytes());
297
        $this->values['dq'] = Base64Url::encode($d->mod($q->subtract($one))->toBytes());
298
        $this->values['qi'] = Base64Url::encode($q->modInverse($p)->toBytes());
299
    }
300
301
    /**
302
     * @throws \Exception
303
     */
304
    private function initPublicKey()
305
    {
306
        $oid_sequence = new Sequence();
307
        $oid_sequence->addChild(new ObjectIdentifier('1.2.840.113549.1.1.1'));
308
        $oid_sequence->addChild(new NullObject());
309
        $this->addChild($oid_sequence);
310
311
        $n = new Integer($this->fromBase64ToInteger($this->values['n']));
312
        $e = new Integer($this->fromBase64ToInteger($this->values['e']));
313
314
        $key_sequence = new Sequence();
315
        $key_sequence->addChild($n);
316
        $key_sequence->addChild($e);
317
        $key_bit_string = new BitString(bin2hex($key_sequence->getBinary()));
318
        $this->addChild($key_bit_string);
319
    }
320
321
    private function initPrivateKey()
322
    {
323
        $this->addChild(new Integer(0));
324
325
        $oid_sequence = new Sequence();
326
        $oid_sequence->addChild(new ObjectIdentifier('1.2.840.113549.1.1.1'));
327
        $oid_sequence->addChild(new NullObject());
328
        $this->addChild($oid_sequence);
329
330
        $v = new Integer(0);
331
        $n = new Integer($this->fromBase64ToInteger($this->values['n']));
332
        $e = new Integer($this->fromBase64ToInteger($this->values['e']));
333
        $d = new Integer($this->fromBase64ToInteger($this->values['d']));
334
        $p = new Integer($this->fromBase64ToInteger($this->values['p']));
335
        $q = new Integer($this->fromBase64ToInteger($this->values['q']));
336
        $dp = array_key_exists('dp', $this->values) ? new Integer($this->fromBase64ToInteger($this->values['dp'])) : new Integer(0);
337
        $dq = array_key_exists('dq', $this->values) ? new Integer($this->fromBase64ToInteger($this->values['dq'])) : new Integer(0);
338
        $qi = array_key_exists('qi', $this->values) ? new Integer($this->fromBase64ToInteger($this->values['qi'])) : new Integer(0);
339
340
        $key_sequence = new Sequence();
341
        $key_sequence->addChild($v);
342
        $key_sequence->addChild($n);
343
        $key_sequence->addChild($e);
344
        $key_sequence->addChild($d);
345
        $key_sequence->addChild($p);
346
        $key_sequence->addChild($q);
347
        $key_sequence->addChild($dp);
348
        $key_sequence->addChild($dq);
349
        $key_sequence->addChild($qi);
350
        $key_octet_string = new OctetString(bin2hex($key_sequence->getBinary()));
351
        $this->addChild($key_octet_string);
352
    }
353
354
    /**
355
     * @param string $value
356
     *
357
     * @return string
358
     */
359
    private function fromBase64ToInteger($value)
360
    {
361
        return gmp_strval(gmp_init(current(unpack('H*', Base64Url::decode($value))), 16), 10);
362
    }
363
364
    private function populateBigIntegers()
365
    {
366
        $this->modulus = $this->convertBase64StringToBigInteger($this->values['n']);
367
        $this->modulus_length = mb_strlen($this->getModulus()->toBytes(), '8bit');
368
        $this->public_exponent = $this->convertBase64StringToBigInteger($this->values['e']);
369
370
        if (true === $this->isPrivate()) {
371
            $this->private_exponent = $this->convertBase64StringToBigInteger($this->values['d']);
372
373
            if (array_key_exists('p', $this->values) && array_key_exists('q', $this->values)) {
374
                $this->primes = [
375
                    $this->convertBase64StringToBigInteger($this->values['p']),
376
                    $this->convertBase64StringToBigInteger($this->values['q']),
377
                ];
378
                $this->exponents = [
379
                    $this->convertBase64StringToBigInteger($this->values['dp']),
380
                    $this->convertBase64StringToBigInteger($this->values['dq']),
381
                ];
382
                $this->coefficient = $this->convertBase64StringToBigInteger($this->values['qi']);
383
            }
384
        }
385
    }
386
387
    /**
388
     * @param string $value
389
     *
390
     * @return \Jose\Util\BigInteger
391
     */
392
    private function convertBase64StringToBigInteger($value)
393
    {
394
        return BigInteger::createFromBinaryString(Base64Url::decode($value));
395
    }
396
397
    /**
398
     * Calculates the P and Q values of a private key.
399
     */
400
    private function calculatePAndQ()
401
    {
402
        $zero = BigInteger::createFromDecimal(0);
403
        $one = BigInteger::createFromDecimal(1);
404
        $d = BigInteger::createFromBinaryString(Base64Url::decode($this->values['d']));
405
        $e = BigInteger::createFromBinaryString(Base64Url::decode($this->values['e']));
406
        $n = BigInteger::createFromBinaryString(Base64Url::decode($this->values['n']));
407
408
        $n_minus_one = $n->subtract(BigInteger::createFromDecimal(1));
409
410
        // 1a: Let k = de-1
411
        $k = $d->multiply($e)->subtract($one);
412
413
        // 1b: If k is odd, then say there are no prime factors found.
414
        Assertion::true($k->isEven(), 'Prime factors not found.'); // STEP 4
415
416
        // 2: Write k as k = 2tr, where r is the largest odd integer dividing k, and t ≥ 1.
417
        //    Or in simpler terms, divide k repeatedly by 2 until you reach an odd number.
418
        //
419
        // BEGIN STEP 2
420
        $r = clone $k;
421
        $t = BigInteger::createFromDecimal(0);
422
        $two = BigInteger::createFromDecimal(2);
423
424
        do {
425
            $r = $r->divide($two);
426
            $t = $t->add($one);
427
        } while ($r->isEven());
428
        // END STEP 2
429
430
        // BEGIN STEP 3
431
        $success = false;
432
        $y = null;
433
434
        for ($i = 0; $i <= 100; $i++) {
435
436
            // 3a: Generate a random integer g in the range [0, n−1].
437
            $g = BigInteger::randomInt($zero, $n_minus_one);
438
439
            // 3b: Let y = gr mod n
440
            $y = $g->modPow($r, $n);
441
442
            // 3c: If y = 1 or y = n – 1, then go to Step 3.1 (i.e. repeat this loop).
443
            if ($y->equals($one) || $y->equals($n_minus_one)) {
444
                continue;
445
            }
446
447
            // 3d: For j = 1 to t – 1 do:
448
            for ($j = $one; $j->compare($t) <= 0; $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...
449
450
                // 3d1: Let x = y2 mod n
451
                $x = $y->modPow($two, $n);
452
453
                // 3d2: If x = 1, go to STEP 5.
454
                if ($x->equals($one)) {
455
                    $success = true;
456
                    break 2;
457
                }
458
459
                // 3d3: If x = n – 1, go to Step 3.1.
460
                if ($x->equals($n_minus_one)) {
461
                    continue 2;
462
                }
463
464
                // 3d4: Let y = x.
465
                $y = $x;
466
            }
467
468
            // 3e: Let x = y2 mod n
469
            $x = $y->modPow($two, $n);
470
471
            // 3f: If x = 1, go to STEP 5.
472
            if ($x->equals($one)) {
473
                $success = true;
474
                break;
475
            }
476
477
            // 3g: Loop again
478
        }
479
        //   END STEP 3
480
481
        // BEGIN STEP 5
482
        if ($success) {
483
            $p = $y->subtract($one)->gcd($n);
484
            $q = $n->divide($p);
485
486
            $this->values['p'] = Base64Url::encode($p->toBytes());
487
            $this->values['q'] = Base64Url::encode($q->toBytes());
488
489
            return;
490
        }
491
        //  END  STEP 5
492
493
        // STEP 4
494
        Assertion::true(false, 'Prime factors not found.');
495
    }
496
}
497