Failed Conditions
Push — master ( d39f59...052283 )
by Florent
01:54
created

RSAKey::isPublic()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
343
            throw new \RuntimeException();
344
        }
345
        if ($key->isPublic() || empty($key->getPrimes()) || empty($key->getExponents()) || null === $key->getCoefficient()) {
346
            return $c->modPow($key->getExponent(), $key->getModulus());
0 ignored issues
show
Documentation introduced by
$key->getExponent() is of type object<Jose\Component\Core\Util\BigInteger>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$key->getModulus() is of type object<Jose\Component\Core\Util\BigInteger>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
347
        }
348
349
        $p = $key->getPrimes()[0];
350
        $q = $key->getPrimes()[1];
351
        $dP = $key->getExponents()[0];
352
        $dQ = $key->getExponents()[1];
353
        $qInv = $key->getCoefficient();
354
355
        $m1 = $c->modPow($dP, $p);
0 ignored issues
show
Documentation introduced by
$dP is of type object<Jose\Component\Core\Util\BigInteger>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$p is of type object<Jose\Component\Core\Util\BigInteger>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
356
        $m2 = $c->modPow($dQ, $q);
0 ignored issues
show
Documentation introduced by
$dQ is of type object<Jose\Component\Core\Util\BigInteger>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$q is of type object<Jose\Component\Core\Util\BigInteger>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
357
        $h = $qInv->multiply($m1->subtract($m2)->add($p))->mod($p);
358
        $m = $m2->add($h->multiply($q));
359
360
        return $m;
361
    }
362
}
363