Failed Conditions
Push — master ( 91fff0...206233 )
by Florent
12:52 queued 09:19
created

RSAKey::populateCRT()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 17
rs 9.2
cc 4
eloc 12
nc 2
nop 0
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
        Assertion::keyExists($this->values, 'p', 'The prime "p" is not available.');
284
        Assertion::keyExists($this->values, 'q', 'The prime "q" is not available.');
285
        if (array_key_exists('dp', $this->values) && array_key_exists('dq', $this->values) && array_key_exists('qi', $this->values)) {
286
            return;
287
        }
288
289
        $one = BigInteger::createFromDecimal(1);
290
        $d = BigInteger::createFromBinaryString(Base64Url::decode($this->values['d']));
291
        $p = BigInteger::createFromBinaryString(Base64Url::decode($this->values['p']));
292
        $q = BigInteger::createFromBinaryString(Base64Url::decode($this->values['q']));
293
294
        $this->values['dp'] = Base64Url::encode($d->mod($p->subtract($one))->toBytes());
295
        $this->values['dq'] = Base64Url::encode($d->mod($q->subtract($one))->toBytes());
296
        $this->values['qi'] = Base64Url::encode($q->modInverse($p)->toBytes());
297
    }
298
299
    /**
300
     * @throws \Exception
301
     */
302
    private function initPublicKey()
303
    {
304
        $oid_sequence = new Sequence();
305
        $oid_sequence->addChild(new ObjectIdentifier('1.2.840.113549.1.1.1'));
306
        $oid_sequence->addChild(new NullObject());
307
        $this->addChild($oid_sequence);
308
309
        $n = new Integer($this->fromBase64ToInteger($this->values['n']));
310
        $e = new Integer($this->fromBase64ToInteger($this->values['e']));
311
312
        $key_sequence = new Sequence();
313
        $key_sequence->addChild($n);
314
        $key_sequence->addChild($e);
315
        $key_bit_string = new BitString(bin2hex($key_sequence->getBinary()));
316
        $this->addChild($key_bit_string);
317
    }
318
319
    private function initPrivateKey()
320
    {
321
        $this->addChild(new Integer(0));
322
323
        $oid_sequence = new Sequence();
324
        $oid_sequence->addChild(new ObjectIdentifier('1.2.840.113549.1.1.1'));
325
        $oid_sequence->addChild(new NullObject());
326
        $this->addChild($oid_sequence);
327
328
        $v = new Integer(0);
329
        $n = new Integer($this->fromBase64ToInteger($this->values['n']));
330
        $e = new Integer($this->fromBase64ToInteger($this->values['e']));
331
        $d = new Integer($this->fromBase64ToInteger($this->values['d']));
332
        $p = new Integer($this->fromBase64ToInteger($this->values['p']));
333
        $q = new Integer($this->fromBase64ToInteger($this->values['q']));
334
        $dp = array_key_exists('dp', $this->values) ? new Integer($this->fromBase64ToInteger($this->values['dp'])) : new Integer(0);
335
        $dq = array_key_exists('dq', $this->values) ? new Integer($this->fromBase64ToInteger($this->values['dq'])) : new Integer(0);
336
        $qi = array_key_exists('qi', $this->values) ? new Integer($this->fromBase64ToInteger($this->values['qi'])) : new Integer(0);
337
338
        $key_sequence = new Sequence();
339
        $key_sequence->addChild($v);
340
        $key_sequence->addChild($n);
341
        $key_sequence->addChild($e);
342
        $key_sequence->addChild($d);
343
        $key_sequence->addChild($p);
344
        $key_sequence->addChild($q);
345
        $key_sequence->addChild($dp);
346
        $key_sequence->addChild($dq);
347
        $key_sequence->addChild($qi);
348
        $key_octet_string = new OctetString(bin2hex($key_sequence->getBinary()));
349
        $this->addChild($key_octet_string);
350
    }
351
352
    /**
353
     * @param string $value
354
     *
355
     * @return string
356
     */
357
    private function fromBase64ToInteger($value)
358
    {
359
        return gmp_strval(gmp_init(current(unpack('H*', Base64Url::decode($value))), 16), 10);
360
    }
361
362
    private function populateBigIntegers()
363
    {
364
        $this->modulus = $this->convertBase64StringToBigInteger($this->values['n']);
365
        $this->modulus_length = mb_strlen($this->getModulus()->toBytes(), '8bit');
366
        $this->public_exponent = $this->convertBase64StringToBigInteger($this->values['e']);
367
368
        if (true === $this->isPrivate()) {
369
            $this->private_exponent = $this->convertBase64StringToBigInteger($this->values['d']);
370
371
            if (array_key_exists('p', $this->values) && array_key_exists('q', $this->values)) {
372
                $this->primes = [
373
                    $this->convertBase64StringToBigInteger($this->values['p']),
374
                    $this->convertBase64StringToBigInteger($this->values['q']),
375
                ];
376
                $this->exponents = [
377
                    $this->convertBase64StringToBigInteger($this->values['dp']),
378
                    $this->convertBase64StringToBigInteger($this->values['dq']),
379
                ];
380
                $this->coefficient = $this->convertBase64StringToBigInteger($this->values['qi']);
381
            }
382
        }
383
    }
384
385
    /**
386
     * @param string $value
387
     *
388
     * @return \Jose\Util\BigInteger
389
     */
390
    private function convertBase64StringToBigInteger($value)
391
    {
392
        return BigInteger::createFromBinaryString(Base64Url::decode($value));
393
    }
394
}
395