Completed
Push — master ( a80b49...b6cabc )
by Florent
02:33
created

ECDHES::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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