Failed Conditions
Push — master ( f88415...5f7ed3 )
by Florent
02:49
created

RSAKey::populateBigIntegers()   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 22
rs 8.9197
nc 3
cc 4
eloc 14
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
    /**
69
     * @param \Jose\Object\JWKInterface|string|array $data
70
     */
71
    public function __construct($data)
72
    {
73
        parent::__construct();
74
75
        if ($data instanceof JWKInterface) {
76
            $this->loadJWK($data->getAll());
77
        } elseif (is_array($data)) {
78
            $this->loadJWK($data);
79
        } elseif (is_string($data)) {
80
            $this->loadPEM($data);
81
        } else {
82
            throw new \InvalidArgumentException('Unsupported input');
83
        }
84
85
        $this->populateBigIntegers();
86
    }
87
88
    /**
89
     * @return bool
90
     */
91
    public function isPublic()
92
    {
93
        return !$this->isPrivate();
94
    }
95
96
    /**
97
     * @return bool
98
     */
99
    public function isPrivate()
100
    {
101
        return array_key_exists('d', $this->values);
102
    }
103
104
    /**
105
     * @return \Jose\Util\BigInteger
106
     */
107
    public function getModulus()
108
    {
109
        return $this->modulus;
110
    }
111
112
    /**
113
     * @return int
114
     */
115
    public function getModulusLength()
116
    {
117
        return $this->modulus_length;
118
    }
119
120
    /**
121
     * @return \Jose\Util\BigInteger
122
     */
123
    public function getExponent()
124
    {
125
        $d = $this->getPrivateExponent();
126
        if (null !== $d) {
127
            return $d;
128
        }
129
130
        return $this->getPublicExponent();
131
    }
132
133
    /**
134
     * @return \Jose\Util\BigInteger
135
     */
136
    public function getPublicExponent()
137
    {
138
        return $this->public_exponent;
139
    }
140
141
    /**
142
     * @return \Jose\Util\BigInteger
143
     */
144
    public function getPrivateExponent()
145
    {
146
        return $this->private_exponent;
147
    }
148
149
    /**
150
     * @return \Jose\Util\BigInteger[]
151
     */
152
    public function getPrimes()
153
    {
154
        return $this->primes;
155
    }
156
157
    /**
158
     * @return \Jose\Util\BigInteger[]
159
     */
160
    public function getExponents()
161
    {
162
        return $this->exponents;
163
    }
164
165
    /**
166
     * @return \Jose\Util\BigInteger|null
167
     */
168
    public function getCoefficient()
169
    {
170
        return $this->coefficient;
171
    }
172
173
    /**
174
     * @param \Jose\KeyConverter\RSAKey $private
175
     *
176
     * @return \Jose\KeyConverter\RSAKey
177
     */
178
    public static function toPublic(RSAKey $private)
179
    {
180
        $data = $private->toArray();
181
        $keys = ['p', 'd', 'q', 'dp', 'dq', 'qi'];
182
        foreach ($keys as $key) {
183
            if (array_key_exists($key, $data)) {
184
                unset($data[$key]);
185
            }
186
        }
187
188
        return new self($data);
189
    }
190
191
    public function __toString()
192
    {
193
        return $this->toPEM();
194
    }
195
196
    /**
197
     * @return array
198
     */
199
    public function toArray()
200
    {
201
        return $this->values;
202
    }
203
204
    /**
205
     * @return string
206
     */
207
    public function toDER()
208
    {
209
        return $this->getBinary();
210
    }
211
212
    /**
213
     * @return string
214
     */
215
    public function toPEM()
216
    {
217
        $result = '-----BEGIN '.($this->isPrivate() ? 'RSA PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL;
218
        $result .= chunk_split(base64_encode($this->getBinary()), 64, PHP_EOL);
219
        $result .= '-----END '.($this->isPrivate() ? 'RSA PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL;
220
221
        return $result;
222
    }
223
224
    /**
225
     * @param string $data
226
     *
227
     * @throws \Exception
228
     * @throws \FG\ASN1\Exception\ParserException
229
     *
230
     * @return array
231
     */
232
    private function loadPEM($data)
233
    {
234
        $res = openssl_pkey_get_private($data);
235
        if (false === $res) {
236
            $res = openssl_pkey_get_public($data);
237
        }
238
        Assertion::false(false === $res, 'Unable to load the key');
239
240
        $details = openssl_pkey_get_details($res);
241
        Assertion::keyExists($details, 'rsa', 'Unable to load the key');
242
243
        $this->values['kty'] = 'RSA';
244
        $keys = [
245
            'n'  => 'n',
246
            'e'  => 'e',
247
            'd'  => 'd',
248
            'p'  => 'p',
249
            'q'  => 'q',
250
            'dp' => 'dmp1',
251
            'dq' => 'dmq1',
252
            'qi' => 'iqmp',
253
        ];
254
        foreach ($details['rsa'] as $key => $value) {
255
            if (in_array($key, $keys)) {
256
                $value = Base64Url::encode($value);
257
                $this->values[array_search($key, $keys)] = $value;
258
            }
259
        }
260
    }
261
262
    /**
263
     * @param array $jwk
264
     */
265
    private function loadJWK(array $jwk)
266
    {
267
        Assertion::keyExists($jwk, 'kty', 'The key parameter "kty" is missing.');
268
        Assertion::eq($jwk['kty'], 'RSA', 'The JWK is not a RSA key');
269
270
        $this->values = $jwk;
271
        if (array_key_exists('d', $jwk)) {
272
            $this->populateCRT();
273
            $this->initPrivateKey();
274
        } else {
275
            $this->initPublicKey();
276
        }
277
    }
278
279
    /**
280
     * This method adds Chinese Remainder Theorem (CRT) parameters if primes 'p' and 'q' are available.
281
     */
282
    private function populateCRT()
283
    {
284
        if (!array_key_exists('p', $this->values) && !array_key_exists('q', $this->values)) {
285
            return;
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 = array_key_exists('p', $this->values) ? new Integer($this->fromBase64ToInteger($this->values['p'])) : new Integer(0);
335
        $q = array_key_exists('q', $this->values) ? new Integer($this->fromBase64ToInteger($this->values['q'])) : new Integer(0);
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