KeyExchange::exportPrivateKeyString()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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