Completed
Push — master ( 022625...2b82d4 )
by Florent
05:58
created

RSAKey::initPublicKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 12
nc 1
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
24
final class RSAKey extends Sequence
25
{
26
    /**
27
     * @var bool
28
     */
29
    private $private = false;
30
31
    /**
32
     * @var array
33
     */
34
    private $values = [];
35
36
    /**
37
     * @param \Jose\Object\JWKInterface|string|array $data
38
     */
39
    public function __construct($data)
40
    {
41
        parent::__construct();
42
43
        if ($data instanceof JWKInterface) {
44
            $this->loadJWK($data->getAll());
45
        } elseif (is_array($data)) {
46
            $this->loadJWK($data);
47
        } elseif (is_string($data)) {
48
            $this->loadPEM($data);
49
        } else {
50
            throw new \InvalidArgumentException('Unsupported input');
51
        }
52
        $this->private = isset($this->values['d']);
53
    }
54
55
    /**
56
     * @param $data
57
     *
58
     * @throws \Exception
59
     * @throws \FG\ASN1\Exception\ParserException
60
     *
61
     * @return array
62
     */
63
    private function loadPEM($data)
64
    {
65
        $res = openssl_pkey_get_private($data);
66
        if (false === $res) {
67
            $res = openssl_pkey_get_public($data);
68
        }
69
        Assertion::false(false === $res, 'Unable to load the key');
70
71
        $details = openssl_pkey_get_details($res);
72
        Assertion::keyExists($details, 'rsa', 'Unable to load the key');
73
74
        $this->values['kty'] = 'RSA';
75
        $keys = [
76
            'n'  => 'n',
77
            'e'  => 'e',
78
            'd'  => 'd',
79
            'p'  => 'p',
80
            'q'  => 'q',
81
            'dp' => 'dmp1',
82
            'dq' => 'dmq1',
83
            'qi' => 'iqmp',
84
        ];
85
        foreach ($details['rsa'] as $key => $value) {
86
            if (in_array($key, $keys)) {
87
                $value = Base64Url::encode($value);
88
                $this->values[array_search($key, $keys)] = $value;
89
            }
90
        }
91
    }
92
93
    /**
94
     * @param array $jwk
95
     */
96
    private function loadJWK(array $jwk)
97
    {
98
        Assertion::keyExists($jwk, 'kty', 'The key parameter "kty" is missing.');
99
        Assertion::eq($jwk['kty'], 'RSA', 'The JWK is not a RSA key');
100
101
        $this->values = $jwk;
102
        if (array_key_exists('p', $jwk)) {
103
            $this->values['dp'] = isset($jwk['dp']) ? $jwk['dp'] : Base64Url::encode(0);
104
            $this->values['dq'] = isset($jwk['dq']) ? $jwk['dq'] : Base64Url::encode(0);
105
            $this->values['qi'] = isset($jwk['qi']) ? $jwk['qi'] : Base64Url::encode(0);
106
            $this->initPrivateKey();
107
        } else {
108
            $this->initPublicKey();
109
        }
110
    }
111
112
    /**
113
     * @throws \Exception
114
     */
115
    private function initPublicKey()
116
    {
117
        $oid_sequence = new Sequence();
118
        $oid_sequence->addChild(new ObjectIdentifier('1.2.840.113549.1.1.1'));
119
        $oid_sequence->addChild(new NullObject());
120
        $this->addChild($oid_sequence);
121
122
        $n = new Integer($this->fromBase64ToInteger($this->values['n']));
123
        $e = new Integer($this->fromBase64ToInteger($this->values['e']));
124
125
        $key_sequence = new Sequence();
126
        $key_sequence->addChild($n);
127
        $key_sequence->addChild($e);
128
        $key_bit_string = new BitString(bin2hex($key_sequence->getBinary()));
129
        $this->addChild($key_bit_string);
130
    }
131
132
    private function initPrivateKey()
133
    {
134
        $this->addChild(new Integer(0));
135
136
        $oid_sequence = new Sequence();
137
        $oid_sequence->addChild(new ObjectIdentifier('1.2.840.113549.1.1.1'));
138
        $oid_sequence->addChild(new NullObject());
139
        $this->addChild($oid_sequence);
140
141
        $v = new Integer(0);
142
        $n = new Integer($this->fromBase64ToInteger($this->values['n']));
143
        $e = new Integer($this->fromBase64ToInteger($this->values['e']));
144
        $d = new Integer($this->fromBase64ToInteger($this->values['d']));
145
        $p = new Integer($this->fromBase64ToInteger($this->values['p']));
146
        $q = new Integer($this->fromBase64ToInteger($this->values['q']));
147
        $dp = new Integer($this->fromBase64ToInteger($this->values['dp']));
148
        $dq = new Integer($this->fromBase64ToInteger($this->values['dq']));
149
        $qi = new Integer($this->fromBase64ToInteger($this->values['qi']));
150
151
        $key_sequence = new Sequence();
152
        $key_sequence->addChild($v);
153
        $key_sequence->addChild($n);
154
        $key_sequence->addChild($e);
155
        $key_sequence->addChild($d);
156
        $key_sequence->addChild($p);
157
        $key_sequence->addChild($q);
158
        $key_sequence->addChild($dp);
159
        $key_sequence->addChild($dq);
160
        $key_sequence->addChild($qi);
161
        $key_octet_string = new OctetString(bin2hex($key_sequence->getBinary()));
162
        $this->addChild($key_octet_string);
163
    }
164
165
    /**
166
     * @param string $value
167
     *
168
     * @return string
169
     */
170
    private function fromBase64ToInteger($value)
171
    {
172
        return gmp_strval(gmp_init(current(unpack('H*', Base64Url::decode($value))), 16), 10);
173
    }
174
175
    /**
176
     * @return bool
177
     */
178
    public function isPrivate()
179
    {
180
        return $this->private;
181
    }
182
183
    /**
184
     * @param \Jose\KeyConverter\RSAKey $private
185
     *
186
     * @return \Jose\KeyConverter\RSAKey
187
     */
188
    public static function toPublic(RSAKey $private)
189
    {
190
        $data = $private->toArray();
191
        $keys = ['p', 'd', 'q', 'dp', 'dq', 'qi'];
192
        foreach ($keys as $key) {
193
            if (array_key_exists($key, $data)) {
194
                unset($data[$key]);
195
            }
196
        }
197
198
        return new self($data);
199
    }
200
201
    public function __toString()
202
    {
203
        return $this->toPEM();
204
    }
205
206
    /**
207
     * @return array
208
     */
209
    public function toArray()
210
    {
211
        return $this->values;
212
    }
213
214
    /**
215
     * @return string
216
     */
217
    public function toDER()
218
    {
219
        return $this->getBinary();
220
    }
221
222
    /**
223
     * @return string
224
     */
225
    public function toPEM()
226
    {
227
        $result = '-----BEGIN '.($this->private ? 'RSA PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL;
228
        $result .= chunk_split(base64_encode($this->getBinary()), 64, PHP_EOL);
229
        $result .= '-----END '.($this->private ? 'RSA PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL;
230
231
        return $result;
232
    }
233
}
234