Passed
Push — master ( e9ba59...c2c890 )
by Tony Karavasilev (Тони
19:20
created

KeyExchange::generateKeyPair()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 30
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 16
c 1
b 0
f 0
dl 0
loc 30
ccs 0
cts 20
cp 0
rs 9.7333
cc 3
nc 4
nop 2
crap 12
1
<?php
2
3
/**
4
 * Cryptographic protocol for secure key exchange.
5
 */
6
7
namespace CryptoManana\CryptographicProtocol;
8
9
use \CryptoManana\Core\Abstractions\Containers\AbstractCryptographicProtocol as CryptographicProtocol;
10
use \CryptoManana\Core\Abstractions\MessageDigestion\AbstractKeyMaterialDerivationFunction as KeyDerivationFunction;
11
use \CryptoManana\Core\Interfaces\Containers\KeyExchangeInterface as KeyExchangeInformationProcessing;
12
use \CryptoManana\Core\Interfaces\Containers\KeyExpansionInjectableInterface as KeyExpansionFunctionSetter;
13
use \CryptoManana\Core\Traits\Containers\KeyExpansionInjectableTrait as KeyExpansionFunctionSetterImplementation;
14
use \CryptoManana\Core\Traits\CommonValidations\KeyPairFormatValidationTrait as KeyFormatValidations;
15
use \CryptoManana\Core\Traits\CommonValidations\KeyPairSizeValidationTrait as KeyPairSizeValidations;
16
use \CryptoManana\DataStructures\ExchangeInformation as ExchangeInformationStructure;
17
18
/**
19
 * Class KeyExchange - The key exchange protocol object, based on the Diffie-Hellman algorithm.
20
 *
21
 * @package CryptoManana\CryptographicProtocol
22
 *
23
 * @mixin KeyExpansionFunctionSetterImplementation
24
 * @mixin KeyFormatValidations
25
 * @mixin KeyPairSizeValidations
26
 */
27
class KeyExchange extends CryptographicProtocol implements KeyExchangeInformationProcessing, KeyExpansionFunctionSetter
28
{
29
    /**
30
     * The message key expansion derivation service dependency injection via a setter method implementation.
31
     *
32
     * {@internal Reusable implementation of `KeyExpansionInjectableInterface`. }}
33
     */
34
    use KeyExpansionFunctionSetterImplementation;
35
36
    /**
37
     * Asymmetric key pair format validations.
38
     *
39
     * {@internal Reusable implementation of the common key pair format validation. }}
40
     */
41
    use KeyFormatValidations;
42
43
    /**
44
     * Asymmetric key pair size in bits validations.
45
     *
46
     * {@internal Reusable implementation of the common key pair size in bits validation. }}
47
     */
48
    use KeyPairSizeValidations;
49
50
    /**
51
     * The key pair size in bytes length property storage.
52
     *
53
     * @param int $keySize The key pair size in bits.
54
     */
55
    protected $keyPairSize = 2048;
56
57
    /**
58
     * The key expansion derivation algorithm service property storage.
59
     *
60
     * @var KeyDerivationFunction|null The key expansion derivation service.
61
     */
62
    protected $keyExpansionSource = null;
63
64
    /**
65
     * Internal method for generating a new key pair resource base on the given configuration.
66
     *
67
     * @param array $settings The key generation configuration settings.
68
     *
69
     * @return resource The private key resource containing all necessary information (prime, generator and public key).
70
     * @throws \Exception Validation or system errors.
71
     *
72
     * @codeCoverageIgnore
73
     */
74
    protected function generateKeyPairResource(array $settings)
75
    {
76
        $settings = array_merge(
77
            [
78
                'private_key_bits' => $this->keyPairSize,
79
                'private_key_type' => OPENSSL_KEYTYPE_DH,
80
            ],
81
            $settings
82
        );
83
84
        $privateKeyResource = openssl_pkey_new($settings);
85
86
        if ($privateKeyResource === false) {
87
            throw new \RuntimeException(
88
                'Failed to generate a key pair, probably because of a misconfigured ' .
89
                'OpenSSL library or invalid prime and generator values.'
90
            );
91
        }
92
93
        return $privateKeyResource;
94
    }
95
96
    /**
97
     * Internal method for extracting the key pair details from from the private key resource.
98
     *
99
     * @param resource $privateKeyResource The private key resource.
100
     *
101
     * @return array The key pair details as an array.
102
     * @throws \Exception Validation or system errors.
103
     *
104
     * @internal The private key resource is passed via reference from the main logical method for performance reasons.
105
     *
106
     * @codeCoverageIgnore
107
     */
108
    protected function getKeyPairInformation(&$privateKeyResource)
109
    {
110
        $myOptions = openssl_pkey_get_details($privateKeyResource);
111
112
        if ($myOptions === false) {
113
            throw new \RuntimeException(
114
                'Failed to generate a key pair, probably because of a misconfigured ' .
115
                'OpenSSL library or invalid prime and generator values.'
116
            );
117
        }
118
119
        return $myOptions;
120
    }
121
122
    /**
123
     * Internal method for exporting the private key as a Base64 string from the private key resource.
124
     *
125
     * @param resource $privateKeyResource The private key resource.
126
     *
127
     * @return string The exported private key as a Base64 string.
128
     * @throws \Exception Validation or system errors.
129
     *
130
     * @internal The private key resource is passed via reference from the main logical method for performance reasons.
131
     *
132
     * @codeCoverageIgnore
133
     */
134
    protected function exportPrivateKeyString(&$privateKeyResource)
135
    {
136
        $privateKeyString = '';
137
        $privateExport = openssl_pkey_export($privateKeyResource, $privateKeyString);
138
139
        if (empty($privateKeyString) || $privateExport === false) {
140
            throw new \RuntimeException(
141
                'Failed to export the private key to a string, probably because of a misconfigured OpenSSL library.'
142
            );
143
        }
144
145
        return $privateKeyString;
146
    }
147
148
    /**
149
     * Generates fresh Diffie–Hellman key exchange information.
150
     *
151
     * @param null|string $prime The hexadecimal representation of a prime number or null to generate a new one.
152
     * @param null|string $generator The hexadecimal representation of a generator number or null to generate a new one.
153
     *
154
     * @return array The generated key pair information.
155
     * @throws \Exception Validation or generation errors.
156
     */
157
    protected function generateKeyPair($prime = null, $generator = null)
158
    {
159
        $settings = [];
160
161
        if (is_string($prime)) {
162
            $settings['dh']['p'] = hex2bin($prime);
163
        }
164
165
        if (is_string($generator)) {
166
            $settings['dh']['g'] = hex2bin($generator);
167
        }
168
169
        $privateKeyResource = $this->generateKeyPairResource($settings);
170
        $privateKeyString = $this->exportPrivateKeyString($privateKeyResource);
171
        $keyPairDetails = $this->getKeyPairInformation($privateKeyResource);
172
173
        // Free the private key (resource cleanup)
174
        openssl_free_key($privateKeyResource);
175
        $privateKeyResource = null;
1 ignored issue
show
Unused Code introduced by
The assignment to $privateKeyResource is dead and can be removed.
Loading history...
176
177
        /**
178
         * {@internal The array has always the accessed keys because of the OpenSSL library details format. }}
179
         */
180
        $details = [];
181
        $details['prime'] = bin2hex($keyPairDetails['dh']['p']);
182
        $details['generator'] = bin2hex($keyPairDetails['dh']['g']);
183
        $details['private'] = base64_encode($privateKeyString);
184
        $details['public'] = base64_encode($keyPairDetails['dh']['pub_key']);
185
186
        return $details;
187
    }
188
189
    /**
190
     * Generates and builds a key exchange information object.
191
     *
192
     * @param null|string $prime The hexadecimal representation of a prime number or null to generate a new one.
193
     * @param null|string $generator The hexadecimal representation of a generator number or null to generate a new one.
194
     *
195
     * @return ExchangeInformationStructure The key exchange information object.
196
     * @throws \Exception Validation or generation errors.
197
     */
198
    protected function buildExchangeInformation($prime = null, $generator = null)
199
    {
200
        $information = $this->generateKeyPair($prime, $generator);
201
202
        $exchangeInformation = new ExchangeInformationStructure();
203
204
        $exchangeInformation->prime = $information['prime'];
205
        $exchangeInformation->generator = $information['generator'];
206
        $exchangeInformation->private = $information['private'];
207
        $exchangeInformation->public = $information['public'];
208
209
        return $exchangeInformation;
210
    }
211
212
    /**
213
     * Setter for the key pair size property.
214
     *
215
     * @param int $keySize The key size in bits.
216
     *
217
     * @return $this The container object.
218
     * @throws \Exception Validation errors.
219
     */
220
    public function setKeyExchangeSize($keySize)
221
    {
222
        $this->validateKeyPairSize($keySize);
223
224
        $this->keyPairSize = (int)$keySize;
225
226
        return $this;
227
    }
228
229
    /**
230
     * Getter for the key pair size property.
231
     *
232
     * @return int The key pair size in bits.
233
     */
234
    public function getKeyExchangeSize()
235
    {
236
        return $this->keyPairSize;
237
    }
238
239
    /**
240
     * Container constructor.
241
     *
242
     * @param KeyDerivationFunction|null $hasher The message key expansion derivation service.
243
     *
244
     * @throws \Exception Initialization validation.
245
     */
246
    public function __construct(KeyDerivationFunction $hasher = null)
247
    {
248
        if ($hasher !== null) {
249
            $this->keyExpansionSource = $hasher;
250
        } else {
251
            throw new \RuntimeException('No key expansion derivation service has been set.');
252
        }
253
    }
254
255
    /**
256
     * Container destructor.
257
     */
258
    public function __destruct()
259
    {
260
        unset($this->keyExpansionSource);
261
    }
262
263
    /**
264
     * Container cloning via deep copy.
265
     */
266
    public function __clone()
267
    {
268
        $this->keyExpansionSource = clone $this->keyExpansionSource;
269
    }
270
271
    /**
272
     * Generates fresh key exchange information for sending to the remote party.
273
     *
274
     * @return ExchangeInformationStructure The key exchange information object.
275
     * @throws \Exception Validation errors.
276
     *
277
     * @internal Remember never to send the private key to the remote party!
278
     */
279
    public function generateExchangeRequestInformation()
280
    {
281
        return $this->buildExchangeInformation();
282
    }
283
284
    /**
285
     * Generates fresh key exchange information based on the received prime and generator values.
286
     *
287
     * @param string $prime The hexadecimal representation of a prime number, also knows as `p`.
288
     * @param string $generator The hexadecimal generator number, a primitive root modulo of `p`, also known as `g`.
289
     *
290
     * @return ExchangeInformationStructure The key exchange information object.
291
     * @throws \Exception Validation errors.
292
     *
293
     * @internal Remember never to send the private key to the remote party!
294
     */
295
    public function generateExchangeResponseInformation($prime, $generator)
296
    {
297
        if (!is_string($prime)) {
1 ignored issue
show
introduced by
The condition is_string($prime) is always true.
Loading history...
298
            throw new \InvalidArgumentException('The prime number representation must be a hexadecimal string.');
299
        } elseif (!is_string($generator)) {
1 ignored issue
show
introduced by
The condition is_string($generator) is always true.
Loading history...
300
            throw new \InvalidArgumentException('The generator number representation must be a hexadecimal string.');
301
        }
302
303
        return $this->buildExchangeInformation($prime, $generator);
304
    }
305
306
    /**
307
     * Computes the secret shared key for usage of both parties.
308
     *
309
     * @param string $remotePublicKey The remote side's public key, based on the same prime and generator combination.
310
     * @param string $localPrivateKey The local side's private key, based on the same prime and generator combination.
311
     *
312
     * @return string The shared secret key.
313
     * @throws \Exception Validation errors.
314
     *
315
     * @internal The key is digested before returning for both authentication, length control and output formatting.
316
     */
317
    public function computeSharedSecret($remotePublicKey, $localPrivateKey)
318
    {
319
        $this->validatePublicKeyFormat($remotePublicKey);
320
        $this->validatePrivateKeyFormat($localPrivateKey);
321
322
        $privateKeyResource = openssl_pkey_get_private(base64_decode($localPrivateKey));
323
324
        if ($privateKeyResource === false) {
325
            throw new \RuntimeException(
326
                'Failed to use the current private key, probably because of ' .
327
                'a misconfigured OpenSSL library or an invalid key.'
328
            );
329
        }
330
331
        $sharedKey = openssl_dh_compute_key(base64_decode($remotePublicKey), $privateKeyResource);
332
333
        if ($sharedKey === false) {
334
            throw new \RuntimeException('The public key is invalid or based on different prime and generator values.');
335
        }
336
337
        // Free the private key (resource cleanup)
338
        openssl_free_key($privateKeyResource);
339
        $privateKeyResource = null;
1 ignored issue
show
Unused Code introduced by
The assignment to $privateKeyResource is dead and can be removed.
Loading history...
340
341
        return $this->keyExpansionSource->hashData($sharedKey);
1 ignored issue
show
Bug introduced by
The method hashData() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

341
        return $this->keyExpansionSource->/** @scrutinizer ignore-call */ hashData($sharedKey);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
342
    }
343
}
344