Completed
Push — master ( 1a59c2...c73113 )
by Florent
02:55
created

ECDHES::checkKey()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 11
rs 9.4285
cc 2
eloc 7
nc 2
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
            $private_key = JWKFactory::createRandomECPrivateKey($public_key->get('crv'));
51
            $additional_header_values = array_merge($additional_header_values, [
52
                'epk' => [
53
                    'kty' => $private_key->get('kty'),
54
                    'crv' => $private_key->get('crv'),
55
                    'x'   => $private_key->get('x'),
56
                    'y'   => $private_key->get('y'),
57
                ],
58
            ]);
59
        }
60
        Assertion::eq($private_key->get('crv'), $public_key->get('crv'), 'Curves are different');
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), $algorithm, $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
        $priv_key = $p->getPrivateKeyFrom($sen_d);
87
        $pub_key = $p->getPublicKeyFrom($rec_x, $rec_y);
88
89
        $message = new MessageFactory($this->adapter);
90
        $exchange = $priv_key->createExchange($message, $pub_key);
91
92
        return $exchange->calculateSharedKey();
93
    }
94
95
    /**
96
     * {@inheritdoc}
97
     */
98
    public function getAlgorithmName()
99
    {
100
        return 'ECDH-ES';
101
    }
102
103
    /**
104
     * {@inheritdoc}
105
     */
106
    public function getKeyManagementMode()
107
    {
108
        return self::MODE_AGREEMENT;
109
    }
110
111
    /**
112
     * @param array $complete_header
113
     *
114
     * @return \Jose\Object\JWKInterface
115
     */
116
    private function getPublicKey(array $complete_header)
117
    {
118
        Assertion::keyExists($complete_header, 'epk', 'The header parameter "epk" is missing');
119
        Assertion::isArray($complete_header['epk'], 'The header parameter "epk" is not an array of parameter');
120
121
        $public_key = new JWK($complete_header['epk']);
122
        $this->checkKey($public_key, false);
123
124
        return $public_key;
125
    }
126
127
    /**
128
     * @param \Jose\Object\JWKInterface $key
129
     * @param bool                      $is_private
130
     */
131
    private function checkKey(JWKInterface $key, $is_private)
132
    {
133
        Assertion::eq($key->get('kty'), 'EC', 'Wrong key type.');
134
        Assertion::true($key->has('x'), 'The key parameter "x" is missing.');
135
        Assertion::true($key->has('y'), 'The key parameter "y" is missing.');
136
        Assertion::true($key->has('crv'), 'The key parameter "crv" is missing.');
137
138
        if (true === $is_private) {
139
            Assertion::true($key->has('d'), 'The key parameter "d" is missing.');
140
        }
141
    }
142
143
    /**
144
     * @param JWKInterface $key
145
     *
146
     * @throws \InvalidArgumentException
147
     *
148
     * @return \Mdanter\Ecc\Primitives\GeneratorPoint
149
     */
150
    private function getGenerator(JWKInterface $key)
151
    {
152
        $crv = $key->get('crv');
153
154
        switch ($crv) {
155
            case 'P-256':
156
                return EccFactory::getNistCurves()->generator256();
157
            case 'P-384':
158
                return EccFactory::getNistCurves()->generator384();
159
            case 'P-521':
160
                return EccFactory::getNistCurves()->generator521();
161
            default:
162
                throw new \InvalidArgumentException(sprintf('Curve "%s" is not supported', $crv));
163
        }
164
    }
165
166
    /**
167
     * @param $value
168
     *
169
     * @return int|string
170
     */
171
    private function convertHexToDec($value)
172
    {
173
        return $this->adapter->hexDec($value);
174
    }
175
176
    /**
177
     * @param $value
178
     *
179
     * @return int|string
180
     */
181
    private function convertBase64ToDec($value)
182
    {
183
        $value = unpack('H*', Base64Url::decode($value));
184
185
        return $this->convertHexToDec($value[1]);
186
    }
187
188
    /**
189
     * @param $value
190
     *
191
     * @return string
192
     */
193
    private function convertDecToBin($value)
194
    {
195
        $adapter = EccFactory::getAdapter();
196
197
        return hex2bin($adapter->decHex($value));
198
    }
199
}
200