- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
268 lines
9.5 KiB
PHP
268 lines
9.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\Cryptography\CryptographicUtilities;
|
|
use App\Framework\Random\SecureRandomGenerator;
|
|
|
|
it('performs timing-safe string comparison', function () {
|
|
$utils = new CryptographicUtilities(new SecureRandomGenerator());
|
|
|
|
$string1 = 'secret-value';
|
|
$string2 = 'secret-value';
|
|
$string3 = 'different-value';
|
|
|
|
expect($utils->timingSafeEquals($string1, $string2))->toBeTrue();
|
|
expect($utils->timingSafeEquals($string1, $string3))->toBeFalse();
|
|
});
|
|
|
|
it('performs timing-safe array comparison', function () {
|
|
$utils = new CryptographicUtilities(new SecureRandomGenerator());
|
|
|
|
$array1 = ['key1' => 'value1', 'key2' => 'value2'];
|
|
$array2 = ['key2' => 'value2', 'key1' => 'value1']; // Different order
|
|
$array3 = ['key1' => 'value1', 'key2' => 'different'];
|
|
|
|
expect($utils->timingSafeArrayEquals($array1, $array2))->toBeTrue();
|
|
expect($utils->timingSafeArrayEquals($array1, $array3))->toBeFalse();
|
|
});
|
|
|
|
it('generates cryptographically secure nonce', function () {
|
|
$utils = new CryptographicUtilities(new SecureRandomGenerator());
|
|
|
|
$nonce1 = $utils->generateNonce(32);
|
|
$nonce2 = $utils->generateNonce(32);
|
|
|
|
expect(strlen($nonce1))->toBe(32);
|
|
expect(strlen($nonce2))->toBe(32);
|
|
expect($nonce1)->not->toBe($nonce2); // Should be different
|
|
});
|
|
|
|
it('generates initialization vector', function () {
|
|
$utils = new CryptographicUtilities(new SecureRandomGenerator());
|
|
|
|
$iv1 = $utils->generateIv(16);
|
|
$iv2 = $utils->generateIv(16);
|
|
|
|
expect(strlen($iv1))->toBe(16);
|
|
expect(strlen($iv2))->toBe(16);
|
|
expect($iv1)->not->toBe($iv2); // Should be different
|
|
});
|
|
|
|
it('validates entropy correctly', function () {
|
|
$utils = new CryptographicUtilities(new SecureRandomGenerator());
|
|
|
|
// Use known high-entropy data (256 unique bytes)
|
|
$highEntropyData = '';
|
|
for ($i = 0; $i < 256; $i++) {
|
|
$highEntropyData .= chr($i);
|
|
}
|
|
$lowEntropyData = str_repeat('a', 32); // Low entropy
|
|
$emptyData = '';
|
|
|
|
expect($utils->validateEntropy($highEntropyData, 6.0))->toBeTrue(); // Lower threshold for realistic testing
|
|
expect($utils->validateEntropy($lowEntropyData))->toBeFalse();
|
|
expect($utils->validateEntropy($emptyData))->toBeFalse();
|
|
});
|
|
|
|
it('calculates Shannon entropy', function () {
|
|
$utils = new CryptographicUtilities(new SecureRandomGenerator());
|
|
|
|
$randomData = $utils->generateNonce(32);
|
|
$uniformData = str_repeat('a', 32);
|
|
|
|
$randomEntropy = $utils->calculateShannonEntropy($randomData);
|
|
$uniformEntropy = $utils->calculateShannonEntropy($uniformData);
|
|
|
|
expect($randomEntropy)->toBeGreaterThan($uniformEntropy);
|
|
expect($uniformEntropy)->toBe(0.0); // Uniform data has zero entropy
|
|
});
|
|
|
|
it('performs constant-time modular exponentiation if GMP available', function () {
|
|
$utils = new CryptographicUtilities(new SecureRandomGenerator());
|
|
|
|
if (! extension_loaded('gmp')) {
|
|
expect(fn () => $utils->constantTimeModPow('5', '3', '13'))
|
|
->toThrow(InvalidArgumentException::class, 'GMP extension required');
|
|
} else {
|
|
$result = $utils->constantTimeModPow('5', '3', '13'); // 5^3 mod 13 = 125 mod 13 = 8
|
|
expect($result)->toBe('8');
|
|
}
|
|
});
|
|
|
|
it('generates cryptographically secure UUID v4', function () {
|
|
$utils = new CryptographicUtilities(new SecureRandomGenerator());
|
|
|
|
$uuid1 = $utils->generateUuid4();
|
|
$uuid2 = $utils->generateUuid4();
|
|
|
|
// Check UUID format
|
|
$pattern = '/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i';
|
|
expect($uuid1)->toMatch($pattern);
|
|
expect($uuid2)->toMatch($pattern);
|
|
expect($uuid1)->not->toBe($uuid2); // Should be unique
|
|
});
|
|
|
|
it('stretches keys correctly', function () {
|
|
$utils = new CryptographicUtilities(new SecureRandomGenerator());
|
|
|
|
$key = 'password';
|
|
$salt = 'salt1234';
|
|
|
|
$stretched1 = $utils->stretchKey($key, $salt, 10000, 32);
|
|
$stretched2 = $utils->stretchKey($key, $salt, 10000, 32); // Same parameters
|
|
$stretched3 = $utils->stretchKey($key, 'differentsalt1234', 10000, 32); // Different salt
|
|
|
|
expect(strlen($stretched1))->toBe(32);
|
|
expect($stretched1)->toBe($stretched2); // Same inputs = same output
|
|
expect($stretched1)->not->toBe($stretched3); // Different salt = different output
|
|
});
|
|
|
|
it('XORs strings correctly', function () {
|
|
$utils = new CryptographicUtilities(new SecureRandomGenerator());
|
|
|
|
$str1 = "\x01\x02\x03\x04";
|
|
$str2 = "\xFF\xFE\xFD\xFC";
|
|
|
|
$result = $utils->xorStrings($str1, $str2);
|
|
|
|
expect($result)->toBe("\xFE\xFC\xFE\xF8"); // XOR result
|
|
expect(strlen($result))->toBe(4);
|
|
});
|
|
|
|
it('generates and removes PKCS#7 padding', function () {
|
|
$utils = new CryptographicUtilities(new SecureRandomGenerator());
|
|
|
|
$data = 'Hello World!'; // 12 bytes
|
|
$blockSize = 16;
|
|
|
|
$padding = $utils->generatePadding($blockSize, strlen($data));
|
|
$paddedData = $data . $padding;
|
|
|
|
expect(strlen($paddedData) % $blockSize)->toBe(0); // Should be block-aligned
|
|
|
|
$unpaddedData = $utils->removePadding($paddedData);
|
|
expect($unpaddedData)->toBe($data);
|
|
});
|
|
|
|
it('generates bit string correctly', function () {
|
|
$utils = new CryptographicUtilities(new SecureRandomGenerator());
|
|
|
|
$bitString = $utils->generateBitString(16); // 2 bytes = 16 bits
|
|
|
|
expect(strlen($bitString))->toBe(16);
|
|
expect($bitString)->toMatch('/^[01]+$/'); // Only 0s and 1s
|
|
});
|
|
|
|
it('converts bit string to bytes', function () {
|
|
$utils = new CryptographicUtilities(new SecureRandomGenerator());
|
|
|
|
$bitString = '0110100001101001'; // "hi" in binary
|
|
$bytes = $utils->bitStringToBytes($bitString);
|
|
|
|
expect($bytes)->toBe('hi');
|
|
});
|
|
|
|
it('validates key strength', function () {
|
|
$utils = new CryptographicUtilities(new SecureRandomGenerator());
|
|
|
|
// Use known high-entropy data (64 different bytes)
|
|
$strongKey = '';
|
|
for ($i = 0; $i < 64; $i++) {
|
|
$strongKey .= chr($i);
|
|
}
|
|
$weakKey = str_repeat('a', 32); // No entropy
|
|
$shortKey = 'short'; // Too short
|
|
|
|
expect($utils->validateKeyStrength($strongKey, 128))->toBeTrue();
|
|
expect($utils->validateKeyStrength($weakKey, 128))->toBeFalse();
|
|
expect($utils->validateKeyStrength($shortKey, 128))->toBeFalse();
|
|
});
|
|
|
|
it('generates deterministic UUID v5', function () {
|
|
$utils = new CryptographicUtilities(new SecureRandomGenerator());
|
|
|
|
$namespace = str_repeat("\x00", 16); // Null namespace
|
|
$name1 = 'test-name';
|
|
$name2 = 'test-name'; // Same name
|
|
$name3 = 'different-name';
|
|
|
|
$uuid1 = $utils->generateUuid5($namespace, $name1);
|
|
$uuid2 = $utils->generateUuid5($namespace, $name2);
|
|
$uuid3 = $utils->generateUuid5($namespace, $name3);
|
|
|
|
// Check UUID v5 format (version 5)
|
|
$pattern = '/^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i';
|
|
expect($uuid1)->toMatch($pattern);
|
|
|
|
expect($uuid1)->toBe($uuid2); // Same inputs = same UUID
|
|
expect($uuid1)->not->toBe($uuid3); // Different name = different UUID
|
|
});
|
|
|
|
it('performs constant-time array search', function () {
|
|
$utils = new CryptographicUtilities(new SecureRandomGenerator());
|
|
|
|
$haystack = ['value1', 'value2', 'secret-value', 'value3'];
|
|
|
|
expect($utils->constantTimeArraySearch($haystack, 'secret-value'))->toBeTrue();
|
|
expect($utils->constantTimeArraySearch($haystack, 'not-found'))->toBeFalse();
|
|
});
|
|
|
|
it('securely wipes memory', function () {
|
|
$utils = new CryptographicUtilities(new SecureRandomGenerator());
|
|
|
|
$sensitiveData = 'very-secret-password';
|
|
$utils->secureWipe($sensitiveData);
|
|
|
|
expect($sensitiveData)->toBe(''); // Should be empty after wipe
|
|
});
|
|
|
|
it('throws exception for too short nonce', function () {
|
|
$utils = new CryptographicUtilities(new SecureRandomGenerator());
|
|
|
|
expect(fn () => $utils->generateNonce(4))
|
|
->toThrow(InvalidArgumentException::class, 'Nonce length must be at least 8 bytes');
|
|
});
|
|
|
|
it('throws exception for too short IV', function () {
|
|
$utils = new CryptographicUtilities(new SecureRandomGenerator());
|
|
|
|
expect(fn () => $utils->generateIv(4))
|
|
->toThrow(InvalidArgumentException::class, 'IV length must be at least 8 bytes');
|
|
});
|
|
|
|
it('throws exception for too few key stretching iterations', function () {
|
|
$utils = new CryptographicUtilities(new SecureRandomGenerator());
|
|
|
|
expect(fn () => $utils->stretchKey('key', 'salt', 500))
|
|
->toThrow(InvalidArgumentException::class, 'Iterations must be at least 1000');
|
|
});
|
|
|
|
it('throws exception for invalid bit string length', function () {
|
|
$utils = new CryptographicUtilities(new SecureRandomGenerator());
|
|
|
|
expect(fn () => $utils->generateBitString(12)) // Not divisible by 8
|
|
->toThrow(InvalidArgumentException::class, 'Bit length must be divisible by 8');
|
|
});
|
|
|
|
it('throws exception for invalid bit string characters', function () {
|
|
$utils = new CryptographicUtilities(new SecureRandomGenerator());
|
|
|
|
expect(fn () => $utils->bitStringToBytes('01012345')) // Contains invalid characters
|
|
->toThrow(InvalidArgumentException::class, 'Bit string contains invalid characters');
|
|
});
|
|
|
|
it('throws exception for invalid UUID v5 namespace', function () {
|
|
$utils = new CryptographicUtilities(new SecureRandomGenerator());
|
|
|
|
expect(fn () => $utils->generateUuid5('short', 'name')) // Namespace too short
|
|
->toThrow(InvalidArgumentException::class, 'Namespace must be 16 bytes');
|
|
});
|
|
|
|
it('creates instance using factory method', function () {
|
|
$randomGen = new SecureRandomGenerator();
|
|
$utils = CryptographicUtilities::create($randomGen);
|
|
|
|
expect($utils)->toBeInstanceOf(CryptographicUtilities::class);
|
|
});
|