Failed Conditions
Push — master ( e3206a...8d8ce8 )
by Florent
02:45
created

src/KeyConverter/RSAKey.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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 string $value
175
     *
176
     * @return \Jose\Util\BigInteger
177
     */
178
    private function convertBase64StringToBigInteger($value)
179
    {
180
        return BigInteger::createFromBinaryString(Base64Url::decode($value));
181
    }
182
183
    /**
184
     * @param $data
185
     *
186
     * @throws \Exception
187
     * @throws \FG\ASN1\Exception\ParserException
188
     *
189
     * @return array
190
     */
191
    private function loadPEM($data)
192
    {
193
        $res = openssl_pkey_get_private($data);
194
        if (false === $res) {
195
            $res = openssl_pkey_get_public($data);
196
        }
197
        Assertion::false(false === $res, 'Unable to load the key');
198
199
        $details = openssl_pkey_get_details($res);
200
        Assertion::keyExists($details, 'rsa', 'Unable to load the key');
201
202
        $this->values['kty'] = 'RSA';
203
        $keys = [
204
            'n'  => 'n',
205
            'e'  => 'e',
206
            'd'  => 'd',
207
            'p'  => 'p',
208
            'q'  => 'q',
209
            'dp' => 'dmp1',
210
            'dq' => 'dmq1',
211
            'qi' => 'iqmp',
212
        ];
213
        foreach ($details['rsa'] as $key => $value) {
214
            if (in_array($key, $keys)) {
215
                $value = Base64Url::encode($value);
216
                $this->values[array_search($key, $keys)] = $value;
217
            }
218
        }
219
    }
220
221
    /**
222
     * @param array $jwk
223
     */
224
    private function loadJWK(array $jwk)
225
    {
226
        Assertion::keyExists($jwk, 'kty', 'The key parameter "kty" is missing.');
227
        Assertion::eq($jwk['kty'], 'RSA', 'The JWK is not a RSA key');
228
229
        $this->values = $jwk;
230
        if (array_key_exists('d', $jwk)) {
231
            $this->populateCRT();
232
            $this->initPrivateKey();
233
        } else {
234
            $this->initPublicKey();
235
        }
236
    }
237
238
    /**
239
     * This method adds Chinese Remainder Theorem (CRT) parameters if primes 'p' and 'q' are available.
240
     */
241
    private function populateCRT()
242
    {
243
        if (!array_key_exists('p', $this->values) && !array_key_exists('q', $this->values)) {
244
            return;
245
        }
246
        if (array_key_exists('dp', $this->values) && array_key_exists('dq', $this->values) && array_key_exists('qi', $this->values)) {
247
            return;
248
        }
249
250
        $one = BigInteger::createFromDecimal(1);
251
        $d = BigInteger::createFromBinaryString(Base64Url::decode($this->values['d']));
252
        $p = BigInteger::createFromBinaryString(Base64Url::decode($this->values['p']));
253
        $q = BigInteger::createFromBinaryString(Base64Url::decode($this->values['q']));
254
255
        $this->values['dp'] = Base64Url::encode($d->mod($p->subtract($one))->toBytes());
256
        $this->values['dq'] = Base64Url::encode($d->mod($q->subtract($one))->toBytes());
257
        $this->values['qi'] = Base64Url::encode($q->modInverse($p)->toBytes());
258
    }
259
260
    /**
261
     * @throws \Exception
262
     */
263
    private function initPublicKey()
264
    {
265
        $oid_sequence = new Sequence();
266
        $oid_sequence->addChild(new ObjectIdentifier('1.2.840.113549.1.1.1'));
267
        $oid_sequence->addChild(new NullObject());
268
        $this->addChild($oid_sequence);
269
270
        $n = new Integer($this->fromBase64ToInteger($this->values['n']));
271
        $e = new Integer($this->fromBase64ToInteger($this->values['e']));
272
273
        $key_sequence = new Sequence();
274
        $key_sequence->addChild($n);
275
        $key_sequence->addChild($e);
276
        $key_bit_string = new BitString(bin2hex($key_sequence->getBinary()));
277
        $this->addChild($key_bit_string);
278
    }
279
280
    private function initPrivateKey()
