Completed
Push — master ( a44b29...b8fcd2 )
by Tony Karavasilev (Тони
16:57
created

TokenGenerator::generatePrivateKey()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 7
c 0
b 0
f 0
dl 0
loc 14
ccs 0
cts 0
cp 0
rs 10
cc 2
nc 2
nop 2
crap 6
1
<?php
2
3
/**
4
 * Utility class for cryptography key and token generation.
5
 */
6
7
namespace CryptoManana\Utilities;
8
9
use \CryptoManana\Core\Abstractions\Containers\AbstractRandomnessInjectable as RandomnessContainer;
10
use \CryptoManana\Core\Interfaces\Randomness\AsymmetricKeyPairGenerationInterface as KeyPairGeneration;
11
use \CryptoManana\Core\Interfaces\Randomness\EncryptionKeyGenerationInterface as EncryptionKeyGeneration;
12
use \CryptoManana\Core\Interfaces\Randomness\HashingKeyGenerationInterface as HashingKeyGeneration;
13
use \CryptoManana\Core\Interfaces\Randomness\TokenGenerationInterface as TokenStringGeneration;
14
use \CryptoManana\Core\Interfaces\MessageEncryption\KeyPairInterface as KeyPairSpecification;
15
use \CryptoManana\Core\StringBuilder as StringBuilder;
16
17
/**
18
 * Class TokenGenerator - Utility class for cryptography token generation.
19
 *
20
 * @package CryptoManana\Utilities
21
 *
22
 * @property \CryptoManana\Core\Abstractions\Randomness\AbstractGenerator $randomnessSource The randomness generator.
23
 */
24
class TokenGenerator extends RandomnessContainer implements
25
    TokenStringGeneration,
26
    HashingKeyGeneration,
27
    EncryptionKeyGeneration,
28
    KeyPairGeneration
29
{
30
    /**
31
     * Internal method for validation of positive output length.
32
     *
33
     * @param int $length The output length value for validation.
34
     *
35
     * @throws \Exception Validation errors.
36
     */
37 18
    protected function applyLengthValidation($length)
38
    {
39 18
        $length = filter_var(
40 18
            $length,
41 18
            FILTER_VALIDATE_INT,
42
            [
43
                "options" => [
44 18
                    "min_range" => 1,
45 18
                    "max_range" => PHP_INT_MAX,
46
                ],
47
            ]
48
        );
49
50 18
        if ($length === false) {
51 2
            throw new \LengthException(
52 2
                'The length of the desired output data must me at least 1 character long.'
53
            );
54
        }
55 16
    }
56
57
    /**
58
     * Internal method for asymmetric algorithm type validation.
59
     *
60
     * @param int $algorithmType The asymmetric algorithm type integer code.
61
     *
62
     * @throws \Exception Validation errors.
63
     */
64 10
    protected function validateAsymmetricAlgorithmType($algorithmType)
65
    {
66 10
        $algorithmType = filter_var(
67 10
            $algorithmType,
68 10
            FILTER_VALIDATE_INT,
69
            [
70
                "options" => [
71 10
                    "min_range" => self::RSA_KEY_PAIR_TYPE,
72 10
                    "max_range" => self::DSA_KEY_PAIR_TYPE,
73
                ],
74
            ]
75
        );
76
77 10
        if ($algorithmType === false) {
78 2
            throw new \InvalidArgumentException(
79
                'The asymmetric algorithm type must be a valid integer between ' .
80 2
                self::RSA_KEY_PAIR_TYPE . ' and ' . self::DSA_KEY_PAIR_TYPE . '.'
81
            );
82
        }
83 8
    }
84
85
    /**
86
     * Internal method for asymmetric algorithm type validation.
87
     *
88
     * @param int $keySize The key size in bits.
89
     *
90
     * @throws \Exception Validation errors.
91
     */
92 8
    protected function validateKeyPairSize($keySize)
93
    {
94 8
        $keySize = filter_var(
95 8
            $keySize,
96 8
            FILTER_VALIDATE_INT,
97
            [
98
                "options" => [
99 8
                    "min_range" => 384,
100
                    "max_range" => 15360,
101
                ],
102
            ]
103
        );
104
105 8
        if ($keySize === false || $keySize % 128 !== 0) {
106 6
            throw new \InvalidArgumentException(
107
                'The key size must be between 384 (fastest but weakest) ' .
108 6
                'and 15360 (slowest but strongest) bits and be dividable by 128 bits.'
109
            );
110
        }
111 2
    }
112
113
    /**
114
     * Internal method for generating a fresh private key pair of the given size and type.
115
     *
116
     * @param int $keySize The private key size in bits.
117
     * @param string $algorithmType The asymmetric algorithm type.
118
     *
119
     * @return resource The private key resource.
120
     * @throws \Exception Validation or system errors.
121
     *
122
     * @codeCoverageIgnore
123
     */
124
    protected function generatePrivateKey($keySize, $algorithmType)
125
    {
126
        $privateKeyResource = openssl_pkey_new([
127
            'private_key_bits' => $keySize, // Size of the key (>= 384)
128
            'private_key_type' => $algorithmType, // The algorithm type (RSA/DSA) type
129
        ]);
130
131
        if ($privateKeyResource === false) {
132
            throw new \RuntimeException(
133
                'Failed to generate a private key, probably because of a misconfigured OpenSSL library.'
134
            );
135
        }
136
137
        return $privateKeyResource;
138
    }
139
140
    /**
141
     * Internal method for generating a fresh public key pair of the given size by extracting it from the  private key.
142
     *
143
     * @param int $keySize The private key size in bits.
144
     * @param resource $privateKeyResource The private key resource.
145
     *
146
     * @return string The extracted public key string.
147
     * @throws \Exception Validation or system errors.
148
     *
149
     * @internal The private key resource is passed via reference from the main logical method for performance reasons.
150
     *
151
     * @codeCoverageIgnore
152
     */
153
    protected function generatePublicKey($keySize, &$privateKeyResource)
154
    {
155
        $privateKeyInformation = openssl_pkey_get_details($privateKeyResource);
156
157
        if ($privateKeyInformation === false) {
158
            throw new \RuntimeException(
159
                'Failed to generate/extract and export a public key, probably because of a misconfigured ' .
160
                'OpenSSL library or an invalid private key.'
161
            );
162
        } elseif ($privateKeyInformation['bits'] !== $keySize) {
163
            throw new \DomainException('The extracted public key is not of the correct size: `' . $keySize . '`.');
164
        }
165
166
        // Free the private key (resource cleanup)
167
        openssl_free_key($privateKeyResource);
168
        $privateKeyResource = null;
169
170
        return (string)$privateKeyInformation['key']; // <- The public key
171
    }
172
173
    /**
174
     * Internal method for generation of characters used for secure password string building.
175
     *
176
     * @param int|mixed $case Generation case as integer.
177
     *
178
     * @return string Password character.
179
     * @throws \Exception Validation Errors.
180
     */
181 2
    protected function getPasswordCharacter($case)
182
    {
183
        switch ($case) {
184 2
            case 1:
185 2
                return $this->randomnessSource->getDigit(true);
186 2
            case 2:
187 2
                return $this->randomnessSource->getLetter(false);
188 2
            case 3:
189 2
                return StringBuilder::stringToUpper($this->randomnessSource->getLetter(false));
190
191
            default:
192 2
                return $this->randomnessSource->getString(1, ['!', '@', '#', '$', '%', '^']);
193
        }
194
    }
195
196
    /**
197
     * Generate a random token string in alphanumeric or hexadecimal format.
198
     *
199
     * Note: This method can generate HEX output if the `$useAlphaNumeric` parameter is set to `false`.
200
     *
201
     * @param int $length The desired output length (default => 32).
202
     * @param bool|int $useAlphaNumeric Flag for switching to alphanumerical (default => true).
203
     *
204
     * @return string Randomly generated alphanumeric/hexadecimal token string.
205
     * @throws \Exception Validation errors.
206
     */
207 8
    public function getTokenString($length = self::MODERATE_TOKEN_LENGTH, $useAlphaNumeric = true)
208
    {
209 8
        $this->applyLengthValidation($length);
210
211 6
        if ($useAlphaNumeric) {
212 6
            $token = $this->randomnessSource->getAlphaNumeric($length, true);
213
        } else {
214 2
            $token = $this->randomnessSource->getHex($length, true);
215
        }
216
217 6
        return StringBuilder::stringReverse($token);
218
    }
219
220
    /**
221
     * Generate a random password string.
222
     *
223
     * Note: This method can use more special symbols on generation if the `$stronger` parameter is set to `true`.
224
     *
225
     * @param int $length The desired output length (default => 12).
226
     * @param bool|int $stronger Flag for using all printable ASCII characters (default => true).
227
     *
228
     * @return string Randomly generated password string.
229
     * @throws \Exception Validation errors.
230
     */
231 2
    public function getPasswordString($length = self::MODERATE_PASSWORD_LENGTH, $stronger = true)
232
    {
233 2
        $this->applyLengthValidation($length);
234
235 2
        if ($stronger) {
236 2
            $password = $this->randomnessSource->getAscii($length);
237
        } else {
238 2
            $password = '';
239
240 2
            for ($i = 1; $i <= $length; $i++) {
241 2
                $case = $this->randomnessSource->getInt(1, 4);
242
243 2
                $password .= $this->getPasswordCharacter($case);
244
            }
245
        }
246
247 2
        return $password;
248
    }
249
250
    /**
251
     * Generate a random HMAC key for hashing purposes.
252
     *
253
     * Note: The output string can be in raw bytes of the `$printable` parameter is set to `true`.
254
     *
255
     * @param int $length The desired output length (default => 16).
256
     * @param bool|int $printable Flag for using only printable characters instead of bytes (default => true).
257
     *
258
     * @return string Randomly generated HMAC key.
259
     * @throws \Exception Validation errors.
260
     */
261 2
    public function getHashingKey($length = self::DIGESTION_KEY_128_BITS, $printable = true)
262
    {
263 2
        $this->applyLengthValidation($length);
264
265 2
        return ($printable) ? $this->randomnessSource->getAscii($length) : $this->randomnessSource->getBytes($length);
266
    }
267
268
    /**
269
     * Generate a random salt string for hashing purposes.
270
     *
271
     * Note: The output string can be in raw bytes of the `$printable` parameter is set to `true`.
272
     *
273
     * @param int $length The desired output length (default => 16).
274
     * @param bool|int $printable Flag for using only printable characters instead of bytes (default => true).
275
     *
276
     * @return string Randomly generated hashing salt.
277
     * @throws \Exception Validation errors.
278
     */
279 2
    public function getHashingSalt($length = self::DIGESTION_SALT_128_BITS, $printable = true)
280
    {
281 2
        $this->applyLengthValidation($length);
282
283 2
        return ($printable) ? $this->randomnessSource->getAscii($length) : $this->randomnessSource->getBytes($length);
284
    }
285
286
    /**
287
     * Generate a random encryption key for symmetrical cyphers.
288
     *
289
     * Note: The output string can be in raw bytes of the `$printable` parameter is set to `true`.
290
     *
291
     * @param int $length The desired output length (default => 16).
292
     * @param bool|int $printable Flag for using only printable characters instead of bytes (default => true).
293
     *
294
     * @return string Randomly generated encryption key.
295
     * @throws \Exception Validation errors.
296
     */
297 2
    public function getEncryptionKey($length = self::SECRET_KEY_128_BITS, $printable = true)
298
    {
299 2
        $this->applyLengthValidation($length);
300
301 2
        return ($printable) ? $this->randomnessSource->getAscii($length) : $this->randomnessSource->getBytes($length);
302
    }
303
304
    /**
305
     * Generate a random initialization vector (IV) for encryption purposes.
306
     *
307
     * Note: The output string can be in raw bytes of the `$printable` parameter is set to `true`.
308
     *
309
     * @param int $length The desired output length (default => 16).
310
     * @param bool|int $printable Flag for using only printable characters instead of bytes (default => true).
311
     *
312
     * @return string Randomly generated encryption initialization vector.
313
     * @throws \Exception Validation errors.
314
     */
315 2
    public function getEncryptionInitializationVector($length = self::IV_128_BITS, $printable = true)
316
    {
317 2
        $this->applyLengthValidation($length);
318
319 2
        return ($printable) ? $this->randomnessSource->getAscii($length) : $this->randomnessSource->getBytes($length);
320
    }
321
322
    /**
323
     * Generate a random key pair for asymmetrical cyphers.
324
     *
325
     * @param int $keySize The key size in bits.
326
     * @param int $algorithmType The asymmetric algorithm type integer code.
327
     *
328
     * @return \stdClass Randomly generated asymmetric key pair (private and public keys) as an object.
329
     * @throws \Exception Validation errors.
330
     *
331
     * @codeCoverageIgnore
332
     */
333
    public function getAsymmetricKeyPair($keySize = self::KEY_PAIR_4096_BITS, $algorithmType = self::RSA_KEY_PAIR_TYPE)
334
    {
335
        $this->validateAsymmetricAlgorithmType($algorithmType);
336
        $this->validateKeyPairSize($keySize);
337
338
        $privateKeyResource = $this->generatePrivateKey((int)$keySize, (int)$algorithmType);
339
340
        $privateKeyString = '';
341
        $privateExport = openssl_pkey_export($privateKeyResource, $privateKeyString);
342
343
        if (empty($privateKeyString) || $privateExport === false) {
344
            throw new \RuntimeException(
345
                'Failed to export the private key to a string, probably because of a misconfigured OpenSSL library.'
346
            );
347
        }
348
349
        $publicKeyString = $this->generatePublicKey((int)$keySize, $privateKeyResource);
350
351
        $object = new \stdClass();
352
353
        $object->{KeyPairSpecification::PRIVATE_KEY_INDEX_NAME} = base64_encode($privateKeyString);
354
        $object->{KeyPairSpecification::PUBLIC_KEY_INDEX_NAME} = base64_encode($publicKeyString);
355
356
        return $object;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $object returns the type stdClass which is incompatible with the return type mandated by CryptoManana\Core\Interf...:getAsymmetricKeyPair() of string.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
357
    }
358
}
359