Completed
Push — master ( 604a9a...cf79ad )
by Tony Karavasilev (Тони
18:44
created

TokenGenerator::validateKeyPairSize()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

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