Completed
Push — master ( 7b61ef...71e756 )
by Florent
08:30 queued 07:08
created

src/Component/KeyManagement/KeyConverter/ECKey.php (12 issues)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2014-2019 Spomky-Labs
9
 *
10
 * This software may be modified and distributed under the terms
11
 * of the MIT license.  See the LICENSE file for details.
12
 */
13
14
namespace Jose\Component\KeyManagement\KeyConverter;
15
16
use Base64Url\Base64Url;
17
use Exception;
18
use FG\ASN1\ASNObject;
19
use FG\ASN1\ExplicitlyTaggedObject;
20
use FG\ASN1\Universal\BitString;
21
use FG\ASN1\Universal\Integer;
22
use FG\ASN1\Universal\ObjectIdentifier;
23
use FG\ASN1\Universal\OctetString;
24
use FG\ASN1\Universal\Sequence;
25
use InvalidArgumentException;
26
27
/**
28
 * @internal
29
 */
30
class ECKey
31
{
32
    /**
33
     * @var array
34
     */
35
    private $values = [];
36
37
    private function __construct(array $data)
38
    {
39
        $this->loadJWK($data);
40
    }
41
42
    public static function createFromPEM(string $pem): self
43
    {
44
        $data = self::loadPEM($pem);
45
46
        return new self($data);
47
    }
48
49
    /**
50
     * @param ECKey $private
51
     *
52
     * @return ECKey
53
     */
54
    public static function toPublic(self $private): self
55
    {
56
        $data = $private->toArray();
57
        if (\array_key_exists('d', $data)) {
58
            unset($data['d']);
59
        }
60
61
        return new self($data);
62
    }
63
64
    /**
65
     * @return array
66
     */
67
    public function toArray()
68
    {
69
        return $this->values;
70
    }
71
72
    private static function loadPEM(string $data): array
73
    {
74
        $data = base64_decode(preg_replace('#-.*-|\r|\n#', '', $data), true);
75
        $asnObject = ASNObject::fromBinary($data);
76
        if (!$asnObject instanceof Sequence) {
0 ignored issues
show
The class FG\ASN1\Universal\Sequence does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
77
            throw new InvalidArgumentException('Unable to load the key.');
78
        }
79
        $children = $asnObject->getChildren();
80
        if (self::isPKCS8($children)) {
81
            $children = self::loadPKCS8($children);
82
        }
83
84
        if (4 === \count($children)) {
85
            return self::loadPrivatePEM($children);
86
        }
87
        if (2 === \count($children)) {
88
            return self::loadPublicPEM($children);
89
        }
90
91
        throw new Exception('Unable to load the key.');
92
    }
93
94
    /**
95
     * @param ASNObject[] $children
96
     */
97
    private static function loadPKCS8(array $children): array
98
    {
99
        $binary = hex2bin($children[2]->getContent());
100
        $asnObject = ASNObject::fromBinary($binary);
101
        if (!$asnObject instanceof Sequence) {
0 ignored issues
show
The class FG\ASN1\Universal\Sequence does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
102
            throw new InvalidArgumentException('Unable to load the key.');
103
        }
104
105
        return $asnObject->getChildren();
106
    }
107
108
    private static function loadPublicPEM(array $children): array
109
    {
110
        if (!$children[0] instanceof Sequence) {
0 ignored issues
show
The class FG\ASN1\Universal\Sequence does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
111
            throw new InvalidArgumentException('Unsupported key type.');
112
        }
113
114
        $sub = $children[0]->getChildren();
115
        if (!$sub[0] instanceof ObjectIdentifier) {
0 ignored issues
show
The class FG\ASN1\Universal\ObjectIdentifier does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
116
            throw new InvalidArgumentException('Unsupported key type.');
117
        }
118
        if ('1.2.840.10045.2.1' !== $sub[0]->getContent()) {
119
            throw new InvalidArgumentException('Unsupported key type.');
120
        }
121
        if (!$sub[1] instanceof ObjectIdentifier) {
0 ignored issues
show
The class FG\ASN1\Universal\ObjectIdentifier does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
122
            throw new InvalidArgumentException('Unsupported key type.');
123
        }
124
        if (!$children[1] instanceof BitString) {
0 ignored issues
show
The class FG\ASN1\Universal\BitString does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
125
            throw new InvalidArgumentException('Unable to load the key.');
126
        }
127
128
        $bits = $children[1]->getContent();
129
        $bits_length = mb_strlen($bits, '8bit');
130
        if ('04' !== mb_substr($bits, 0, 2, '8bit')) {
131
            throw new InvalidArgumentException('Unsupported key type');
132
        }
133
134
        $values = ['kty' => 'EC'];
135
        $values['crv'] = self::getCurve($sub[1]->getContent());
136
        $values['x'] = Base64Url::encode(hex2bin(mb_substr($bits, 2, ($bits_length - 2) / 2, '8bit')));
137
        $values['y'] = Base64Url::encode(hex2bin(mb_substr($bits, (int) (($bits_length - 2) / 2 + 2), ($bits_length - 2) / 2, '8bit')));
138
139
        return $values;
140
    }
141
142
    private static function getCurve(string $oid): string
143
    {
144
        $curves = self::getSupportedCurves();
145
        $curve = array_search($oid, $curves, true);
146
        if (!\is_string($curve)) {
147
            throw new InvalidArgumentException('Unsupported OID.');
148
        }
149
150
        return $curve;
151
    }
152
153
    private static function getSupportedCurves(): array
154
    {
155
        return [
156
            'P-256' => '1.2.840.10045.3.1.7',
157
            'P-384' => '1.3.132.0.34',
158
            'P-521' => '1.3.132.0.35',
159
        ];
160
    }
161
162
    private static function verifyVersion(ASNObject $children): void
163
    {
164
        if (!$children instanceof Integer || '1' !== $children->getContent()) {
0 ignored issues
show
The class FG\ASN1\Universal\Integer does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
165
            throw new InvalidArgumentException('Unable to load the key.');
166
        }
167
    }
168
169
    private static function getXAndY(ASNObject $children, string &$x, string &$y): void
170
    {
171
        if (!$children instanceof ExplicitlyTaggedObject || !\is_array($children->getContent())) {
0 ignored issues
show
The class FG\ASN1\ExplicitlyTaggedObject does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
172
            throw new InvalidArgumentException('Unable to load the key.');
173
        }
174
        if (!$children->getContent()[0] instanceof BitString) {
0 ignored issues
show
The class FG\ASN1\Universal\BitString does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
175
            throw new InvalidArgumentException('Unable to load the key.');
176
        }
177
178
        $bits = $children->getContent()[0]->getContent();
179
        $bits_length = mb_strlen($bits, '8bit');
180
181
        if ('04' !== mb_substr($bits, 0, 2, '8bit')) {
182
            throw new InvalidArgumentException('Unsupported key type');
183
        }
184
185
        $x = mb_substr($bits, 2, (int) (($bits_length - 2) / 2), '8bit');
186
        $y = mb_substr($bits, (int) (($bits_length - 2) / 2 + 2), (int) (($bits_length - 2) / 2), '8bit');
187
    }
188
189
    private static function getD(ASNObject $children): string
190
    {
191
        if (!$children instanceof OctetString) {
0 ignored issues
show
The class FG\ASN1\Universal\OctetString does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
192
            throw new InvalidArgumentException('Unable to load the key.');
193
        }
194
195
        return $children->getContent();
196
    }
197
198
    private static function loadPrivatePEM(array $children): array
199
    {
200
        self::verifyVersion($children[0]);
201
        $x = '';
202
        $y = '';
203
        $d = self::getD($children[1]);
204
        self::getXAndY($children[3], $x, $y);
205
206
        if (!$children[2] instanceof ExplicitlyTaggedObject || !\is_array($children[2]->getContent())) {
0 ignored issues
show
The class FG\ASN1\ExplicitlyTaggedObject does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
207
            throw new InvalidArgumentException('Unable to load the key.');
208
        }
209
        if (!$children[2]->getContent()[0] instanceof ObjectIdentifier) {
0 ignored issues
show
The class FG\ASN1\Universal\ObjectIdentifier does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
210
            throw new InvalidArgumentException('Unable to load the key.');
211
        }
212
213
        $curve = $children[2]->getContent()[0]->getContent();
214
215
        $values = ['kty' => 'EC'];
216
        $values['crv'] = self::getCurve($curve);
217
        $values['d'] = Base64Url::encode(hex2bin($d));
218
        $values['x'] = Base64Url::encode(hex2bin($x));
219
        $values['y'] = Base64Url::encode(hex2bin($y));
220
221
        return $values;
222
    }
223
224
    /**
225
     * @param ASNObject[] $children
226
     */
227
    private static function isPKCS8(array $children): bool
228
    {
229
        if (3 !== \count($children)) {
230
            return false;
231
        }
232
233
        $classes = [0 => Integer::class, 1 => Sequence::class, 2 => OctetString::class];
234
        foreach ($classes as $k => $class) {
235
            if (!$children[$k] instanceof $class) {
236
                return false;
237
            }
238
        }
239
240
        return true;
241
    }
242
243
    private function loadJWK(array $jwk): void
244
    {
245
        $keys = [
246
            'kty' => 'The key parameter "kty" is missing.',
247
            'crv' => 'Curve parameter is missing',
248
            'x' => 'Point parameters are missing.',
249
            'y' => 'Point parameters are missing.',
250
        ];
251
        foreach ($keys as $k => $v) {
252
            if (!\array_key_exists($k, $jwk)) {
253
                throw new InvalidArgumentException($v);
254
            }
255
        }
256
257
        if ('EC' !== $jwk['kty']) {
258
            throw new InvalidArgumentException('JWK is not an Elliptic Curve key.');
259
        }
260
        $this->values = $jwk;
261
    }
262
}
263