mirror of
https://github.com/moodle/moodle.git
synced 2025-01-18 22:08:20 +01:00
Merge branch 'MDL-71421' of https://github.com/paulholden/moodle
This commit is contained in:
commit
612b8e0891
@ -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
|
||||
|
@ -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',
|
||||
|
@ -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);
|
||||
|
@ -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`
|
||||
|
Loading…
x
Reference in New Issue
Block a user