Failed Conditions
Push — v7 ( 6a6683...1d7ef8 )
by Florent
03:11
created

RSAKey   B

Complexity

Total Complexity 38

Size/Duplication

Total Lines 302
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Importance

Changes 0
Metric Value
wmc 38
lcom 1
cbo 9
dl 0
loc 302
rs 8.3999
c 0
b 0
f 0

20 Methods

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