Failed Conditions
Push — PHPSecLib_Rid ( fca9fd...3a3eb8 )
by Florent
03:04
created

RSAKey::getExponent()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 7
rs 9.4285
cc 2
eloc 4
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
     * @param \Jose\Object\JWKInterface|string|array $data
34
     */
35
    public function __construct($data)
36
    {
37
        parent::__construct();
38
39
        if ($data instanceof JWKInterface) {
40
            $this->loadJWK($data->getAll());
41
        } elseif (is_array($data)) {
42
            $this->loadJWK($data);
43
        } elseif (is_string($data)) {
44
            $this->loadPEM($data);
45
        } else {
46
            throw new \InvalidArgumentException('Unsupported input');
47
        }
48
    }
49
50
    /**
51
     * @return bool
52
     */
53
    public function isPublic()
54
    {
55
        return !$this->isPrivate();
56
    }
57
58
    /**
59
     * @return bool
60
     */
61
    public function isPrivate()
62
    {
63
        return array_key_exists('d', $this->values);
64
    }
65
66
    /**
67
     * @return \Jose\Util\BigInteger
68
     */
69
    public function getModulus()
70
    {
71
        return $this->convertBase64StringToBigInteger($this->values['n']);
72
    }
73
74
    /**
75
     * @return int
76
     */
77
    public function getModulusLength()
78
    {
79
        return strlen($this->getModulus()->toBytes());
80
    }
81
82
    /**
83
     * @return \Jose\Util\BigInteger
84
     */
85
    public function getExponent()
86
    {
87
        if (array_key_exists('d', $this->values)) {
88
            return $this->getPrivateExponent();
89
        }
90
        return $this->getPublicExponent();
91
    }
92
93
    /**
94
     * @return \Jose\Util\BigInteger
95
     */
96
    public function getPublicExponent()
97
    {
98
        return $this->convertBase64StringToBigInteger($this->values['e']);
99
    }
100
101
    /**
102
     * @return \Jose\Util\BigInteger
103
     */
104
    public function getPrivateExponent()
105
    {
106
        Assertion::keyExists($this->values, 'd');
107
108
        return $this->convertBase64StringToBigInteger($this->values['d']);
109
    }
110
111
    /**
112
     * @return \Jose\Util\BigInteger[]
113
     */
114
    public function getPrimes()
115
    {
116
        if (array_key_exists('p', $this->values) && array_key_exists('q', $this->values)) {
117
            return [
118
                $this->convertBase64StringToBigInteger($this->values['p']),
119
                $this->convertBase64StringToBigInteger($this->values['q']),
120
            ];
121
        }
122
123
        return [];
124
    }
125
126
    /**
127
     * @return \Jose\Util\BigInteger[]
128
     */
129
    public function getExponents()
130
    {
131
        if (array_key_exists('dp', $this->values) && array_key_exists('dq', $this->values) && array_key_exists('qi', $this->values)) {
132
            return [
133
                $this->convertBase64StringToBigInteger($this->values['dp']),
134
                $this->convertBase64StringToBigInteger($this->values['dq']),
135
            ];
136
        }
137
138
        return [];
139
    }
140
141
    /**
142
     * @return \Jose\Util\BigInteger|null
143
     */
144
    public function getCoefficient()
145
    {
146
        if (array_key_exists('qi', $this->values)) {
147
            return $this->convertBase64StringToBigInteger($this->values['qi']);
148
        }
149
    }
150
151
    /**
152
     * @param string $value
153
     *
154
     * @return \Jose\Util\BigInteger
155
     */
156
    private function convertBase64StringToBigInteger($value)
157
    {
158
        return BigInteger::createFromBinaryString(Base64Url::decode($value));
159
    }
160
161
    /**
162
     * @param $data
163
     *
164
     * @throws \Exception
165
     * @throws \FG\ASN1\Exception\ParserException
166
     *
167
     * @return array
168
     */
169
    private function loadPEM($data)
170
    {
171
        $res = openssl_pkey_get_private($data);
172
        if (false === $res) {
173
            $res = openssl_pkey_get_public($data);
174
        }
175
        Assertion::false(false === $res, 'Unable to load the key');
176
177
        $details = openssl_pkey_get_details($res);
178
        Assertion::keyExists($details, 'rsa', 'Unable to load the key');
179
180
        $this->values['kty'] = 'RSA';
181
        $keys = [
182
            'n'  => 'n',
183
            'e'  => 'e',
184
            'd'  => 'd',
185
            'p'  => 'p',
186
            'q'  => 'q',
187
            'dp' => 'dmp1',
188
            'dq' => 'dmq1',
189
            'qi' => 'iqmp',
190
        ];
191
        foreach ($details['rsa'] as $key => $value) {
192
            if (in_array($key, $keys)) {
193
                $value = Base64Url::encode($value);
194
                $this->values[array_search($key, $keys)] = $value;
195
            }
196
        }
197
    }
