From 0d033e08e03626fb544c8e65ea235069aff0576b Mon Sep 17 00:00:00 2001 From: Dearsh Oberoi Date: Wed, 31 Dec 2025 00:54:22 +0530 Subject: [PATCH] MDEV-35254 Make iterations count configurable in PARSEC plugin This patch adds a global plugin variable parsec_iterations to define define the number of iterations to be used when generating the key corresponding to the password. It has a default value, lower and upper bounds. --- mysql-test/suite/plugins/r/parsec.result | 4 +- .../suite/plugins/r/parsec_iterations.result | 70 +++++++++++++++ .../suite/plugins/t/parsec_iterations.opt | 1 + .../suite/plugins/t/parsec_iterations.test | 86 +++++++++++++++++++ plugin/auth_parsec/server_parsec.cc | 55 +++++++++++- 5 files changed, 210 insertions(+), 6 deletions(-) create mode 100644 mysql-test/suite/plugins/r/parsec_iterations.result create mode 100644 mysql-test/suite/plugins/t/parsec_iterations.opt create mode 100644 mysql-test/suite/plugins/t/parsec_iterations.test diff --git a/mysql-test/suite/plugins/r/parsec.result b/mysql-test/suite/plugins/r/parsec.result index a7472d0e6668b..4b1a5730ed048 100644 --- a/mysql-test/suite/plugins/r/parsec.result +++ b/mysql-test/suite/plugins/r/parsec.result @@ -3,7 +3,7 @@ ERROR HY000: Wrong ext-salt format create user test1@'%' identified via parsec using PASSWORD('pwd'); show grants for test1@'%'; Grants for test1@% -GRANT USAGE ON *.* TO `test1`@`%` IDENTIFIED VIA parsec USING 'P0:salt:password' +GRANT USAGE ON *.* TO `test1`@`%` IDENTIFIED VIA parsec USING 'P8:salt:password' connect con1, localhost, test1, pwd; select 1, USER(), CURRENT_USER(); 1 USER() CURRENT_USER() @@ -32,7 +32,7 @@ drop user test1@'%'; create user test2@'%' identified via parsec using PASSWORD(''); show grants for test2@'%'; Grants for test2@% -GRANT USAGE ON *.* TO `test2`@`%` IDENTIFIED VIA parsec USING 'P0:salt:password' +GRANT USAGE ON *.* TO `test2`@`%` IDENTIFIED VIA parsec USING 'P8:salt:password' connect con4, localhost, test2,; select 4, USER(), CURRENT_USER(); 4 USER() CURRENT_USER() diff --git a/mysql-test/suite/plugins/r/parsec_iterations.result b/mysql-test/suite/plugins/r/parsec_iterations.result new file mode 100644 index 0000000000000..3ef8279901077 --- /dev/null +++ b/mysql-test/suite/plugins/r/parsec_iterations.result @@ -0,0 +1,70 @@ +# +# Basic visibility and default value +# +select @@global.parsec_iterations; +# +# Valid power-of-two values should be accepted +# +set global parsec_iterations = 1024; +select @@global.parsec_iterations; +@@global.parsec_iterations +1024 +set global parsec_iterations = 262144; +select @@global.parsec_iterations; +@@global.parsec_iterations +262144 +# +# Values should be rounded UP to next power of two +# +# 5000 -> 8192 +set global parsec_iterations = 5000; +Warnings: +Warning 1231 parsec: parsec_iterations rounded up to 8192 (next supported value) +select @@global.parsec_iterations; +@@global.parsec_iterations +8192 +# 131073 -> 262144 +set global parsec_iterations = 131073; +Warnings: +Warning 1231 parsec: parsec_iterations rounded up to 262144 (next supported value) +select @@global.parsec_iterations; +@@global.parsec_iterations +262144 +# +# Lower bound enforcement +# +set global parsec_iterations = 512; +Warnings: +Warning 1292 Truncated incorrect parsec_iterations value: '512' +select @@global.parsec_iterations; +@@global.parsec_iterations +1024 +# +# Upper bound enforcement +# +set global parsec_iterations = 2097152; +Warnings: +Warning 1292 Truncated incorrect parsec_iterations value: '2097152' +select @@global.parsec_iterations; +@@global.parsec_iterations +524288 +# +# Session variable must not exist +# +select @@session.parsec_iterations; +ERROR HY000: Variable 'parsec_iterations' is a GLOBAL variable +set session parsec_iterations = 1024; +ERROR HY000: Variable 'parsec_iterations' is a GLOBAL variable and should be set with SET GLOBAL +# +# parsec_iterations should should not get accidently mutated during user creation +# +set @iter_before := @@global.parsec_iterations; +create user t_iter@'%' identified via parsec using password('pwd'); +select @@global.parsec_iterations = @iter_before; +@@global.parsec_iterations = @iter_before +1 +drop user t_iter@'%'; +# +# Preserve the state that existed before the test case was executed +# +set global parsec_iterations = 262144; diff --git a/mysql-test/suite/plugins/t/parsec_iterations.opt b/mysql-test/suite/plugins/t/parsec_iterations.opt new file mode 100644 index 0000000000000..a21ee2e5ff8d9 --- /dev/null +++ b/mysql-test/suite/plugins/t/parsec_iterations.opt @@ -0,0 +1 @@ +--plugin-load-add=$AUTH_PARSEC_SO \ No newline at end of file diff --git a/mysql-test/suite/plugins/t/parsec_iterations.test b/mysql-test/suite/plugins/t/parsec_iterations.test new file mode 100644 index 0000000000000..1e09e32a7e928 --- /dev/null +++ b/mysql-test/suite/plugins/t/parsec_iterations.test @@ -0,0 +1,86 @@ +# +# Test for MDEV-35254: PARSEC plugin should allow DBAs to specify number of iterations +# + +source include/platform.inc; +source include/not_embedded.inc; + +if (`select count(*) = 0 from information_schema.plugins where plugin_name = 'parsec'`) +{ + --skip Needs parsec plugin +} + +if (!$PARSEC_SO) { + skip No auth_parsec plugin; +} + +--echo # +--echo # Basic visibility and default value +--echo # + +--disable_result_log +select @@global.parsec_iterations; +--enable_result_log + +--echo # +--echo # Valid power-of-two values should be accepted +--echo # + +set global parsec_iterations = 1024; +select @@global.parsec_iterations; + +set global parsec_iterations = 262144; +select @@global.parsec_iterations; + +--echo # +--echo # Values should be rounded UP to next power of two +--echo # + +--echo # 5000 -> 8192 +set global parsec_iterations = 5000; +select @@global.parsec_iterations; + +--echo # 131073 -> 262144 +set global parsec_iterations = 131073; +select @@global.parsec_iterations; + +--echo # +--echo # Lower bound enforcement +--echo # + +set global parsec_iterations = 512; +select @@global.parsec_iterations; + +--echo # +--echo # Upper bound enforcement +--echo # + +set global parsec_iterations = 2097152; +select @@global.parsec_iterations; + +--echo # +--echo # Session variable must not exist +--echo # + +--error ER_INCORRECT_GLOBAL_LOCAL_VAR +select @@session.parsec_iterations; + +--error ER_GLOBAL_VARIABLE +set session parsec_iterations = 1024; + +--echo # +--echo # parsec_iterations should should not get accidently mutated during user creation +--echo # + +set @iter_before := @@global.parsec_iterations; +create user t_iter@'%' identified via parsec using password('pwd'); +select @@global.parsec_iterations = @iter_before; +drop user t_iter@'%'; + +-- echo # +-- echo # Preserve the state that existed before the test case was executed +-- echo # + +set global parsec_iterations = 262144; + + diff --git a/plugin/auth_parsec/server_parsec.cc b/plugin/auth_parsec/server_parsec.cc index 572de97ff0cf9..b0188212dcd76 100644 --- a/plugin/auth_parsec/server_parsec.cc +++ b/plugin/auth_parsec/server_parsec.cc @@ -38,6 +38,52 @@ constexpr size_t PBKDF2_HASH_LENGTH= ED25519_KEY_LENGTH; constexpr size_t CLIENT_RESPONSE_LENGTH= CHALLENGE_SCRAMBLE_LENGTH + ED25519_SIG_LENGTH; +constexpr unsigned int PARSEC_ITERATIONS_DEFAULT= 1u << 18; +constexpr unsigned int PARSEC_ITERATIONS_MIN= 1u << 10; +constexpr unsigned int PARSEC_ITERATIONS_MAX= 1u << 19; +constexpr unsigned int PARSEC_ITERATIONS_STEP= 1u; +constexpr unsigned int ITER_FACTOR_BASE_VAL= 10u; + +static unsigned int iterations= PARSEC_ITERATIONS_DEFAULT; + +static inline unsigned int round_to_power_of_two(const unsigned int x) { + unsigned int p = 1; + while (p < x) + p <<= 1; + return p; +} + + +static inline unsigned int log_2_of_power_of_2(const unsigned int x) { + // function will not work correctly for non power of 2 unsigned integers + unsigned int p = 0; + while ((1u << p) != x) + p++; + return p; +} + + +static void update_parsec_iterations(MYSQL_THD thd, + struct st_mysql_sys_var *var __attribute__((unused)), + void *var_ptr __attribute__((unused)), const void *save) +{ + unsigned int iterations_user_input= *(unsigned int *) save; + iterations= round_to_power_of_two(iterations_user_input); + if (iterations != iterations_user_input) + my_printf_error(ER_WRONG_VALUE_FOR_VAR, "parsec: parsec_iterations rounded up to %d (next supported value)", + ME_WARNING, iterations); +} + +static MYSQL_SYSVAR_UINT(iterations, iterations, PLUGIN_VAR_NOCMDOPT, + "Number of iterations to be used when generating the key corresponding to the password", + NULL, update_parsec_iterations, PARSEC_ITERATIONS_DEFAULT, PARSEC_ITERATIONS_MIN, + PARSEC_ITERATIONS_MAX, PARSEC_ITERATIONS_STEP); + +static struct st_mysql_sys_var* system_vars[] = { + MYSQL_SYSVAR(iterations), + NULL +}; + constexpr size_t base64_length(size_t input_length) { return ((input_length + 2) / 3) * 4; // with padding @@ -95,7 +141,7 @@ int compute_derived_key(const char* password, size_t pass_len, { assert(params->algorithm == 'P'); int ret = PKCS5_PBKDF2_HMAC(password, (int)pass_len, params->salt, - sizeof(params->salt), 1024 << params->iterations, + sizeof(params->salt), 1024u << params->iterations, EVP_sha512(), PBKDF2_HASH_LENGTH, derived_key); if(ret == 0) return print_ssl_error(); @@ -176,7 +222,7 @@ int hash_password(const char *password, size_t password_length, Passwd_in_memory memory; memory.algorithm= 'P'; - memory.iterations= 0; + memory.iterations= log_2_of_power_of_2(iterations) - ITER_FACTOR_BASE_VAL; my_random_bytes(memory.salt, sizeof(memory.salt)); uchar derived_key[PBKDF2_HASH_LENGTH]; @@ -204,10 +250,11 @@ int digest_to_binary(const char *hash, size_t hash_length, { auto stored= (Passwd_as_stored*)hash; auto memory= (Passwd_in_memory*)out; + const uchar ITER_MAX_VAL= '0' + (log_2_of_power_of_2(PARSEC_ITERATIONS_MAX) - ITER_FACTOR_BASE_VAL); if (hash_length != sizeof (*stored) || *out_length < sizeof(*memory) || stored->algorithm != 'P' || - stored->iterations < '0' || stored->iterations > '3' || + stored->iterations < '0' || stored->iterations > ITER_MAX_VAL || stored->colon != ':' || stored->colon2 != ':') { my_printf_error(ER_PASSWD_LENGTH, "Wrong ext-salt format", 0); @@ -313,7 +360,7 @@ maria_declare_plugin(auth_parsec) NULL, 0x0100, NULL, - NULL, + system_vars, "1.0", MariaDB_PLUGIN_MATURITY_GAMMA }