Completed
Push — master ( 934958...cc163d )
by Florent
02:37
created

ECKey::initPublicKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 12
rs 9.4285
cc 1
eloc 9
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));
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));
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::isInstanceOf($children->getContent(), '\FG\ASN1\Universal\BitString', 'Unable to load the key');
179
180
        $bits = $children->getContent()->getContent();
181
182
        Assertion::eq('04', substr($bits, 0, 2), 'Unsupported key type');
183
184
        $x = substr($bits, 2, (strlen($bits) - 2) / 2);
185
        $y = substr($bits, (strlen($bits) - 2) / 2 + 2, (strlen($bits) - 2) / 2);
186
    }
187
188
    /**
189
     * @param \FG\ASN1\Object $children
190
     *
191
     * @return string
192
     */
193
    private function getD(Object $children)
194
    {
195
        Assertion::isInstanceOf($children, '\FG\ASN1\Universal\OctetString', 'Unable to load the key');
196
197
        return $children->getContent();
198
    }
199
200
    /**
201
     * @param array $children
202
     *
203
     * @return array
204
     */
205
    private function loadPrivatePEM(array $children)
206
    {
207
        $this->verifyVersion($children[0]);
208
209
        $x = null;
210
        $y = null;
211
        $d = $this->getD($children[1]);
212
        $this->getXAndY($children[3], $x, $y);
213
214
        Assertion::isInstanceOf($children[2], '\FG\ASN1\ExplicitlyTaggedObject', 'Unable to load the key');
215
        Assertion::isInstanceOf($children[2]->getContent(), '\FG\ASN1\Universal\ObjectIdentifier', 'Unable to load the key');
216
217
        $curve = $children[2]->getContent()->getContent();
218
219
        $this->private = true;
220
        $this->values['kty'] = 'EC';
221
        $this->values['crv'] = $this->getCurve($curve);
222
        $this->values['d'] = Base64Url::encode(hex2bin($d));
223
        $this->values['x'] = Base64Url::encode(hex2bin($x));
224
        $this->values['y'] = Base64Url::encode(hex2bin($y));
225
    }
226
227
    /**
228
     * @return bool
229
     */
230
    public function isPrivate()
231
    {
232
        return $this->private;
233
    }
234
235
    /**
236
     * @param \Jose\KeyConverter\ECKey $private
237
     *
238
     * @return \Jose\KeyConverter\ECKey
239
     */
240
    public static function toPublic(ECKey $private)
241
    {
242
        $data = $private->toArray();
243
        if (array_key_exists('d', $data)) {
244
            unset($data['d']);
245
        }
246
247
        return new self($data);
248
    }
249
250
    public function __toString()
251
    {
252
        return $this->toPEM();
253
    }
254
255
    /**
256
     * @return array
257
     */
258
    public function toArray()
259
    {
260
        return $this->values;
261
    }
262
263
    /**
264
     * @return string
265
     */
266
    public function toPEM()
267
    {
268
        $result = '-----BEGIN '.($this->private ? 'EC PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL;
269
        $result .= chunk_split(base64_encode($this->getBinary()), 64, PHP_EOL);
270
        $result .= '-----END '.($this->private ? 'EC PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL;
271
272
        return $result;
273
    }
274
275
    /**
276
     * @param $curve
277
     *
278
     * @return string
279
     */
280
    private function getOID($curve)
281
    {
282
        $curves = $this->getSupportedCurves();
283
        $oid = array_key_exists($curve, $curves) ? $curves[$curve] : null;
284
285
        Assertion::notNull($oid, 'Unsupported curve');
286
287
        return $oid;
288
    }
289
290
    /**
291
     * @param string $oid
292
     *
293
     * @return string
294
     */
295
    private function getCurve($oid)
296
    {
297
        $curves = $this->getSupportedCurves();
298
        $curve = array_search($oid, $curves, true);
299
        Assertion::string($curve, 'Unsupported OID');
300
301
        return $curve;
302
    }
303
304
    /**
305
     * @return array
306
     */
307
    private function getSupportedCurves()
308
    {
309
        return [
310
            'P-192' => '1.2.840.10045.3.1.1',
311
            'P-256' => '1.2.840.10045.3.1.7',
312
            'P-384' => '1.3.132.0.34',
313
            'P-521' => '1.3.132.0.35',
314
        ];
315
    }
316
}
317