See Release Notes
Long Term Support Release
Differences Between: [Versions 401 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Test encryption. 19 * 20 * @package core 21 * @copyright 2020 The Open University 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 namespace core; 26 27 /** 28 * Test encryption. 29 * 30 * @package core 31 * @copyright 2020 The Open University 32 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 33 */ 34 class encryption_test extends \basic_testcase { 35 36 /** 37 * Clear junk created by tests. 38 */ 39 protected function tearDown(): void { 40 global $CFG; 41 $keyfile = encryption::get_key_file(encryption::METHOD_OPENSSL); 42 if (file_exists($keyfile)) { 43 chmod($keyfile, 0700); 44 } 45 $keyfile = encryption::get_key_file(encryption::METHOD_SODIUM); 46 if (file_exists($keyfile)) { 47 chmod($keyfile, 0700); 48 } 49 remove_dir($CFG->dataroot . '/secret'); 50 unset($CFG->nokeygeneration); 51 } 52 53 protected function setUp(): void { 54 $this->tearDown(); 55 56 require_once (__DIR__ . '/fixtures/testable_encryption.php'); 57 } 58 59 /** 60 * Tests using Sodium need to check the extension is available. 61 * 62 * @param string $method Encryption method 63 */ 64 protected function require_sodium(string $method) { 65 if ($method == encryption::METHOD_SODIUM) { 66 if (!encryption::is_sodium_installed()) { 67 $this->markTestSkipped('Sodium not installed'); 68 } 69 } 70 } 71 72 /** 73 * Many of the tests work with both encryption methods. 74 * 75 * @return array[] Array of method options for test 76 */ 77 public function encryption_method_provider(): array { 78 return ['Sodium' => [encryption::METHOD_SODIUM], 'OpenSSL' => [encryption::METHOD_OPENSSL]]; 79 } 80 81 /** 82 * Tests the create_keys and get_key functions. 83 * 84 * @param string $method Encryption method 85 * @dataProvider encryption_method_provider 86 */ 87 public function test_create_key(string $method): void { 88 $this->require_sodium($method); 89 encryption::create_key($method); 90 $key = testable_encryption::get_key($method); 91 92 // Conveniently, both encryption methods have the same key length. 93 $this->assertEquals(32, strlen($key)); 94 95 $this->expectExceptionMessage('Key already exists'); 96 encryption::create_key($method); 97 } 98 99 /** 100 * Tests encryption and decryption with empty strings. 101 * 102 * @throws \moodle_exception 103 */ 104 public function test_encrypt_and_decrypt_empty(): void { 105 $this->assertEquals('', encryption::encrypt('')); 106 $this->assertEquals('', encryption::decrypt('')); 107 } 108 109 /** 110 * Tests encryption when the keys weren't created yet. 111 * 112 * @param string $method Encryption method 113 * @dataProvider encryption_method_provider 114 */ 115 public function test_encrypt_nokeys(string $method): void { 116 global $CFG; 117 $this->require_sodium($method); 118 119 // Prevent automatic generation of keys. 120 $CFG->nokeygeneration = true; 121 $this->expectExceptionMessage('Key not found'); 122 encryption::encrypt('frogs', $method); 123 } 124 125 /** 126 * Tests decryption when the data has a different encryption method 127 */ 128 public function test_decrypt_wrongmethod(): void { 129 $this->expectExceptionMessage('Data does not match a supported encryption method'); 130 encryption::decrypt('FAKE-CIPHER-METHOD:xx'); 131 } 132 133 /** 134 * Tests decryption when not enough data is supplied to get the IV and some data. 135 * 136 * @dataProvider encryption_method_provider 137 * @param string $method Encryption method 138 */ 139 public function test_decrypt_tooshort(string $method): void { 140 $this->require_sodium($method); 141 142 $this->expectExceptionMessage('Insufficient data'); 143 switch ($method) { 144 case encryption::METHOD_OPENSSL: 145 // It needs min 49 bytes (16 bytes IV + 32 bytes HMAC + 1 byte data). 146 $justtooshort = '0123456789abcdef0123456789abcdef0123456789abcdef'; 147 break; 148 case encryption::METHOD_SODIUM: 149 // Sodium needs 25 bytes at least as far as our code is concerned (24 bytes IV + 1 150 // byte data); it splits out any authentication hashes itself. 151 $justtooshort = '0123456789abcdef01234567'; 152 break; 153 } 154 155 encryption::decrypt($method . ':' .base64_encode($justtooshort)); 156 } 157 158 /** 159 * Tests decryption when data is not valid base64. 160 * 161 * @dataProvider encryption_method_provider 162 * @param string $method Encryption method 163 */ 164 public function test_decrypt_notbase64(string $method): void { 165 $this->require_sodium($method); 166 167 $this->expectExceptionMessage('Invalid base64 data'); 168 encryption::decrypt($method . ':' . chr(160)); 169 } 170 171 /** 172 * Tests decryption when the keys weren't created yet. 173 * 174 * @dataProvider encryption_method_provider 175 * @param string $method Encryption method 176 */ 177 public function test_decrypt_nokeys(string $method): void { 178 global $CFG; 179 $this->require_sodium($method); 180 181 // Prevent automatic generation of keys. 182 $CFG->nokeygeneration = true; 183 $this->expectExceptionMessage('Key not found'); 184 encryption::decrypt($method . ':' . base64_encode( 185 '0123456789abcdef0123456789abcdef0123456789abcdef0')); 186 } 187 188 /** 189 * Test automatic generation of keys when needed. 190 * 191 * @dataProvider encryption_method_provider 192 * @param string $method Encryption method 193 */ 194 public function test_auto_key_generation(string $method): void { 195 $this->require_sodium($method); 196 197 // Allow automatic generation (default). 198 $encrypted = encryption::encrypt('frogs', $method); 199 $this->assertEquals('frogs', encryption::decrypt($encrypted)); 200 } 201 202 /** 203 * Checks that invalid key causes failures. 204 * 205 * @dataProvider encryption_method_provider 206 * @param string $method Encryption method 207 */ 208 public function test_invalid_key(string $method): void { 209 global $CFG; 210 $this->require_sodium($method); 211 212 // Set the key to something bogus. 213 $folder = $CFG->dataroot . '/secret/key'; 214 check_dir_exists($folder); 215 file_put_contents(encryption::get_key_file($method), 'silly'); 216 217 switch ($method) { 218 case encryption::METHOD_SODIUM: 219 $this->expectExceptionMessageMatches('/(should|must) be SODIUM_CRYPTO_SECRETBOX_KEYBYTES bytes/'); 220 break; 221 222 case encryption::METHOD_OPENSSL: 223 $this->expectExceptionMessage('Invalid key'); 224 break; 225 } 226 encryption::encrypt('frogs', $method); 227 } 228 229 /** 230 * Checks that modified data causes failures. 231 * 232 * @dataProvider encryption_method_provider 233 * @param string $method Encryption method 234 */ 235 public function test_modified_data(string $method): void { 236 $this->require_sodium($method); 237 238 $encrypted = encryption::encrypt('frogs', $method); 239 $mainbit = base64_decode(substr($encrypted, strlen($method) + 1)); 240 $mainbit = substr($mainbit, 0, 16) . 'X' . substr($mainbit, 16); 241 $encrypted = $method . ':' . base64_encode($mainbit); 242 $this->expectExceptionMessage('Integrity check failed'); 243 encryption::decrypt($encrypted); 244 } 245 246 /** 247 * Tests encryption and decryption for real. 248 * 249 * @dataProvider encryption_method_provider 250 * @param string $method Encryption method 251 * @throws \moodle_exception 252 */ 253 public function test_encrypt_and_decrypt_realdata(string $method): void { 254 $this->require_sodium($method); 255 256 // Encrypt short string. 257 $encrypted = encryption::encrypt('frogs', $method); 258 $this->assertNotEquals('frogs', $encrypted); 259 $this->assertEquals('frogs', encryption::decrypt($encrypted)); 260 261 // Encrypt really long string (1 MB). 262 $long = str_repeat('X', 1024 * 1024); 263 $this->assertEquals($long, encryption::decrypt(encryption::encrypt($long, $method))); 264 } 265 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body