Failed Conditions
Push — v7 ( 0b17f0...b3d8c9 )
by Florent
02:08
created

ECDHES::getAgreementKey()   D

Complexity

Conditions 9
Paths 26

Size

Total Lines 39
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

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