Completed
Push — master ( ff5377...16526e )
by Florent
02:39
created

ECDHES::checkKey()   B

Complexity

Conditions 6
Paths 9

Size

Total Lines 22
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 22
rs 8.6737
cc 6
eloc 17
nc 9
nop 2
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\Algorithm\KeyEncryption;
13
14
use Assert\Assertion;
15
use Base64Url\Base64Url;
16
use Jose\Factory\JWKFactory;
17
use Jose\Object\JWK;
18
use Jose\Object\JWKInterface;
19
use Jose\Util\ConcatKDF;
20
use Mdanter\Ecc\EccFactory;
21
use Mdanter\Ecc\Message\MessageFactory;
22
23
/**
24
 * Class ECDHES.
25
 */
26
final class ECDHES implements KeyAgreementInterface
27
{
28
    /**
29
     * @var \Mdanter\Ecc\Math\MathAdapterInterface
30
     */
31
    private $adapter;
32
33
    public function __construct()
34
    {
35
        $this->adapter = EccFactory::getAdapter();
36
    }
37
38
    /**
39
     * {@inheritdoc}
40
     */
41
    public function getAgreementKey($encryption_key_length, $algorithm, JWKInterface $recipient_key, array $complete_header = [], array &$additional_header_values = [])
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::createRandomECPrivateKey($public_key->get('crv'));
55
                    $epk = [
56
                        'kty' => $private_key->get('kty'),
57
                        'crv' => $private_key->get('crv'),
58
                        'x'   => $private_key->get('x'),
59
                        'y'   => $private_key->get('y'),
60
                    ];
61
                    break;
62
                case 'X25519':
63
                    $private_key = JWKFactory::createRandomX25519PrivateKey();
64
                    $epk = [
65
                        'kty' => $private_key->get('kty'),
66
                        'crv' => $private_key->get('crv'),
67
                        'x'   => $private_key->get('x'),
68
                    ];
69
                    break;
70
                default:
71
                    throw new \InvalidArgumentException(sprintf('The curve "%s" is not supported', $public_key->get('crv')));
72
            }
73
            $additional_header_values = array_merge($additional_header_values, [
74
                'epk' => $epk,
75
            ]);
76
        }
77
        Assertion::eq($private_key->get('crv'), $public_key->get('crv'), 'Curves are different');
78
79
        $agreed_key = $this->calculateAgreementKey($private_key, $public_key);
80
81
        $apu = array_key_exists('apu', $complete_header) ? $complete_header['apu'] : '';
82
        $apv = array_key_exists('apv', $complete_header) ? $complete_header['apv'] : '';
83
84
        return ConcatKDF::generate($agreed_key, $algorithm, $encryption_key_length, $apu, $apv);
85
    }
86
87
    /**
88
     * @param \Jose\Object\JWKInterface $private_key
89
     * @param \Jose\Object\JWKInterface $public_key
90
     *
91
     * @throws \InvalidArgumentException
92
     *
93
     * @return int|string|void
94
     */
95
    public function calculateAgreementKey(JWKInterface $private_key, JWKInterface $public_key)
96
    {
97
        switch ($public_key->get('crv')) {
98
            case 'P-256':
99
            case 'P-384':
100
            case 'P-521':
101
                $p = $this->getGenerator($private_key);
102
103
                $rec_x = $this->convertBase64ToDec($public_key->get('x'));
104
                $rec_y = $this->convertBase64ToDec($public_key->get('y'));
105
                $sen_d = $this->convertBase64ToDec($private_key->get('d'));
106
107
                $priv_key = $p->getPrivateKeyFrom($sen_d);
108
                $pub_key = $p->getPublicKeyFrom($rec_x, $rec_y);
109
110
                $message = new MessageFactory($this->adapter);
111
                $exchange = $priv_key->createExchange($message, $pub_key);
112
113
                return $this->convertDecToBin($exchange->calculateSharedKey());
114
            case 'X25519':
115
                return curve25519_shared(
116
                    Base64Url::decode($private_key->get('d')),
117
                    Base64Url::decode($public_key->get('x'))
118
                );
119
            default:
120
                throw new \InvalidArgumentException(sprintf('The curve "%s" is not supported', $public_key->get('crv')));
121
        }
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     */
127
    public function getAlgorithmName()
128
    {
129
        return 'ECDH-ES';
130
    }
131
132
    /**
133
     * {@inheritdoc}
134
     */
135
    public function getKeyManagementMode()
136
    {
137
        return self::MODE_AGREEMENT;
138
    }
139
140
    /**
141
     * @param array $complete_header
142
     *
143
     * @return \Jose\Object\JWKInterface
144
     */
145
    private function getPublicKey(array $complete_header)
146
    {
147
        Assertion::keyExists($complete_header, 'epk', 'The header parameter "epk" is missing');
148
        Assertion::isArray($complete_header['epk'], 'The header parameter "epk" is not an array of parameter');
149
150
        $public_key = new JWK($complete_header['epk']);
151
        $this->checkKey($public_key, false);
152
153
        return $public_key;
154
    }
155
156
    /**
157
     * @param \Jose\Object\JWKInterface $key
158
     * @param bool                      $is_private
159
     */
160
    private function checkKey(JWKInterface $key, $is_private)
161
    {
162
        Assertion::true($key->has('x'), 'The key parameter "x" is missing.');
163
        Assertion::true($key->has('crv'), 'The key parameter "crv" is missing.');
164
165
        switch ($key->get('crv')) {
166
            case 'P-256':
167
            case 'P-384':
168
            case 'P-521':
169
                Assertion::eq($key->get('kty'), 'EC', 'Wrong key type.');
170
                Assertion::true($key->has('y'), 'The key parameter "y" is missing.');
171
                break;
172
            case 'X25519':
173
                Assertion::eq($key->get('kty'), 'OKP', 'Wrong key type.');
174
                break;
175
            default:
176
                throw new \InvalidArgumentException(sprintf('The curve "%s" is not supported', $key->get('crv')));
177
        }
178
        if (true === $is_private) {
179
            Assertion::true($key->has('d'), 'The key parameter "d" is missing.');
180
        }
181
    }
182
183
    /**
184
     * @param JWKInterface $key
185
     *
186
     * @throws \InvalidArgumentException
187
     *
188
     * @return \Mdanter\Ecc\Primitives\GeneratorPoint
189
     */
190
    private function getGenerator(JWKInterface $key)
191
    {
192
        $crv = $key->get('crv');
193
194
        switch ($crv) {
195
            case 'P-256':
196
                return EccFactory::getNistCurves()->generator256();
197
            case 'P-384':
198
                return EccFactory::getNistCurves()->generator384();
199
            case 'P-521':
200
                return EccFactory::getNistCurves()->generator521();
201
            default:
202
                throw new \InvalidArgumentException(sprintf('The curve "%s" is not supported', $crv));
203
        }
204
    }
205
206
    /**
207
     * @param $value
208
     *
209
     * @return int|string
210
     */
211
    private function convertHexToDec($value)
212
    {
213
        return $this->adapter->hexDec($value);
214
    }
215
216
    /**
217
     * @param $value
218
     *
219
     * @return int|string
220
     */
221
    private function convertBase64ToDec($value)
222
    {
223
        $value = unpack('H*', Base64Url::decode($value));
224
225
        return $this->convertHexToDec($value[1]);
226
    }
227
228
    /**
229
     * @param $value
230
     *
231
     * @return string
232
     */
233
    private function convertDecToBin($value)
234
    {
235
        $adapter = EccFactory::getAdapter();
236
237
        return hex2bin($adapter->decHex($value));
238
    }
239
}
240