Failed Conditions
Push — v7 ( 318c5f...2b71c7 )
by Florent
03:43
created

ECDHES   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 189
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 0
Metric Value
wmc 29
lcom 1
cbo 10
dl 0
loc 189
rs 10
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
C getAgreementKey() 0 35 8
B calculateAgreementKey() 0 29 5
A getAlgorithmName() 0 4 1
A getKeyManagementMode() 0 4 1
A getPublicKey() 0 10 1
C checkKey() 0 25 7
A getGenerator() 0 15 4
A convertBase64ToGmp() 0 6 1
A convertDecToBin() 0 7 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\Crypto\EcDH\EcDH;
21
use Mdanter\Ecc\EccFactory;
22
use Mdanter\Ecc\Primitives\GeneratorPoint;
23
24
/**
25
 * Class ECDHES.
26
 */
27
final class ECDHES implements KeyAgreementInterface
28
{
29
    /**
30
     * {@inheritdoc}
31
     */
32
    public function getAgreementKey(int $encryption_key_length, string $algorithm, JWKInterface $recipient_key, array $complete_header = [], array &$additional_header_values = []): string
33
    {
34
        if ($recipient_key->has('d')) {
35
            $this->checkKey($recipient_key, true);
36
            $private_key = $recipient_key;
37
            $public_key = $this->getPublicKey($complete_header);
38
        } else {
39
            $this->checkKey($recipient_key, false);
40
            $public_key = $recipient_key;
41
            switch ($public_key->get('crv')) {
42
                case 'P-256':
43
                case 'P-384':
44
                case 'P-521':
45
                    $private_key = JWKFactory::createECKey(['crv' => $public_key->get('crv')]);
46
                    break;
47
                case 'X25519':
48
                    $private_key = JWKFactory::createOKPKey(['crv' => 'X25519']);
49
                    break;
50
                default:
51
                    throw new \InvalidArgumentException(sprintf('The curve "%s" is not supported', $public_key->get('crv')));
52
            }
53
            $epk = $private_key->toPublic()->getAll();
54
            $additional_header_values = array_merge($additional_header_values, [
55
                'epk' => $epk,
56
            ]);
57
        }
58
        Assertion::eq($private_key->get('crv'), $public_key->get('crv'), 'Curves are different');
59
60
        $agreed_key = $this->calculateAgreementKey($private_key, $public_key);
61
62
        $apu = array_key_exists('apu', $complete_header) ? $complete_header['apu'] : '';
63
        $apv = array_key_exists('apv', $complete_header) ? $complete_header['apv'] : '';
64
65
        return ConcatKDF::generate($agreed_key, $algorithm, $encryption_key_length, $apu, $apv);
66
    }
67
68
    /**
69
     * @param \Jose\Object\JWKInterface $private_key
70
     * @param \Jose\Object\JWKInterface $public_key
71
     *
72
     * @throws \InvalidArgumentException
73
     *
74
     * @return string
75
     */
76
    public function calculateAgreementKey(JWKInterface $private_key, JWKInterface $public_key): string
77
    {
78
        switch ($public_key->get('crv')) {
79
            case 'P-256':
80
            case 'P-384':
81
            case 'P-521':
82
                $p = $this->getGenerator($private_key);
83
84
                $rec_x = $this->convertBase64ToGmp($public_key->get('x'));
85
                $rec_y = $this->convertBase64ToGmp($public_key->get('y'));
86
                $sen_d = $this->convertBase64ToGmp($private_key->get('d'));
87
88
                $priv_key = $p->getPrivateKeyFrom($sen_d);
89
                $pub_key = $p->getPublicKeyFrom($rec_x, $rec_y);
90
91
                $ecdh = new EcDH(EccFactory::getAdapter());
0 ignored issues
show
Bug introduced by
It seems like \Mdanter\Ecc\EccFactory::getAdapter() can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
92
                $ecdh->setSenderKey($priv_key);
93
                $ecdh->setRecipientKey($pub_key);
94
95
                return $this->convertDecToBin($ecdh->calculateSharedKey());
96
            case 'X25519':
97
                return curve25519_shared(
98
                    Base64Url::decode($private_key->get('d')),
99
                    Base64Url::decode($public_key->get('x'))
100
                );
101
            default:
102
                throw new \InvalidArgumentException(sprintf('The curve "%s" is not supported', $public_key->get('crv')));
103
        }
104
    }
105
106
    /**
107
     * {@inheritdoc}
108
     */
109
    public function getAlgorithmName(): string
110
    {
111
        return 'ECDH-ES';
112
    }
113
114
    /**
115
     * {@inheritdoc}
116
     */
117
    public function getKeyManagementMode(): string
118
    {
119
        return self::MODE_AGREEMENT;
120
    }
121
122
    /**
123
     * @param array $complete_header
124
     *
125
     * @return JWKInterface
126
     */
127
    private function getPublicKey(array $complete_header): JWKInterface
128
    {
129
        Assertion::keyExists($complete_header, 'epk', 'The header parameter "epk" is missing');
130
        Assertion::isArray($complete_header['epk'], 'The header parameter "epk" is not an array of parameter');
131
132
        $public_key = new JWK($complete_header['epk']);
133
        $this->checkKey($public_key, false);
134
135
        return $public_key;
136
    }
137
138
    /**
139
     * @param JWKInterface $key
140
     * @param bool         $is_private
141
     */
142
    private function checkKey(JWKInterface $key, bool $is_private)
143
    {
144
        Assertion::true($key->has('x'), 'The key parameter "x" is missing.');
145
        Assertion::true($key->has('crv'), 'The key parameter "crv" is missing.');
146
147
        switch ($key->get('crv')) {
148
            case 'P-256':
149
            case 'P-384':
150
            case 'P-521':
151
                if (!class_exists('Mdanter\Ecc\Crypto\EcDH\EcDH')) {
152
                    throw new \RuntimeException('the library "mdanter/ecc" must be installed to use ECDH-ES algorithms.');
153
                }
154
                Assertion::eq($key->get('kty'), 'EC', 'Wrong key type.');
155
                Assertion::true($key->has('y'), 'The key parameter "y" is missing.');
156
                break;
157
            case 'X25519':
158
                Assertion::eq($key->get('kty'), 'OKP', 'Wrong key type.');
159
                break;
160
            default:
161
                throw new \InvalidArgumentException(sprintf('The curve "%s" is not supported', $key->get('crv')));
162
        }
163
        if (true === $is_private) {
164
            Assertion::true($key->has('d'), 'The key parameter "d" is missing.');
165
        }
166
    }
167
168
    /**
169
     * @param JWKInterface $key
170
     *
171
     * @throws \InvalidArgumentException
172
     *
173
     * @return GeneratorPoint
174
     */
175
    private function getGenerator(JWKInterface $key): GeneratorPoint
176
    {
177
        $crv = $key->get('crv');
178
179
        switch ($crv) {
180
            case 'P-256':
181
                return EccFactory::getNistCurves()->generator256();
182
            case 'P-384':
183
                return EccFactory::getNistCurves()->generator384();
184
            case 'P-521':
185
                return EccFactory::getNistCurves()->generator521();
186
            default:
187
                throw new \InvalidArgumentException(sprintf('The curve "%s" is not supported', $crv));
188
        }
189
    }
190
191
    /**
192
     * @param string $value
193
     *
194
     * @return \GMP
195
     */
196
    private function convertBase64ToGmp(string $value): \GMP
197
    {
198
        $value = unpack('H*', Base64Url::decode($value));
199
200
        return gmp_init($value[1], 16);
201
    }
202
203
    /**
204
     * @param string $value
205
     *
206
     * @return string
207
     */
208
    private function convertDecToBin(string $value): string
209
    {
210
        $value = gmp_strval($value, 10);
211
        $adapter = EccFactory::getAdapter();
212
213
        return hex2bin($adapter->decHex($value));
214
    }
215
}
216