198
199
    /**
200
     * @param array $jwk
201
     */
202
    private function loadJWK(array $jwk)
203
    {
204
        Assertion::keyExists($jwk, 'kty', 'The key parameter "kty" is missing.');
205
        Assertion::eq($jwk['kty'], 'RSA', 'The JWK is not a RSA key');
206
207
        $this->values = $jwk;
208
        if (array_key_exists('d', $jwk)) {
209
            $this->values['dp'] = isset($jwk['dp']) ? $jwk['dp'] : Base64Url::encode(0);
210
            $this->values['dq'] = isset($jwk['dq']) ? $jwk['dq'] : Base64Url::encode(0);
211
            $this->values['qi'] = isset($jwk['qi']) ? $jwk['qi'] : Base64Url::encode(0);
212
            $this->initPrivateKey();
213
        } else {
214
            $this->initPublicKey();
215
        }
216
    }
217
218
    /**
219
     * @throws \Exception
220
     */
221
    private function initPublicKey()
222
    {
223
        $oid_sequence = new Sequence();
224
        $oid_sequence->addChild(new ObjectIdentifier('1.2.840.113549.1.1.1'));
225
        $oid_sequence->addChild(new NullObject());
226
        $this->addChild($oid_sequence);
227
228
        $n = new Integer($this->fromBase64ToInteger($this->values['n']));
229
        $e = new Integer($this->fromBase64ToInteger($this->values['e']));
230
231
        $key_sequence = new Sequence();
232
        $key_sequence->addChild($n);
233
        $key_sequence->addChild($e);
234
        $key_bit_string = new BitString(bin2hex($key_sequence->getBinary()));
235
        $this->addChild($key_bit_string);
236
    }
237
238
    private function initPrivateKey()
239
    {
240
        $this->addChild(new Integer(0));
241
242
        $oid_sequence = new Sequence();
243
        $oid_sequence->addChild(new ObjectIdentifier('1.2.840.113549.1.1.1'));
244
        $oid_sequence->addChild(new NullObject());
245
        $this->addChild($oid_sequence);
246
247
        $v = new Integer(0);
248
        $n = new Integer($this->fromBase64ToInteger($this->values['n']));
249
        $e = new Integer($this->fromBase64ToInteger($this->values['e']));
250
        $d = new Integer($this->fromBase64ToInteger($this->values['d']));
251
        $p = new Integer($this->fromBase64ToInteger($this->values['p']));
252
        $q = new Integer($this->fromBase64ToInteger($this->values['q']));
253
        $dp = new Integer($this->fromBase64ToInteger($this->values['dp']));
254
        $dq = new Integer($this->fromBase64ToInteger($this->values['dq']));
255
        $qi = new Integer($this->fromBase64ToInteger($this->values['qi']));
256
257
        $key_sequence = new Sequence();
258
        $key_sequence->addChild($v);
259
        $key_sequence->addChild($n);
260
        $key_sequence->addChild($e);
261
        $key_sequence->addChild($d);
262
        $key_sequence->addChild($p);
263
        $key_sequence->addChild($q);
264
        $key_sequence->addChild($dp);
265
        $key_sequence->addChild($dq);
266
        $key_sequence->addChild($qi);
267
        $key_octet_string = new OctetString(bin2hex($key_sequence->getBinary()));
268
        $this->addChild($key_octet_string);
269
    }
270
271
    /**
272
     * @param string $value
273
     *
274
     * @return string
275
     */
276
    private function fromBase64ToInteger($value)
277
    {
278
        return gmp_strval(gmp_init(current(unpack('H*', Base64Url::decode($value))), 16), 10);
279
    }
280
281
    /**
282
     * @param \Jose\KeyConverter\RSAKey $private
283
     *
284
     * @return \Jose\KeyConverter\RSAKey
285
     */
286
    public static function toPublic(RSAKey $private)
287
    {
288
        $data = $private->toArray();
289
        $keys = ['p', 'd', 'q', 'dp', 'dq', 'qi'];
290
        foreach ($keys as $key) {
291
            if (array_key_exists($key, $data)) {
292
                unset($data[$key]);
293
            }
294
        }
295
296
        return new self($data);
297
    }
298
299
    public function __toString()
300
    {
301
        return $this->toPEM();
302
    }
303
304
    /**
305
     * @return array
306
     */
307
    public function toArray()
308
    {
309
        return $this->values;
310
    }
311
312
    /**
313
     * @return string
314
     */
315
    public function toDER()
316
    {
317
        return $this->getBinary();
318
    }
319
320
    /**
321
     * @return string
322
     */
323
    public function toPEM()
324
    {
325
        $result = '-----BEGIN '.($this->isPrivate() ? 'RSA PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL;
326
        $result .= chunk_split(base64_encode($this->getBinary()), 64, PHP_EOL);
327
        $result .= '-----END '.($this->isPrivate() ? 'RSA PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL;
328
329
        return $result;
330
    }
331
}
332