1<?php 2 3namespace Defuse\Crypto; 4 5use Defuse\Crypto\Exception as Ex; 6 7/* 8 * We're using static class inheritance to get access to protected methods 9 * inside Crypto. To make it easy to know where the method we're calling can be 10 * found, within this file, prefix calls with `Crypto::` or `RuntimeTests::`, 11 * and don't use `self::`. 12 */ 13 14class RuntimeTests extends Crypto 15{ 16 /** 17 * Runs the runtime tests. 18 * 19 * @throws Ex\EnvironmentIsBrokenException 20 * @return void 21 */ 22 public static function runtimeTest() 23 { 24 // 0: Tests haven't been run yet. 25 // 1: Tests have passed. 26 // 2: Tests are running right now. 27 // 3: Tests have failed. 28 static $test_state = 0; 29 30 if ($test_state === 1 || $test_state === 2) { 31 return; 32 } 33 34 if ($test_state === 3) { 35 /* If an intermittent problem caused a test to fail previously, we 36 * want that to be indicated to the user with every call to this 37 * library. This way, if the user first does something they really 38 * don't care about, and just ignores all exceptions, they won't get 39 * screwed when they then start to use the library for something 40 * they do care about. */ 41 throw new Ex\EnvironmentIsBrokenException('Tests failed previously.'); 42 } 43 44 try { 45 $test_state = 2; 46 47 Core::ensureFunctionExists('openssl_get_cipher_methods'); 48 if (\in_array(Core::CIPHER_METHOD, \openssl_get_cipher_methods()) === false) { 49 throw new Ex\EnvironmentIsBrokenException( 50 'Cipher method not supported. This is normally caused by an outdated ' . 51 'version of OpenSSL (and/or OpenSSL compiled for FIPS compliance). ' . 52 'Please upgrade to a newer version of OpenSSL that supports ' . 53 Core::CIPHER_METHOD . ' to use this library.' 54 ); 55 } 56 57 RuntimeTests::AESTestVector(); 58 RuntimeTests::HMACTestVector(); 59 RuntimeTests::HKDFTestVector(); 60 61 RuntimeTests::testEncryptDecrypt(); 62 Core::ensureTrue(Core::ourStrlen(Key::createNewRandomKey()->getRawBytes()) === Core::KEY_BYTE_SIZE); 63 64 Core::ensureTrue(Core::ENCRYPTION_INFO_STRING !== Core::AUTHENTICATION_INFO_STRING); 65 } catch (Ex\EnvironmentIsBrokenException $ex) { 66 // Do this, otherwise it will stay in the "tests are running" state. 67 $test_state = 3; 68 throw $ex; 69 } 70 71 // Change this to '0' make the tests always re-run (for benchmarking). 72 $test_state = 1; 73 } 74 75 /** 76 * High-level tests of Crypto operations. 77 * 78 * @throws Ex\EnvironmentIsBrokenException 79 * @return void 80 */ 81 private static function testEncryptDecrypt() 82 { 83 $key = Key::createNewRandomKey(); 84 $data = "EnCrYpT EvErYThInG\x00\x00"; 85 86 // Make sure encrypting then decrypting doesn't change the message. 87 $ciphertext = Crypto::encrypt($data, $key, true); 88 try { 89 $decrypted = Crypto::decrypt($ciphertext, $key, true); 90 } catch (Ex\WrongKeyOrModifiedCiphertextException $ex) { 91 // It's important to catch this and change it into a 92 // Ex\EnvironmentIsBrokenException, otherwise a test failure could trick 93 // the user into thinking it's just an invalid ciphertext! 94 throw new Ex\EnvironmentIsBrokenException(); 95 } 96 Core::ensureTrue($decrypted === $data); 97 98 // Modifying the ciphertext: Appending a string. 99 try { 100 Crypto::decrypt($ciphertext . 'a', $key, true); 101 throw new Ex\EnvironmentIsBrokenException(); 102 } catch (Ex\WrongKeyOrModifiedCiphertextException $e) { /* expected */ 103 } 104 105 // Modifying the ciphertext: Changing an HMAC byte. 106 $indices_to_change = [ 107 0, // The header. 108 Core::HEADER_VERSION_SIZE + 1, // the salt 109 Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE + 1, // the IV 110 Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE + Core::BLOCK_BYTE_SIZE + 1, // the ciphertext 111 ]; 112 113 foreach ($indices_to_change as $index) { 114 try { 115 $ciphertext[$index] = \chr((\ord($ciphertext[$index]) + 1) % 256); 116 Crypto::decrypt($ciphertext, $key, true); 117 throw new Ex\EnvironmentIsBrokenException(); 118 } catch (Ex\WrongKeyOrModifiedCiphertextException $e) { /* expected */ 119 } 120 } 121 122 // Decrypting with the wrong key. 123 $key = Key::createNewRandomKey(); 124 $data = 'abcdef'; 125 $ciphertext = Crypto::encrypt($data, $key, true); 126 $wrong_key = Key::createNewRandomKey(); 127 try { 128 Crypto::decrypt($ciphertext, $wrong_key, true); 129 throw new Ex\EnvironmentIsBrokenException(); 130 } catch (Ex\WrongKeyOrModifiedCiphertextException $e) { /* expected */ 131 } 132 133 // Ciphertext too small. 134 $key = Key::createNewRandomKey(); 135 $ciphertext = \str_repeat('A', Core::MINIMUM_CIPHERTEXT_SIZE - 1); 136 try { 137 Crypto::decrypt($ciphertext, $key, true); 138 throw new Ex\EnvironmentIsBrokenException(); 139 } catch (Ex\WrongKeyOrModifiedCiphertextException $e) { /* expected */ 140 } 141 } 142 143 /** 144 * Test HKDF against test vectors. 145 * 146 * @throws Ex\EnvironmentIsBrokenException 147 * @return void 148 */ 149 private static function HKDFTestVector() 150 { 151 // HKDF test vectors from RFC 5869 152 153 // Test Case 1 154 $ikm = \str_repeat("\x0b", 22); 155 $salt = Encoding::hexToBin('000102030405060708090a0b0c'); 156 $info = Encoding::hexToBin('f0f1f2f3f4f5f6f7f8f9'); 157 $length = 42; 158 $okm = Encoding::hexToBin( 159 '3cb25f25faacd57a90434f64d0362f2a' . 160 '2d2d0a90cf1a5a4c5db02d56ecc4c5bf' . 161 '34007208d5b887185865' 162 ); 163 $computed_okm = Core::HKDF('sha256', $ikm, $length, $info, $salt); 164 Core::ensureTrue($computed_okm === $okm); 165 166 // Test Case 7 167 $ikm = \str_repeat("\x0c", 22); 168 $length = 42; 169 $okm = Encoding::hexToBin( 170 '2c91117204d745f3500d636a62f64f0a' . 171 'b3bae548aa53d423b0d1f27ebba6f5e5' . 172 '673a081d70cce7acfc48' 173 ); 174 $computed_okm = Core::HKDF('sha1', $ikm, $length, '', null); 175 Core::ensureTrue($computed_okm === $okm); 176 } 177 178 /** 179 * Test HMAC against test vectors. 180 * 181 * @throws Ex\EnvironmentIsBrokenException 182 * @return void 183 */ 184 private static function HMACTestVector() 185 { 186 // HMAC test vector From RFC 4231 (Test Case 1) 187 $key = \str_repeat("\x0b", 20); 188 $data = 'Hi There'; 189 $correct = 'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7'; 190 Core::ensureTrue( 191 \hash_hmac(Core::HASH_FUNCTION_NAME, $data, $key) === $correct 192 ); 193 } 194 195 /** 196 * Test AES against test vectors. 197 * 198 * @throws Ex\EnvironmentIsBrokenException 199 * @return void 200 */ 201 private static function AESTestVector() 202 { 203 // AES CTR mode test vector from NIST SP 800-38A 204 $key = Encoding::hexToBin( 205 '603deb1015ca71be2b73aef0857d7781' . 206 '1f352c073b6108d72d9810a30914dff4' 207 ); 208 $iv = Encoding::hexToBin('f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'); 209 $plaintext = Encoding::hexToBin( 210 '6bc1bee22e409f96e93d7e117393172a' . 211 'ae2d8a571e03ac9c9eb76fac45af8e51' . 212 '30c81c46a35ce411e5fbc1191a0a52ef' . 213 'f69f2445df4f9b17ad2b417be66c3710' 214 ); 215 $ciphertext = Encoding::hexToBin( 216 '601ec313775789a5b7a7f504bbf3d228' . 217 'f443e3ca4d62b59aca84e990cacaf5c5' . 218 '2b0930daa23de94ce87017ba2d84988d' . 219 'dfc9c58db67aada613c2dd08457941a6' 220 ); 221 222 $computed_ciphertext = Crypto::plainEncrypt($plaintext, $key, $iv); 223 Core::ensureTrue($computed_ciphertext === $ciphertext); 224 225 $computed_plaintext = Crypto::plainDecrypt($ciphertext, $key, $iv, Core::CIPHER_METHOD); 226 Core::ensureTrue($computed_plaintext === $plaintext); 227 } 228} 229