This commit is contained in:
Sara Arjona 2023-08-01 15:17:20 +02:00
commit 612b8e0891
No known key found for this signature in database
4 changed files with 79 additions and 80 deletions

View File

@ -39,8 +39,6 @@ if ($unrecognized) {
cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
}
// TODO: MDL-71421 - Remove the openssl alternative once sodium becomes a requirement in Moodle 4.2.
if ($options['help']) {
echo "Generate secure key
@ -54,9 +52,7 @@ may be manually installed on multiple servers.
Options:
-h, --help Print out this help
--method <method> Generate key for specified encryption method instead of default.
* sodium
* openssl-aes-256-ctr
--method <method> Generate key for specified encryption method instead of default (sodium)
Example:
php admin/cli/generate_key.php

View File

@ -14,14 +14,6 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class used to encrypt or decrypt data.
*
* @package core
* @copyright 2020 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core;
/**
@ -30,47 +22,51 @@ namespace core;
* @package core
* @copyright 2020 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @deprecated since Moodle 3.11 MDL-71420 - the openssl part of the class only.
* @todo MDL-71421 Remove the openssl part in Moodle 4.2.
*/
class encryption {
/** @var string Encryption method: Sodium */
const METHOD_SODIUM = 'sodium';
// TODO: MDL-71421 - Remove the following openssl constants and all uses once sodium becomes a requirement in Moodle 4.2.
/** @var string Encryption method: hand-coded OpenSSL (less safe) */
/**
* @var string Encryption method: hand-coded OpenSSL (less safe)
*
* @deprecated
*/
const METHOD_OPENSSL = 'openssl-aes-256-ctr';
/** @var string OpenSSL cipher method */
/**
* @var string OpenSSL cipher method
*
* @deprecated
*/
const OPENSSL_CIPHER = 'AES-256-CTR';
/**
* Checks if Sodium is installed.
*
* @return bool True if the Sodium extension is available
*
* @deprecated since Moodle 4.3 Sodium is always present
*/
public static function is_sodium_installed(): bool {
debugging(__FUNCTION__ . '() is deprecated, sodium is now always present', DEBUG_DEVELOPER);
return extension_loaded('sodium');
}
/**
* Gets the encryption method to use. We use the Sodium extension if it is installed, or
* otherwise, OpenSSL.
* Gets the encryption method to use
*
* @return string Current encryption method
*/
protected static function get_encryption_method(): string {
if (self::is_sodium_installed()) {
return self::METHOD_SODIUM;
} else {
return self::METHOD_OPENSSL;
}
return self::METHOD_SODIUM;
}
/**
* Creates a key for the server.
*
* Note we currently retain support for all methods, in order to decrypt legacy {@see METHOD_OPENSSL} content
*
* @param string|null $method Encryption method (only if you want to create a non-default key)
* @param bool $chmod If true, restricts the file access of the key
* @throws \moodle_exception If the server already has a key, or there is an error
@ -178,6 +174,8 @@ class encryption {
/**
* Gets the length in bytes of the initial values data required.
*
* Note we currently retain support for all methods, in order to decrypt legacy {@see METHOD_OPENSSL} content
*
* @param string $method Crypto method
* @return int Length in bytes
*/
@ -210,6 +208,12 @@ class encryption {
$method = self::get_encryption_method();
}
// We currently retain support for all methods, falling back to Sodium if deprecated OpenSSL is requested.
if ($method === self::METHOD_OPENSSL) {
debugging('Encryption using legacy OpenSSL is deprecated, reverting to Sodium', DEBUG_DEVELOPER);
$method = self::METHOD_SODIUM;
}
// Create IV.
$iv = random_bytes(self::get_iv_length($method));
@ -223,22 +227,6 @@ class encryption {
}
break;
case self::METHOD_OPENSSL:
// This may not be a secure authenticated encryption implementation;
// administrators should enable the Sodium extension.
$key = self::get_key($method);
if (strlen($key) !== 32) {
throw new \moodle_exception('encryption_invalidkey', 'error');
}
$encrypted = @openssl_encrypt($data, self::OPENSSL_CIPHER, $key, OPENSSL_RAW_DATA, $iv);
if ($encrypted === false) {
throw new \moodle_exception('encryption_encryptfailed', 'error',
'', null, openssl_error_string());
}
$hmac = hash_hmac('sha256', $iv . $encrypted, $key, true);
$encrypted .= $hmac;
break;
default:
throw new \coding_exception('Unknown method: ' . $method);
}
@ -251,6 +239,8 @@ class encryption {
/**
* Decrypts data using the server's key. The decryption works with either supported method.
*
* Note currently we retain support for all methods, in order to decrypt legacy {@see METHOD_OPENSSL} content
*
* @param string $data Data to decrypt
* @return string Decrypted data
*/
@ -306,6 +296,8 @@ class encryption {
'', null, 'Integrity check failed');
}
debugging('Decryption using legacy OpenSSL is deprecated, please upgrade to Sodium', DEBUG_DEVELOPER);
$decrypted = @openssl_decrypt($encrypted, self::OPENSSL_CIPHER, $key, OPENSSL_RAW_DATA, $iv);
if ($decrypted === false) {
throw new \moodle_exception('encryption_decryptfailed', 'error',

View File

@ -14,24 +14,19 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Test encryption.
*
* @package core
* @copyright 2020 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core;
use advanced_testcase;
/**
* Test encryption.
*
* @package core
* @copyright 2020 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \core\encryption
*/
class encryption_test extends \basic_testcase {
class encryption_test extends advanced_testcase {
/**
* Clear junk created by tests.
@ -56,26 +51,15 @@ class encryption_test extends \basic_testcase {
require_once(__DIR__ . '/fixtures/testable_encryption.php');
}
/**
* Tests using Sodium need to check the extension is available.
*
* @param string $method Encryption method
*/
protected function require_sodium(string $method) {
if ($method == encryption::METHOD_SODIUM) {
if (!encryption::is_sodium_installed()) {
$this->markTestSkipped('Sodium not installed');
}
}
}
/**
* Many of the tests work with both encryption methods.
*
* @return array[] Array of method options for test
*/
public function encryption_method_provider(): array {
return ['Sodium' => [encryption::METHOD_SODIUM], 'OpenSSL' => [encryption::METHOD_OPENSSL]];
return [
'Sodium' => [encryption::METHOD_SODIUM],
];
}
/**
@ -85,21 +69,28 @@ class encryption_test extends \basic_testcase {
* @dataProvider encryption_method_provider
*/
public function test_create_key(string $method): void {
$this->require_sodium($method);
encryption::create_key($method);
$key = testable_encryption::get_key($method);
// Conveniently, both encryption methods have the same key length.
$this->assertEquals(32, strlen($key));
$this->expectExceptionMessage('Key already exists');
encryption::create_key($method);
}
/**
* Test that we can create keys for legacy {@see encryption::METHOD_OPENSSL} content
*/
public function test_create_key_openssl(): void {
encryption::create_key(encryption::METHOD_OPENSSL);
$key = testable_encryption::get_key(encryption::METHOD_OPENSSL);
$this->assertEquals(32, strlen($key));
$this->expectExceptionMessage('Key already exists');
encryption::create_key(encryption::METHOD_OPENSSL);
}
/**
* Tests encryption and decryption with empty strings.
*
* @throws \moodle_exception
*/
public function test_encrypt_and_decrypt_empty(): void {
$this->assertEquals('', encryption::encrypt(''));
@ -114,7 +105,6 @@ class encryption_test extends \basic_testcase {
*/
public function test_encrypt_nokeys(string $method): void {
global $CFG;
$this->require_sodium($method);
// Prevent automatic generation of keys.
$CFG->nokeygeneration = true;
@ -122,6 +112,15 @@ class encryption_test extends \basic_testcase {
encryption::encrypt('frogs', $method);
}
/**
* Test that attempting to encrypt with legacy {@see encryption::METHOD_OPENSSL} method falls back to Sodium
*/
public function test_encrypt_openssl(): void {
$encrypted = encryption::encrypt('Frogs', encryption::METHOD_OPENSSL);
$this->assertStringStartsWith(encryption::METHOD_SODIUM . ':', $encrypted);
$this->assertDebuggingCalledCount(1, ['Encryption using legacy OpenSSL is deprecated, reverting to Sodium']);
}
/**
* Tests decryption when the data has a different encryption method
*/
@ -137,7 +136,6 @@ class encryption_test extends \basic_testcase {
* @param string $method Encryption method
*/
public function test_decrypt_tooshort(string $method): void {
$this->require_sodium($method);
$this->expectExceptionMessage('Insufficient data');
switch ($method) {
@ -162,8 +160,6 @@ class encryption_test extends \basic_testcase {
* @param string $method Encryption method
*/
public function test_decrypt_notbase64(string $method): void {
$this->require_sodium($method);
$this->expectExceptionMessage('Invalid base64 data');
encryption::decrypt($method . ':' . chr(160));
}
@ -176,7 +172,6 @@ class encryption_test extends \basic_testcase {
*/
public function test_decrypt_nokeys(string $method): void {
global $CFG;
$this->require_sodium($method);
// Prevent automatic generation of keys.
$CFG->nokeygeneration = true;
@ -185,6 +180,22 @@ class encryption_test extends \basic_testcase {
'0123456789abcdef0123456789abcdef0123456789abcdef0'));
}
/**
* Test that we can decrypt legacy {@see encryption::METHOD_OPENSSL} content
*/
public function test_decrypt_openssl(): void {
$key = testable_encryption::get_key(encryption::METHOD_OPENSSL);
// Construct encrypted string using openssl method/cipher.
$iv = random_bytes(openssl_cipher_iv_length(encryption::OPENSSL_CIPHER));
$encrypted = @openssl_encrypt('Frogs', encryption::OPENSSL_CIPHER, $key, OPENSSL_RAW_DATA, $iv);
$hmac = hash_hmac('sha256', $iv . $encrypted, $key, true);
$decrypted = encryption::decrypt(encryption::METHOD_OPENSSL . ':' . base64_encode($iv . $encrypted . $hmac));
$this->assertEquals('Frogs', $decrypted);
$this->assertDebuggingCalledCount(1, ['Decryption using legacy OpenSSL is deprecated, please upgrade to Sodium']);
}
/**
* Test automatic generation of keys when needed.
*
@ -192,7 +203,6 @@ class encryption_test extends \basic_testcase {
* @param string $method Encryption method
*/
public function test_auto_key_generation(string $method): void {
$this->require_sodium($method);
// Allow automatic generation (default).
$encrypted = encryption::encrypt('frogs', $method);
@ -207,7 +217,6 @@ class encryption_test extends \basic_testcase {
*/
public function test_invalid_key(string $method): void {
global $CFG;
$this->require_sodium($method);
// Set the key to something bogus.
$folder = $CFG->dataroot . '/secret/key';
@ -233,7 +242,6 @@ class encryption_test extends \basic_testcase {
* @param string $method Encryption method
*/
public function test_modified_data(string $method): void {
$this->require_sodium($method);
$encrypted = encryption::encrypt('frogs', $method);
$mainbit = base64_decode(substr($encrypted, strlen($method) + 1));
@ -248,10 +256,8 @@ class encryption_test extends \basic_testcase {
*
* @dataProvider encryption_method_provider
* @param string $method Encryption method
* @throws \moodle_exception
*/
public function test_encrypt_and_decrypt_realdata(string $method): void {
$this->require_sodium($method);
// Encrypt short string.
$encrypted = encryption::encrypt('frogs', $method);

View File

@ -16,6 +16,11 @@ information provided here is intended especially for developers.
example, of `\moodle_url` instances)
* The badges_get_oauth2_service_options() method has been deprecated, because it's not required anymore. It should no longer
be used.
* The following class constants are deprecated, as Sodium is now required and we no longer support the OpenSSL fallback except
when decrypting existing content for backwards compatibility:
- `\core\encryption::METHOD_OPENSSL`
- `\core\encryption::OPENSSL_CIPHER`
* The `\core\encryption::is_sodium_installed` method is deprecated, as Sodium is now a requirement
* Support for the following phpunit coverage info properties, deprecated since 3.11, has been removed:
- `whitelistfolders`
- `whitelistfiles`