Completed
Push — master ( cc163d...6f4d42 )
by Florent
04:37
created

RSAKey::__toString()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

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