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

ECKey::getSupportedCurves()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
dl 0
loc 8
rs 9.4285
c 1
b 0
f 1
cc 1
eloc 5
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\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 $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, '\FG\ASN1\Universal\Sequence');
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 (2 === count($children)) {
75
            return $this->loadPublicPEM($children);
76
        }
77
        throw new \Exception('Unable to load the key');
78
    }
79
80
    /**
81
     * @param array $jwk
82
     */
83
    private function loadJWK(array $jwk)
84
    {
85
        Assertion::true(array_key_exists('kty', $jwk), 'JWK is not an Elliptic Curve key');
86
        Assertion::eq($jwk['kty'], 'EC', 'JWK is not an Elliptic Curve key');
87
        Assertion::true(array_key_exists('crv', $jwk), 'Curve parameter is missing');
88
        Assertion::true(array_key_exists('x', $jwk), 'Point parameters are missing');
89
        Assertion::true(array_key_exists('y', $jwk), 'Point parameters are missing');
90
91
        $this->values = $jwk;
92
        if (array_key_exists('d', $jwk)) {
93
            $this->initPrivateKey();
94
        } else {
95
            $this->initPublicKey();
96
        }
97
    }
98
99
    /**
100
     * @throws \Exception
101
     */
102
    private function initPublicKey()
103
    {
104
        $oid_sequence = new Sequence();
105
        $oid_sequence->addChild(new ObjectIdentifier('1.2.840.10045.2.1'));
106
        $oid_sequence->addChild(new ObjectIdentifier($this->getOID($this->values['crv'])));
107
        $this->addChild($oid_sequence);
108
109
        $bits = '04';
110
        $bits .= bin2hex(Base64Url::decode($this->values['x']));
111
        $bits .= bin2hex(Base64Url::decode($this->values['y']));
112
        $this->addChild(new BitString($bits));
113
    }
114
115
    /**
116
     *
117
     */
118
    private function initPrivateKey()
119
    {
120
        $this->addChild(new Integer(1));
121
        $this->addChild(new OctetString(bin2hex(Base64Url::decode($this->values['d']))));
122
123
        $oid = new ObjectIdentifier($this->getOID($this->values['crv']));
124
        $this->addChild(new ExplicitlyTaggedObject(0, $oid));
0 ignored issues
show
Documentation introduced by
$oid is of type object<FG\ASN1\Universal\ObjectIdentifier>, but the function expects a array<integer,object<FG\ASN1\Object>>.

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...
125
126
        $bits = '04';
127
        $bits .= bin2hex(Base64Url::decode($this->values['x']));
128
        $bits .= bin2hex(Base64Url::decode($this->values['y']));
129
        $bit = new BitString($bits);
130
        $this->addChild(new ExplicitlyTaggedObject(1, $bit));
0 ignored issues
show
Documentation introduced by
$bit is of type object<FG\ASN1\Universal\BitString>, but the function expects a array<integer,object<FG\ASN1\Object>>.

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...
131
    }
132
133
    /**
134
     * @param array $children
135
     *
136
     * @throws \Exception
137
     *
138
     * @return array
139
     */
140
    private function loadPublicPEM(array $children)
141
    {
142
        Assertion::isInstanceOf($children[0], '\FG\ASN1\Universal\Sequence', 'Unsupported key type');
143
144
        $sub = $children[0]->getChildren();
145
        Assertion::isInstanceOf($sub[0], '\FG\ASN1\Universal\ObjectIdentifier', 'Unsupported key type');
146
        Assertion::eq('1.2.840.10045.2.1', $sub[0]->getContent(), 'Unsupported key type');
147
148
        Assertion::isInstanceOf($sub[1], '\FG\ASN1\Universal\ObjectIdentifier', 'Unsupported key type');
149
        Assertion::isInstanceOf($children[1], '\FG\ASN1\Universal\BitString', 'Unable to load the key');
150
151
        $bits = $children[1]->getContent();
152
153
        Assertion::eq('04', substr($bits, 0, 2), 'Unsupported key type');
154
155
        $this->values['kty'] = 'EC';
156
        $this->values['crv'] = $this->getCurve($sub[1]->getContent());
157
        $this->values['x'] = Base64Url::encode(hex2bin(substr($bits, 2, (strlen($bits) - 2) / 2)));
158
        $this->values['y'] = Base64Url::encode(hex2bin(substr($bits, (strlen($bits) - 2) / 2 + 2, (strlen($bits) - 2) / 2)));
159
    }
160
161
    /**
162
     * @param \FG\ASN1\Object $children
163
     */
164
    private function verifyVersion(Object $children)
165
    {
166
        Assertion::isInstanceOf($children, '\FG\ASN1\Universal\Integer', 'Unable to load the key');
167
        Assertion::eq(1, $children->getContent(), 'Unable to load the key');
168
    }
