Failed Conditions
Push — v7 ( 986b39...d5cb12 )
by Florent
04:01
created

ECDHES::getCurve()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.2
c 0
b 0
f 0
cc 4
eloc 11
nc 4
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\Encryption\Algorithm\KeyEncryption;
15
16
use Base64Url\Base64Url;
17
use Jose\Component\Core\JWK;
18
use Jose\Component\Core\Util\Ecc\Crypto\Key\PrivateKey;
19
use Jose\Component\Core\Util\Ecc\Curves\NistCurve;
20
use Jose\Component\Core\Util\Ecc\Math\GmpMath;
21
use Jose\Component\Core\Util\Ecc\Primitives\Curve;
22
use Jose\Component\Encryption\Util\ConcatKDF;
23
use Jose\Component\Core\JWKFactory;
24
use Jose\Component\Core\Util\Ecc\Crypto\EcDH\EcDH;
25
26
/**
27
 * Class ECDHES.
28
 */
29
final class ECDHES implements KeyAgreementInterface
30
{
31
    /**
32
     * {@inheritdoc}
33
     */
34
    public function getAgreementKey(int $encryption_key_length, string $algorithm, JWK $recipient_key, array $complete_header = [], array &$additional_header_values = []): string
35
    {
36
        if ($recipient_key->has('d')) {
37
            $this->checkKey($recipient_key, true);
38
            $private_key = $recipient_key;
39
            $public_key = $this->getPublicKey($complete_header);
40
        } else {
41
            $this->checkKey($recipient_key, false);
42
            $public_key = $recipient_key;
43
            switch ($public_key->get('crv')) {
44
                case 'P-256':
45
                case 'P-384':
46
                case 'P-521':
47
                    $private_key = JWKFactory::createECKey($public_key->get('crv'));
48
49
                    break;
50
                case 'X25519':
51
                    $private_key = JWKFactory::createOKPKey('X25519');
52
53
                    break;
54
                default:
55
                    throw new \InvalidArgumentException(sprintf('The curve "%s" is not supported', $public_key->get('crv')));
56
            }
57
            $epk = $private_key->toPublic()->all();
58
            $additional_header_values = array_merge($additional_header_values, [
59
                'epk' => $epk,
60
            ]);
61
        }
62
        if ($private_key->get('crv') !== $public_key->get('crv')) {
63
            throw new \InvalidArgumentException('Curves are different');
64
        }
65
66
        $agreed_key = $this->calculateAgreementKey($private_key, $public_key);
67
68
        $apu = array_key_exists('apu', $complete_header) ? $complete_header['apu'] : '';
69
        $apv = array_key_exists('apv', $complete_header) ? $complete_header['apv'] : '';
70
71
        return ConcatKDF::generate($agreed_key, $algorithm, $encryption_key_length, $apu, $apv);
72
    }
73
74
    /**
75
     * @param JWK $private_key
76
     * @param JWK $public_key
77
     *
78
     * @throws \InvalidArgumentException
79
     *
80
     * @return string
81
     */
82
    public function calculateAgreementKey(JWK $private_key, JWK $public_key): string
83
    {
84
        switch ($public_key->get('crv')) {
85
            case 'P-256':
86
            case 'P-384':
87
            case 'P-521':
88
                $curve = $this->getCurve($private_key);
89
90
                $rec_x = $this->convertBase64ToGmp($public_key->get('x'));
91
                $rec_y = $this->convertBase64ToGmp($public_key->get('y'));
92
                $sen_d = $this->convertBase64ToGmp($private_key->get('d'));
93
94
                $priv_key = PrivateKey::create($sen_d);
95
                $pub_key = $curve->getPublicKeyFrom($rec_x, $rec_y);
96
97
                return $this->convertDecToBin(EcDH::computeSharedKey($curve, $pub_key, $priv_key));
98
            case 'X25519':
99
                $sKey = Base64Url::decode($private_key->get('d'));
100
                $recipientPublickey = Base64Url::decode($public_key->get('x'));
101
102
                return sodium_crypto_scalarmult($sKey, $recipientPublickey);
103
            default:
104
                throw new \InvalidArgumentException(sprintf('The curve "%s" is not supported', $public_key->get('crv')));
105
        }
106
    }
107
108
    /**
109
     * {@inheritdoc}
110
     */
111
    public function name(): string
112
    {
113
        return 'ECDH-ES';
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119
    public function getKeyManagementMode(): string
120
    {
121
        return self::MODE_AGREEMENT;
122
    }
123
124
    /**
125
     * @param array $complete_header
126
     *
127
     * @return JWK
128
     */
129
    private function getPublicKey(array $complete_header)
130
    {
131
        if (!array_key_exists('epk', $complete_header)) {
132
            throw new \InvalidArgumentException('The header parameter "epk" is missing');
133
        }
134
        if (!is_array($complete_header['epk'])) {
135
            throw new \InvalidArgumentException('The header parameter "epk" is not an array of parameter');
136
        }
137
138
        $public_key = JWK::create($complete_header['epk']);
139
        $this->checkKey($public_key, false);
140
141
        return $public_key;
142
    }
143
144
    /**
145
     * @param JWK  $key
146
     * @param bool $is_private
147
     */
148
    private function checkKey(JWK $key, $is_private)
149
    {
150
        foreach (['x', 'crv'] as $k) {
151
            if (!$key->has($k)) {
152
                throw new \InvalidArgumentException(sprintf('The key parameter "%s" is missing.', $k));
153
            }
154
        }
155
156
        switch ($key->get('crv')) {
157
            case 'P-256':
158
            case 'P-384':
159
            case 'P-521':
160
                if ('EC' !== $key->get('kty')) {
161
                    throw new \InvalidArgumentException('Wrong key type.');
162
                }
163
                if (!$key->has('y')) {
164
                    throw new \InvalidArgumentException('The key parameter "y" is missing.');
165
                }
166
167
                break;
168
            case 'X25519':
169
                if ('OKP' !== $key->get('kty')) {
170
                    throw new \InvalidArgumentException('Wrong key type.');
171
                }
172
173
                break;
174
            default:
175
                throw new \InvalidArgumentException(sprintf('The curve "%s" is not supported', $key->get('crv')));
176
        }
177
        if (true === $is_private) {
178
            if (!$key->has('d')) {
179
                throw new \InvalidArgumentException('The key parameter "d" is missing.');
180
            }
181
        }
182
    }
183
184
    /**
185
     * @param JWK $key
186
     *
187
     * @throws \InvalidArgumentException
188
     *
189
     * @return Curve
190
     */
191
    private function getCurve(JWK $key): Curve
192
    {
193
        $crv = $key->get('crv');
194
195
        switch ($crv) {
196
            case 'P-256':
197
                return NistCurve::curve256();
198
            case 'P-384':
199
                return NistCurve::curve384();
200
            case 'P-521':
201
                return NistCurve::curve521();
202
            default:
203
                throw new \InvalidArgumentException(sprintf('The curve "%s" is not supported', $crv));
204
        }
205
    }
206
207
    /**
208
     * @param string $value
209
     *
210
     * @return \GMP
211
     */
212
    private function convertBase64ToGmp(string $value): \GMP
213
    {
214
        $value = unpack('H*', Base64Url::decode($value));
215
216
        return gmp_init($value[1], 16);
217
    }
218
219
    /**
220
     * @param \GMP $value
221
     *
222
     * @return string
223
     */
224
    private function convertDecToBin(\GMP $value): string
225
    {
226
        $value = gmp_strval($value, 10);
227
228
        return hex2bin(GmpMath::decHex($value));
229
    }
230
}
231