Completed
Branch develop (8ddc4a)
by Florent
02:46
created

ECDHES   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 177
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Importance

Changes 4
Bugs 1 Features 1
Metric Value
wmc 18
c 4
b 1
f 1
lcom 1
cbo 12
dl 0
loc 177
rs 10

11 Methods

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