281
    {
282
        $this->ddChild(new Integer(0));
0 ignored issues
show
The method ddChild() does not exist on Jose\KeyConverter\RSAKey. Did you maybe mean addChild()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
283
284
        $oid_sequence = new Sequence();
285
        $oid_sequence->addChild(new ObjectIdentifier('1.2.840.113549.1.1.1'));
286
        $oid_sequence->addChild(new NullObject());
287
        $this->addChild($oid_sequence);
288
289
        $v = new Integer(0);
290
        $n = new Integer($this->fromBase64ToInteger($this->values['n']));
291
        $e = new Integer($this->fromBase64ToInteger($this->values['e']));
292
        $d = new Integer($this->fromBase64ToInteger($this->values['d']));
293
        $p = new Integer($this->fromBase64ToInteger($this->values['p']));
294
        $q = new Integer($this->fromBase64ToInteger($this->values['q']));
295
        $dp = new Integer($this->fromBase64ToInteger($this->values['dp']));
296
        $dq = new Integer($this->fromBase64ToInteger($this->values['dq']));
297
        $qi = new Integer($this->fromBase64ToInteger($this->values['qi']));
298
299
        $key_sequence = new Sequence();
300
        $key_sequence->addChild($v);
301
        $key_sequence->addChild($n);
302
        $key_sequence->addChild($e);
303
        $key_sequence->addChild($d);
304
        $key_sequence->addChild($p);
305
        $key_sequence->addChild($q);
306
        $key_sequence->addChild($dp);
307
        $key_sequence->addChild($dq);
308
        $key_sequence->addChild($qi);
309
        $key_octet_string = new OctetString(bin2hex($key_sequence->getBinary()));
310
        $this->addChild($key_octet_string);
311
    }
312
313
    /**
314
     * @param string $value
315
     *
316
     * @return string
317
     */
318
    private function fromBase64ToInteger($value)
319
    {
320
        return gmp_strval(gmp_init(current(unpack('H*', Base64Url::decode($value))), 16), 10);
321
    }
322
323
    /**
324
     * @param \Jose\KeyConverter\RSAKey $private
325
     *
326
     * @return \Jose\KeyConverter\RSAKey
327
     */
328
    public static function toPublic(RSAKey $private)
329
    {
330
        $data = $private->toArray();
331
        $keys = ['p', 'd', 'q', 'dp', 'dq', 'qi'];
332
        foreach ($keys as $key) {
333
            if (array_key_exists($key, $data)) {
334
                unset($data[$key]);
335
            }
336
        }
337
338
        return new self($data);
339
    }
340
341
    public function __toString()
342
    {
343
        return $this->toPEM();
344
    }
345
346
    /**
347
     * @return array
348
     */
349
    public function toArray()
350
    {
351
        return $this->values;
352
    }
353
354
    /**
355
     * @return string
356
     */
357
    public function toDER()
358
    {
359
        return $this->getBinary();
360
    }
361
362
    /**
363
     * @return string
364
     */
365
    public function toPEM()
366
    {
367
        $result = '-----BEGIN '.($this->isPrivate() ? 'RSA PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL;
368
        $result .= chunk_split(base64_encode($this->getBinary()), 64, PHP_EOL);
369
        $result .= '-----END '.($this->isPrivate() ? 'RSA PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL;
370
371
        return $result;
372
    }
373
374
    private function populateBigIntegers()
375
    {
376
        $this->modulus = $this->convertBase64StringToBigInteger($this->values['n']);
377
        $this->modulus_length = mb_strlen($this->getModulus()->toBytes(), '8bit');
378
        $this->public_exponent = $this->convertBase64StringToBigInteger($this->values['e']);
379
380
        if (true === $this->isPrivate()) {
381
            $this->private_exponent = $this->convertBase64StringToBigInteger($this->values['d']);
382
383
            $this->primes = [
384
                $this->convertBase64StringToBigInteger($this->values['p']),
385
                $this->convertBase64StringToBigInteger($this->values['q']),
386
            ];
387
            $this->exponents = [
388
                $this->convertBase64StringToBigInteger($this->values['dp']),
389
                $this->convertBase64StringToBigInteger($this->values['dq']),
390
            ];
391
            $this->coefficient = $this->convertBase64StringToBigInteger($this->values['qi']);
392
        }
393
    }
394
}
395