Failed Conditions
Push — v7 ( 629225...6b9564 )
by Florent
02:02
created

ECKey::createFromJWK()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2014-2017 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\Core\Util;
15
16
use Base64Url\Base64Url;
17
use FG\ASN1\ExplicitlyTaggedObject;
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\Component\Core\JWK;
24
25
/**
26
 * Class ECKey.
27
 */
28
final class ECKey
29
{
30
    /**
31
     * @var null|Sequence
32
     */
33
    private $sequence = null;
34
35
    /**
36
     * @var bool
37
     */
38
    private $private = false;
39
40
    /**
41
     * @var array
42
     */
43
    private $values = [];
44
45
    /**
46
     * ECKey constructor.
47
     *
48
     * @param JWK $data
49
     */
50
    private function __construct(JWK $data)
51
    {
52
        $this->loadJWK($data->all());
53
        $this->private = array_key_exists('d', $this->values);
54
    }
55
56
    /**
57
     * @param JWK $jwk
58
     *
59
     * @return ECKey
60
     */
61
    public static function createFromJWK(JWK $jwk): ECKey
62
    {
63
        return new self($jwk);
64
    }
65
66
    /**
67
     * @return array
68
     */
69
    private function getSupportedCurves(): array
70
    {
71
        return [
72
            'P-256' => '1.2.840.10045.3.1.7',
73
            'P-384' => '1.3.132.0.34',
74
            'P-521' => '1.3.132.0.35',
75
        ];
76
    }
77
78
    /**
79
     * @param ECKey $private
80
     *
81
     * @return ECKey
82
     */
83
    public static function toPublic(ECKey $private): ECKey
84
    {
85
        $data = $private->toArray();
86
        if (array_key_exists('d', $data)) {
87
            unset($data['d']);
88
        }
89
90
        return new self(JWK::create($data));
91
    }
92
93
    /**
94
     * @return array
95
     */
96
    public function toArray()
97
    {
98
        return $this->values;
99
    }
100
101
    /**
102
     * @param array $jwk
103
     */
104
    private function loadJWK(array $jwk)
105
    {
106
        $keys = [
107
            'kty' => 'The key parameter "kty" is missing.',
108
            'crv' => 'Curve parameter is missing',
109
            'x' => 'Point parameters are missing.',
110
            'y' => 'Point parameters are missing.',
111
        ];
112
        foreach ($keys as $k => $v) {
113
            if (!array_key_exists($k, $jwk)) {
114
                throw new \InvalidArgumentException($v);
115
            }
116
        }
117
118
        if ('EC' !== $jwk['kty']) {
119
            throw new \InvalidArgumentException('JWK is not an Elliptic Curve key.');
120
        }
121
        $this->values = $jwk;
122
    }
123
124
    private function initPublicKey()
125
    {
126
        $oid_sequence = new Sequence();
127
        $oid_sequence->addChild(new ObjectIdentifier('1.2.840.10045.2.1'));
128
        $oid_sequence->addChild(new ObjectIdentifier($this->getOID($this->values['crv'])));
129
        $this->sequence->addChild($oid_sequence);
130
131
        $bits = '04';
132
        $bits .= bin2hex(Base64Url::decode($this->values['x']));
133
        $bits .= bin2hex(Base64Url::decode($this->values['y']));
134
        $this->sequence->addChild(new BitString($bits));
135
    }
136
137
    private function initPrivateKey()
138
    {
139
        $this->sequence->addChild(new Integer(1));
140
        $this->sequence->addChild(new OctetString(bin2hex(Base64Url::decode($this->values['d']))));
141
142
        $oid = new ObjectIdentifier($this->getOID($this->values['crv']));
143
        $this->sequence->addChild(new ExplicitlyTaggedObject(0, $oid));
144
145
        $bits = '04';
146
        $bits .= bin2hex(Base64Url::decode($this->values['x']));
147
        $bits .= bin2hex(Base64Url::decode($this->values['y']));
148
        $bit = new BitString($bits);
149
        $this->sequence->addChild(new ExplicitlyTaggedObject(1, $bit));
150
    }
151
152
    /**
153
     * @return string
154
     */
155
    public function toPEM(): string
156
    {
157
        if (null === $this->sequence) {
158
            $this->sequence = new Sequence();
159
            if (array_key_exists('d', $this->values)) {
160
                $this->initPrivateKey();
161
            } else {
162
                $this->initPublicKey();
163
            }
164
        }
165
        $result = '-----BEGIN '.($this->private ? 'EC PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL;
166
        $result .= chunk_split(base64_encode($this->sequence->getBinary()), 64, PHP_EOL);
167
        $result .= '-----END '.($this->private ? 'EC PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL;
168
169
        return $result;
170
    }
171
172
    /**
173
     * @param $curve
174
     *
175
     * @return string
176
     */
177
    private function getOID(string $curve): string
178
    {
179
        $curves = $this->getSupportedCurves();
180
        if (!array_key_exists($curve, $curves)) {
181
            throw new \InvalidArgumentException('Unsupported curve.');
182
        }
183
184
        return $curves[$curve];
185
    }
186
}
187