169
170
    /**
171
     * @param \FG\ASN1\Object $children
172
     * @param string|null     $x
173
     * @param string|null     $y
174
     */
175
    private function getXAndY(Object $children, &$x, &$y)
176
    {
177
        Assertion::isInstanceOf($children, '\FG\ASN1\ExplicitlyTaggedObject', 'Unable to load the key');
178
        Assertion::isArray($children->getContent(), 'Unable to load the key');
179
        Assertion::isInstanceOf($children->getContent()[0], '\FG\ASN1\Universal\BitString', 'Unable to load the key');
180
181
        $bits = $children->getContent()[0]->getContent();
182
183
        Assertion::eq('04', substr($bits, 0, 2), 'Unsupported key type');
184
185
        $x = substr($bits, 2, (strlen($bits) - 2) / 2);
186
        $y = substr($bits, (strlen($bits) - 2) / 2 + 2, (strlen($bits) - 2) / 2);
187
    }
188
189
    /**
190
     * @param \FG\ASN1\Object $children
191
     *
192
     * @return string
193
     */
194
    private function getD(Object $children)
195
    {
196
        Assertion::isInstanceOf($children, '\FG\ASN1\Universal\OctetString', 'Unable to load the key');
197
198
        return $children->getContent();
199
    }
200
201
    /**
202
     * @param array $children
203
     *
204
     * @return array
205
     */
206
    private function loadPrivatePEM(array $children)
207
    {
208
        $this->verifyVersion($children[0]);
209
210
        $x = null;
211
        $y = null;
212
        $d = $this->getD($children[1]);
213
        $this->getXAndY($children[3], $x, $y);
214
215
        Assertion::isInstanceOf($children[2], '\FG\ASN1\ExplicitlyTaggedObject', 'Unable to load the key');
216
        Assertion::isArray($children[2]->getContent(), 'Unable to load the key');
217
        Assertion::isInstanceOf($children[2]->getContent()[0], '\FG\ASN1\Universal\ObjectIdentifier', 'Unable to load the key');
218
219
        $curve = $children[2]->getContent()[0]->getContent();
220
221
        $this->private = true;
222
        $this->values['kty'] = 'EC';
223
        $this->values['crv'] = $this->getCurve($curve);
224
        $this->values['d'] = Base64Url::encode(hex2bin($d));
225
        $this->values['x'] = Base64Url::encode(hex2bin($x));
226
        $this->values['y'] = Base64Url::encode(hex2bin($y));
227
    }
228
229
    /**
230
     * @return bool
231
     */
232
    public function isPrivate()
233
    {
234
        return $this->private;
235
    }
236
237
    /**
238
     * @param \Jose\KeyConverter\ECKey $private
239
     *
240
     * @return \Jose\KeyConverter\ECKey
241
     */
242
    public static function toPublic(ECKey $private)
243
    {
244
        $data = $private->toArray();
245
        if (array_key_exists('d', $data)) {
246
            unset($data['d']);
247
        }
248
249
        return new self($data);
250
    }
251
252
    public function __toString()
253
    {
254
        return $this->toPEM();
255
    }
256
257
    /**
258
     * @return array
259
     */
260
    public function toArray()
261
    {
262
        return $this->values;
263
    }
264
265
    /**
266
     * @return string
267
     */
268
    public function toDER()
269
    {
270
        return $this->getBinary();
271
    }
272
273
    /**
274
     * @return string
275
     */
276
    public function toPEM()
277
    {
278
        $result = '-----BEGIN '.($this->private ? 'EC PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL;
279
        $result .= chunk_split(base64_encode($this->getBinary()), 64, PHP_EOL);
280
        $result .= '-----END '.($this->private ? 'EC PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL;
281
282
        return $result;
283
    }
284
285
    /**
286
     * @param $curve
287
     *
288
     * @return string
289
     */
290
    private function getOID($curve)
291
    {
292
        $curves = $this->getSupportedCurves();
293
        $oid = array_key_exists($curve, $curves) ? $curves[$curve] : null;
294
295
        Assertion::notNull($oid, 'Unsupported curve');
296
297
        return $oid;
298
    }
299
300
    /**
301
     * @param string $oid
302
     *
303
     * @return string
304
     */
305
    private function getCurve($oid)
306
    {
307
        $curves = $this->getSupportedCurves();
308
        $curve = array_search($oid, $curves, true);
309
        Assertion::string($curve, 'Unsupported OID');
310
311
        return $curve;
312
    }
313
314
    /**
315
     * @return array
316
     */
317
    private function getSupportedCurves()
318
    {
319
        return [
320
            'P-256' => '1.2.840.10045.3.1.7',
321
            'P-384' => '1.3.132.0.34',
322
            'P-521' => '1.3.132.0.35',
323
        ];
324
    }
325
}
326