Failed Conditions
Pull Request — master (#146)
by Florent
03:41
created

ECKey::loadPEM()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 24
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 24
rs 8.6845
c 0
b 0
f 0
cc 4
eloc 18
nc 4
nop 1
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\ExplicitlyTaggedObject;
17
use FG\ASN1\Object;
18
use FG\ASN1\Universal\BitString;
19
use FG\ASN1\Universal\Integer;
20
use FG\ASN1\Universal\ObjectIdentifier;
21
use FG\ASN1\Universal\OctetString;
22
use FG\ASN1\Universal\Sequence;
23
use Jose\Object\JWKInterface;
24
25
final class ECKey extends Sequence
26
{
27
    /**
28
     * @var bool
29
     */
30
    private $private = false;
31
32
    /**
33
     * @var array
34
     */
35
    private $values = [];
36
37
    /**
38
     * @param \Jose\Object\JWKInterface|string|array $data
39
     */
40
    public function __construct($data)
41
    {
42
        parent::__construct();
43
44
        if ($data instanceof JWKInterface) {
45
            $this->loadJWK($data->getAll());
46
        } elseif (is_array($data)) {
47
            $this->loadJWK($data);
48
        } elseif (is_string($data)) {
49
            $this->loadPEM($data);
50
        } else {
51
            throw new \InvalidArgumentException('Unsupported input');
52
        }
53
        $this->private = isset($this->values['d']);
54
    }
55
56
    /**
57
     * @param string $data
58
     *
59
     * @throws \Exception
60
     * @throws \FG\ASN1\Exception\ParserException
61
     *
62
     * @return array
63
     */
64
    private function loadPEM($data)
65
    {
66
        $data = base64_decode(preg_replace('#-.*-|\r|\n#', '', $data));
67
        $asnObject = Object::fromBinary($data);
68
69
        Assertion::isInstanceOf($asnObject, Sequence::class);
70
71
        $children = $asnObject->getChildren();
0 ignored issues
show
Bug introduced by
The method getChildren does only exist in FG\ASN1\Construct, but not in FG\ASN1\AbstractString a...d FG\ASN1\UnknownObject.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
72
        if (4 === count($children)) {
73
            return $this->loadPrivatePEM($children);
74
        } elseif (3 === count($children)) {
75
            Assertion::isInstanceOf($children[1], Sequence::class);
76
            $binary = hex2bin($children[2]->getContent());
77
            $asnObject = Object::fromBinary($binary);
78
            Assertion::isInstanceOf($asnObject, Sequence::class);
79
            $children = $asnObject->getChildren();
80
            Assertion::eq(4, count($children), 'Unable to load the key');
81
82
            return $this->loadPrivatePEM($children);
83
        } elseif (2 === count($children)) {
84
            return $this->loadPublicPEM($children);
85
        }
86
        throw new \Exception('Unable to load the key');
87
    }
88
89
    /**
90
     * @param array $jwk
91
     */
92
    private function loadJWK(array $jwk)
93
    {
94
        Assertion::true(array_key_exists('kty', $jwk), 'JWK is not an Elliptic Curve key');
95
        Assertion::eq($jwk['kty'], 'EC', 'JWK is not an Elliptic Curve key');
96
        Assertion::true(array_key_exists('crv', $jwk), 'Curve parameter is missing');
97
        Assertion::true(array_key_exists('x', $jwk), 'Point parameters are missing');
98
        Assertion::true(array_key_exists('y', $jwk), 'Point parameters are missing');
99
100
        $this->values = $jwk;
101
        if (array_key_exists('d', $jwk)) {
102
            $this->initPrivateKey();
103
        } else {
104
            $this->initPublicKey();
105
        }
106
    }
107
108
    private function initPublicKey()
109
    {
110
        $oid_sequence = new Sequence();
111
        $oid_sequence->addChild(new ObjectIdentifier('1.2.840.10045.2.1'));
112
        $oid_sequence->addChild(new ObjectIdentifier($this->getOID($this->values['crv'])));
113
        $this->addChild($oid_sequence);
114
115
        $bits = '04';
116
        $bits .= bin2hex(Base64Url::decode($this->values['x']));
117
        $bits .= bin2hex(Base64Url::decode($this->values['y']));
118
        $this->addChild(new BitString($bits));
119
    }
120
121
    private function initPrivateKey()
122
    {
123
        $this->addChild(new Integer(1));
124
        $this->addChild(new OctetString(bin2hex(Base64Url::decode($this->values['d']))));
125
126
        $oid = new ObjectIdentifier($this->getOID($this->values['crv']));
127
        $this->addChild(new ExplicitlyTaggedObject(0, $oid));
128
129
        $bits = '04';
130
        $bits .= bin2hex(Base64Url::decode($this->values['x']));
131
        $bits .= bin2hex(Base64Url::decode($this->values['y']));
132
        $bit = new BitString($bits);
133
        $this->addChild(new ExplicitlyTaggedObject(1, $bit));
134
    }
135
136
    /**
137
     * @param array $children
138
     *
139
     * @throws \Exception
140
     *
141
     * @return array
142
     */
143
    private function loadPublicPEM(array $children)
144
    {
145
        Assertion::isInstanceOf($children[0], Sequence::class, 'Unsupported key type');
146
147
        $sub = $children[0]->getChildren();
148
        Assertion::isInstanceOf($sub[0], ObjectIdentifier::class, 'Unsupported key type');
149
        Assertion::eq('1.2.840.10045.2.1', $sub[0]->getContent(), 'Unsupported key type');
150
151
        Assertion::isInstanceOf($sub[1], ObjectIdentifier::class, 'Unsupported key type');
152
        Assertion::isInstanceOf($children[1], BitString::class, 'Unable to load the key');
153
154
        $bits = $children[1]->getContent();
155
        $bits_length = mb_strlen($bits, '8bit');
156
157
        Assertion::eq('04', mb_substr($bits, 0, 2, '8bit'), 'Unsupported key type');
158
159
        $this->values['kty'] = 'EC';
160
        $this->values['crv'] = $this->getCurve($sub[1]->getContent());
161
        $this->values['x'] = Base64Url::encode(hex2bin(mb_substr($bits, 2, ($bits_length - 2) / 2, '8bit')));
162
        $this->values['y'] = Base64Url::encode(hex2bin(mb_substr($bits, ($bits_length - 2) / 2 + 2, ($bits_length - 2) / 2, '8bit')));
163
    }
164
165
    /**
166
     * @param \FG\ASN1\Object $children
167
     */
168
    private function verifyVersion(Object $children)
169
    {
170
        Assertion::isInstanceOf($children, Integer::class, 'Unable to load the key');
171
        Assertion::eq(1, $children->getContent(), 'Unable to load the key');
172
    }
173
174
    /**
175
     * @param \FG\ASN1\Object $children
176
     * @param string|null     $x
177
     * @param string|null     $y
178
     */
179
    private function getXAndY(Object $children, &$x, &$y)
180
    {
181
        Assertion::isInstanceOf($children, ExplicitlyTaggedObject::class, 'Unable to load the key');
182
        Assertion::isArray($children->getContent(), 'Unable to load the key');
183
        Assertion::isInstanceOf($children->getContent()[0], BitString::class, 'Unable to load the key');
184
185
        $bits = $children->getContent()[0]->getContent();
186
        $bits_length = mb_strlen($bits, '8bit');
187
188
        Assertion::eq('04', mb_substr($bits, 0, 2, '8bit'), 'Unsupported key type');
189
190
        $x = mb_substr($bits, 2, ($bits_length - 2) / 2, '8bit');
191
        $y = mb_substr($bits, ($bits_length - 2) / 2 + 2, ($bits_length - 2) / 2, '8bit');
192
    }
193
194
    /**
195
     * @param \FG\ASN1\Object $children
196
     *
197
     * @return string
198
     */
199
    private function getD(Object $children)
200
    {
201
        Assertion::isInstanceOf($children, '\FG\ASN1\Universal\OctetString', 'Unable to load the key');
202
203
        return $children->getContent();
204
    }
205
206
    /**
207
     * @param array $children
208
     *
209
     * @return array
210
     */
211
    private function loadPrivatePEM(array $children)
212
    {
213
        $this->verifyVersion($children[0]);
214
        $x = null;
215
        $y = null;
216
        $d = $this->getD($children[1]);
217
        $this->getXAndY($children[3], $x, $y);
218
219
        Assertion::isInstanceOf($children[2], ExplicitlyTaggedObject::class, 'Unable to load the key');
220
        Assertion::isArray($children[2]->getContent(), 'Unable to load the key');
221
        Assertion::isInstanceOf($children[2]->getContent()[0], ObjectIdentifier::class, 'Unable to load the key');
222
223
        $curve = $children[2]->getContent()[0]->getContent();
224
225
        $this->private = true;
226
        $this->values['kty'] = 'EC';
227
        $this->values['crv'] = $this->getCurve($curve);
228
        $this->values['d'] = Base64Url::encode(hex2bin($d));
229
        $this->values['x'] = Base64Url::encode(hex2bin($x));
230
        $this->values['y'] = Base64Url::encode(hex2bin($y));
231
    }
232
233
    /**
234
     * @return bool
235
     */
236
    public function isPrivate()
237
    {
238
        return $this->private;
239
    }
240
241
    /**
242
     * @param \Jose\KeyConverter\ECKey $private
243
     *
244
     * @return \Jose\KeyConverter\ECKey
245
     */
246
    public static function toPublic(ECKey $private)
247
    {
248
        $data = $private->toArray();
249
        if (array_key_exists('d', $data)) {
250
            unset($data['d']);
251
        }
252
253
        return new self($data);
254
    }
255
256
    /**
257
     * @return string
258
     */
259
    public function __toString()
260
    {
261
        return $this->toPEM();
262
    }
263
264
    /**
265
     * @return array
266
     */
267
    public function toArray()
268
    {
269
        return $this->values;
270
    }
271
272
    /**
273
     * @return string
274
     */
275
    public function toDER()
276
    {
277
        return $this->getBinary();
278
    }
279
280
    /**
281
     * @return string
282
     */
283
    public function toPEM()
284
    {
285
        $result = '-----BEGIN '.($this->private ? 'EC PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL;
286
        $result .= chunk_split(base64_encode($this->getBinary()), 64, PHP_EOL);
287
        $result .= '-----END '.($this->private ? 'EC PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL;
288
289
        return $result;
290
    }
291
292
    /**
293
     * @param $curve
294
     *
295
     * @return string
296
     */
297
    private function getOID($curve)
298
    {
299
        $curves = $this->getSupportedCurves();
300
        $oid = array_key_exists($curve, $curves) ? $curves[$curve] : null;
301
302
        Assertion::notNull($oid, 'Unsupported curve');
303
304
        return $oid;
305
    }
306
307
    /**
308
     * @param string $oid
309
     *
310
     * @return string
311
     */
312
    private function getCurve($oid)
313
    {
314
        $curves = $this->getSupportedCurves();
315
        $curve = array_search($oid, $curves, true);
316
        Assertion::string($curve, 'Unsupported OID');
317
318
        return $curve;
319
    }
320
321
    /**
322
     * @return array
323
     */
324
    private function getSupportedCurves()
325
    {
326
        return [
327
            'P-256' => '1.2.840.10045.3.1.7',
328
            'P-384' => '1.3.132.0.34',
329
            'P-521' => '1.3.132.0.35',
330
        ];
331
    }
332
}
333