Completed
Push — master ( 7aad34...96e43e )
by Florent
02:35
created

ECDHES::getGenerator()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 15
rs 9.2
cc 4
eloc 11
nc 4
nop 1
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 Base64Url\Base64Url;
15
use Jose\Object\JWK;
16
use Jose\Object\JWKInterface;
17
use Jose\Util\ConcatKDF;
18
use Mdanter\Ecc\EccFactory;
19
use Mdanter\Ecc\Message\MessageFactory;
20
21
/**
22
 * Class ECDHES.
23
 */
24
final class ECDHES implements KeyAgreementInterface
25
{
26
    /**
27
     * @var \Mdanter\Ecc\Math\MathAdapterInterface
28
     */
29
    private $adapter;
30
31
    /**
32
     *
33
     */
34
    public function __construct()
35
    {
36
        $this->adapter = EccFactory::getAdapter();
37
    }
38
39
    /**
40
     * {@inheritdoc}
41
     */
42
    public function getAgreementKey($encryption_key_length, JWKInterface $private_key, JWKInterface $public_key = null, array $complete_header = [], array &$additional_header_values = [])
43
    {
44
        $this->checkKey($private_key, true);
45
        if (null === $public_key) {
46
            $public_key = $this->getPublicKey($complete_header);
47
        } else {
48
            $this->checkKey($public_key, false);
49
            $additional_header_values = array_merge($additional_header_values, [
50
                'epk' => [
51
                    'kty' => $private_key->get('kty'),
52
                    'crv' => $private_key->get('crv'),
53
                    'x'   => $private_key->get('x'),
54
                    'y'   => $private_key->get('y'),
55
                ],
56
            ]);
57
        }
58
        if ($private_key->get('crv') !== $public_key->get('crv')) {
59
            throw new \InvalidArgumentException('Curves are different');
60
        }
61
62
        $agreed_key = $this->calculateAgreementKey($private_key, $public_key);
63
64
        $apu = array_key_exists('apu', $complete_header) ? $complete_header['apu'] : '';
65
        $apv = array_key_exists('apv', $complete_header) ? $complete_header['apv'] : '';
66
67
        return ConcatKDF::generate($this->convertDecToBin($agreed_key), $complete_header['enc'], $encryption_key_length, $apu, $apv);
68
    }
69
70
    /**
71
     * @param \Jose\Object\JWKInterface $private_key
72
     * @param \Jose\Object\JWKInterface $public_key
73
     *
74
     * @throws \InvalidArgumentException
75
     *
76
     * @return int|string|void
77
     */
78
    public function calculateAgreementKey(JWKInterface $private_key, JWKInterface $public_key)
79
    {
80
        $p = $this->getGenerator($private_key);
81
82
        $rec_x = $this->convertBase64ToDec($public_key->get('x'));
83
        $rec_y = $this->convertBase64ToDec($public_key->get('y'));
84
        $sen_d = $this->convertBase64ToDec($private_key->get('d'));
85
86
        $private_key = $p->getPrivateKeyFrom($sen_d);
87
88
        $public_key = $p->getPublicKeyFrom($rec_x, $rec_y);
89
90
        $message = new MessageFactory($this->adapter);
91
        $exchange = $private_key->createExchange($message, $public_key);
92
93
        return $exchange->calculateSharedKey();
94
    }
95
96
    /**
97
     * {@inheritdoc}
98
     */
99
    public function getAlgorithmName()
100
    {
101
        return 'ECDH-ES';
102
    }
103
104
    /**
105
     * {@inheritdoc}
106
     */
107
    public function getKeyManagementMode()
108
    {
109
        return self::MODE_AGREEMENT;
110
    }
111
112
    /**
113
     * @param array $complete_header
114
     *
115
     * @return \Jose\Object\JWKInterface
116
     */
117
    private function getPublicKey(array $complete_header)
118
    {
119
        if (!array_key_exists('epk', $complete_header)) {
120
            throw new \InvalidArgumentException('"epk" parameter missing');
121
        }
122
        if (!is_array($complete_header['epk'])) {
123
            throw new \InvalidArgumentException('"epk" parameter is not an array of parameter');
124
        }
125
        $public_key = new JWK($complete_header['epk']);
126
        $this->checkKey($public_key, false);
127
128
        return $public_key;
129
    }
130
131
    /**
132
     * @param \Jose\Object\JWKInterface $key
133
     * @param bool                      $is_private
134
     */
135
    private function checkKey(JWKInterface $key, $is_private)
136
    {
137
        if ('EC' !== $key->get('kty')) {
138
            throw new \InvalidArgumentException('The key type must be "EC"');
139
        }
140
        if (!$key->has('x') || !$key->has('y') || !$key->has('crv')) {
141
            throw new \InvalidArgumentException('Key components ("x", "y" or "crv") missing');
142
        }
143
        if (!$key->has('d') && true === $is_private) {
144
            throw new \InvalidArgumentException('The key must be private');
145
        }
146
    }
147
148
    /**
149
     * @param JWKInterface $key
150
     *
151
     * @throws \InvalidArgumentException
152
     *
153
     * @return \Mdanter\Ecc\Primitives\GeneratorPoint
154
     */
155
    private function getGenerator(JWKInterface $key)
156
    {
157
        $crv = $key->get('crv');
158
159
        switch ($crv) {
160
            case 'P-256':
161
                return EccFactory::getNistCurves()->generator256();
162
            case 'P-384':
163
                return EccFactory::getNistCurves()->generator384();
164
            case 'P-521':
165
                return EccFactory::getNistCurves()->generator521();
166
            default:
167
                throw new \InvalidArgumentException(sprintf('Curve "%s" is not supported', $crv));
168
        }
169
    }
170
171
    /**
172
     * @param $value
173
     *
174
     * @return int|string
175
     */
176
    private function convertHexToDec($value)
177
    {
178
        return $this->adapter->hexDec($value);
179
    }
180
181
    /**
182
     * @param $value
183
     *
184
     * @return int|string
185
     */
186
    private function convertBase64ToDec($value)
187
    {
188
        $value = unpack('H*', Base64Url::decode($value));
189
190
        return $this->convertHexToDec($value[1]);
191
    }
192
193
    /**
194
     * @param $value
195
     *
196
     * @return string
197
     */
198
    private function convertDecToBin($value)
199
    {
200
        $adapter = EccFactory::getAdapter();
201
202
        return hex2bin($adapter->decHex($value));
203
    }
204
}
205