RSAKey::isPublic()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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