| 1 |
<?php |
| 2 |
|
| 3 |
if (!defined('ABSPATH')) die('Access denied.'); |
| 4 |
|
| 5 |
class Simba_TFA { |
| 6 |
|
| 7 |
private $salt_prefix; |
| 8 |
private $pw_prefix; |
| 9 |
|
| 10 |
public function __construct($base32_encoder, $otp_helper) |
| 11 |
{ |
| 12 |
$this->base32_encoder = $base32_encoder; |
| 13 |
$this->otp_helper = $otp_helper; |
| 14 |
$this->time_window_size = apply_filters('simbatfa_time_window_size', 30); |
| 15 |
$this->check_back_time_windows = apply_filters('simbatfa_check_back_time_windows', 2); |
| 16 |
$this->check_forward_counter_window = apply_filters('simbatfa_check_forward_counter_window', 20); |
| 17 |
$this->otp_length = 6; |
| 18 |
$this->emergency_codes_length = 8; |
| 19 |
$this->salt_prefix = AUTH_SALT; |
| 20 |
$this->pw_prefix = AUTH_KEY; |
| 21 |
$this->default_hmac = 'totp'; |
| 22 |
} |
| 23 |
|
| 24 |
public function generateOTP($user_ID, $key_b64, $length = 6, $counter = false) |
| 25 |
{ |
| 26 |
|
| 27 |
$length = $length ? (int)$length : 6; |
| 28 |
|
| 29 |
$key = $this->decryptString($key_b64, $user_ID); |
| 30 |
$alg = $this->getUserAlgorithm($user_ID); |
| 31 |
|
| 32 |
if($alg == 'hotp') |
| 33 |
{ |
| 34 |
$db_counter = $this->getUserCounter($user_ID); |
| 35 |
|
| 36 |
$counter = $counter ? $counter : $db_counter; |
| 37 |
$otp_res = $this->otp_helper->generateByCounter($key, $counter); |
| 38 |
} |
| 39 |
else |
| 40 |
{ |
| 41 |
//time() is supposed to be UTC |
| 42 |
$time = $counter ? $counter : time(); |
| 43 |
$otp_res = $this->otp_helper->generateByTime($key, $this->time_window_size, $time); |
| 44 |
} |
| 45 |
$code = $otp_res->toHotp($length); |
| 46 |
|
| 47 |
return $code; |
| 48 |
} |
| 49 |
|
| 50 |
public function generateOTPsForLoginCheck($user_ID, $key_b64) |
| 51 |
{ |
| 52 |
$key = trim($this->decryptString($key_b64, $user_ID)); |
| 53 |
$alg = $this->getUserAlgorithm($user_ID); |
| 54 |
|
| 55 |
if($alg == 'totp') |
| 56 |
$otp_res = $this->otp_helper->generateByTimeWindow($key, $this->time_window_size, -1*$this->check_back_time_windows, 0); |
| 57 |
elseif($alg == 'hotp') |
| 58 |
{ |
| 59 |
$counter = $this->getUserCounter($user_ID); |
| 60 |
|
| 61 |
$otp_res = array(); |
| 62 |
for($i = 0; $i < $this->check_forward_counter_window; $i++) |
| 63 |
$otp_res[] = $this->otp_helper->generateByCounter($key, ($counter+$i)); |
| 64 |
} |
| 65 |
return $otp_res; |
| 66 |
} |
| 67 |
|
| 68 |
|
| 69 |
public function addPrivateKey($user_ID, $key = false) |
| 70 |
{ |
| 71 |
//Generate a private key for the user. |
| 72 |
//To work with Google Authenticator it has to be 10 bytes = 16 chars in base32 |
| 73 |
$code = $key ? $key : strtoupper($this->randString(10)); |
| 74 |
|
| 75 |
//Lets encrypt the key |
| 76 |
$code = $this->encryptString($code, $user_ID); |
| 77 |
|
| 78 |
//Add private key to users meta |
| 79 |
update_user_meta($user_ID, 'tfa_priv_key_64', $code); |
| 80 |
|
| 81 |
$alg = $this->getUserAlgorithm($user_ID); |
| 82 |
|
| 83 |
do_action('simba_tfa_adding_private_key', $alg, $user_ID, $code, $this); |
| 84 |
|
| 85 |
$this->changeUserAlgorithmTo($user_ID, $alg); |
| 86 |
|
| 87 |
return $code; |
| 88 |
} |
| 89 |
|
| 90 |
// Port over keys that were encrypted with mcrypt and its non-compliant padding scheme, so that if the site is ever migrated to a server without mcrypt, they can still be decrypted |
| 91 |
public function potentially_port_private_keys() { |
| 92 |
|
| 93 |
$simba_tfa_priv_key_format = get_site_option('simba_tfa_priv_key_format', false); |
| 94 |
|
| 95 |
$attempts = 0; |
| 96 |
$successes = 0; |
| 97 |
|
| 98 |
if ($simba_tfa_priv_key_format < 1 && function_exists('openssl_encrypt')) { |
| 99 |
|
| 100 |
error_log("TFA: Beginning attempt to port private key encryption over to openssl"); |
| 101 |
global $wpdb; |
| 102 |
$sql = "SELECT user_id, meta_value FROM ".$wpdb->usermeta." WHERE meta_key = 'tfa_priv_key_64'"; |
| 103 |
|
| 104 |
$user_results = $wpdb->get_results($sql); |
| 105 |
|
| 106 |
foreach ($user_results as $u) { |
| 107 |
$dec_openssl = $this->decryptString($u->meta_value, $u->user_id, true); |
| 108 |
|
| 109 |
$ported = false; |
| 110 |
if ('' == $dec_openssl) { |
| 111 |
|
| 112 |
$attempts++; |
| 113 |
|
| 114 |
$dec_default = $this->decryptString($u->meta_value, $u->user_id); |
| 115 |
|
| 116 |
if ('' != $dec_default) { |
| 117 |
|
| 118 |
$enc = $this->encryptString($dec_default, $u->user_id); |
| 119 |
|
| 120 |
if ($enc) { |
| 121 |
|
| 122 |
$ported = true; |
| 123 |
$successes++; |
| 124 |
update_user_meta($u->user_id, 'tfa_priv_key_64', $enc); |
| 125 |
} |
| 126 |
} |
| 127 |
|
| 128 |
} |
| 129 |
|
| 130 |
if ($ported) { |
| 131 |
error_log("TFA: Successfully ported the key for user with ID ".$u->user_id." over to openssl"); |
| 132 |
} else { |
| 133 |
error_log("TFA: Failed to port the key for user with ID ".$u->user_id." over to openssl"); |
| 134 |
} |
| 135 |
} |
| 136 |
if ($attempts == 0 || $successes > 0) update_site_option('simba_tfa_priv_key_format', 1); |
| 137 |
|
| 138 |
} |
| 139 |
} |
| 140 |
|
| 141 |
public function getPrivateKeyPlain($enc, $user_ID) |
| 142 |
{ |
| 143 |
$dec = $this->decryptString($enc, $user_ID); |
| 144 |
$this->potentially_port_private_keys(); |
| 145 |
return $dec; |
| 146 |
} |
| 147 |
|
| 148 |
|
| 149 |
public function getPanicCodesString($arr, $user_ID) |
| 150 |
{ |
| 151 |
if(!is_array($arr)) return '<em>'.__('No emergency codes left. Sorry.', 'two-factor-authentication').'</em>'; |
| 152 |
|
| 153 |
$emergency_str = ''; |
| 154 |
|
| 155 |
foreach($arr as $p_code) { |
| 156 |
$emergency_str .= $this->decryptString($p_code, $user_ID).', '; |
| 157 |
} |
| 158 |
|
| 159 |
$emergency_str = rtrim($emergency_str, ', '); |
| 160 |
|
| 161 |
$emergency_str = $emergency_str ? $emergency_str : '<em>'.__('No emergency codes left. Sorry.', 'two-factor-authentication').'</em>'; |
| 162 |
return $emergency_str; |
| 163 |
} |
| 164 |
|
| 165 |
public function preAuth($params) |
| 166 |
{ |
| 167 |
global $wpdb; |
| 168 |
$query = filter_var($params['log'], FILTER_VALIDATE_EMAIL) ? $wpdb->prepare("SELECT ID, user_email from ".$wpdb->users." WHERE user_email=%s", $params['log']) : $wpdb->prepare("SELECT ID, user_email from ".$wpdb->users." WHERE user_login=%s", $params['log']); |
| 169 |
$user = $wpdb->get_row($query); |
| 170 |
if (!$user && filter_var($params['log'], FILTER_VALIDATE_EMAIL)) { |
| 171 |
// Corner-case: login looks like an email, but is a username rather than email address |
| 172 |
$user = $wpdb->get_row($wpdb->prepare("SELECT ID, user_email from ".$wpdb->users." WHERE user_login=%s", $params['log'])); |
| 173 |
} |
| 174 |
$is_activated_for_user = true; |
| 175 |
$is_activated_by_user = false; |
| 176 |
|
| 177 |
if($user) { |
| 178 |
$tfa_priv_key = get_user_meta($user->ID, 'tfa_priv_key_64', true); |
| 179 |
$is_activated_for_user = $this->isActivatedForUser($user->ID); |
| 180 |
$is_activated_by_user = $this->isActivatedByUser($user->ID); |
| 181 |
|
| 182 |
if($is_activated_for_user && $is_activated_by_user) |
| 183 |
{ |
| 184 |
// $delivery_type = get_user_meta($user->ID, 'simbatfa_delivery_type', true); |
| 185 |
|
| 186 |
//No private key yet, generate one. |
| 187 |
//This is safe to do since the code is emailed to the user. |
| 188 |
//Not safe to do if the user has disabled email. |
| 189 |
if(!$tfa_priv_key) |
| 190 |
$tfa_priv_key = $this->addPrivateKey($user->ID); |
| 191 |
|
| 192 |
$code = $this->generateOTP($user->ID, $tfa_priv_key); |
| 193 |
|
| 194 |
return true;//Set to true |
| 195 |
} |
| 196 |
return false; |
| 197 |
} |
| 198 |
return false; |
| 199 |
} |
| 200 |
|
| 201 |
public function authUserFromLogin($params) |
| 202 |
{ |
| 203 |
|
| 204 |
$params = apply_filters('simbatfa_auth_user_from_login_params', $params); |
| 205 |
|
| 206 |
global $simba_two_factor_authentication, $wpdb; |
| 207 |
|
| 208 |
if(!$this->isCallerActive($params)) |
| 209 |
return true; |
| 210 |
|
| 211 |
$field = filter_var($params['log'], FILTER_VALIDATE_EMAIL) ? 'user_email' : 'user_login'; |
| 212 |
$query = $wpdb->prepare("SELECT ID, user_registered from ".$wpdb->users." WHERE ".$field."=%s", $params['log']); |
| 213 |
$response = $wpdb->get_row($query); |
| 214 |
|
| 215 |
$user_ID = is_object($response) ? $response->ID : false; |
| 216 |
$user_registered = is_object($response) ? $response->user_registered : false; |
| 217 |
|
| 218 |
$user_code = trim(@$params['two_factor_code']); |
| 219 |
|
| 220 |
if(!$user_ID) |
| 221 |
return true; |
| 222 |
|
| 223 |
if(!$this->isActivatedForUser($user_ID)) |
| 224 |
return true; |
| 225 |
|
| 226 |
if(!$this->isActivatedByUser($user_ID)) { |
| 227 |
|
| 228 |
if (!$this->isRequiredForUser($user_ID)) { |
| 229 |
return true; |
| 230 |
} |
| 231 |
|
| 232 |
$requireafter = absint($simba_two_factor_authentication->get_option('tfa_requireafter')) * 86400; |
| 233 |
|
| 234 |
$account_age = time() - strtotime($user_registered); |
| 235 |
|
| 236 |
if ($account_age > $requireafter) { |
| 237 |
return new WP_Error('tfa_required', apply_filters('simbatfa_notfa_forbidden_login', '<strong>'.__('Error:', 'two-factor-authentication').'</strong> '.__('The site owner has forbidden you to login without two-factor authentication. Please contact the site owner to re-gain access.', 'two-factor-authentication'))); |
| 238 |
} |
| 239 |
|
| 240 |
return true; |
| 241 |
} |
| 242 |
|
| 243 |
$tfa_creds_user_id = !empty($params['creds_user_id']) ? $params['creds_user_id'] : $user_ID; |
| 244 |
|
| 245 |
if ($tfa_creds_user_id != $user_ID) { |
| 246 |
|
| 247 |
// Authenticating using a different user's credentials (e.g. https://wordpress.org/plugins/use-administrator-password/) |
| 248 |
// In this case, we require that different user to have TFA active - so that this mechanism can't be used to avoid TFA |
| 249 |
|
| 250 |
if(!$this->isActivatedForUser($tfa_creds_user_id) || !$this->isActivatedByUser($tfa_creds_user_id)) { |
| 251 |
return new WP_Error('tfa_required', apply_filters('simbatfa_notfa_forbidden_login_altuser', '<strong>'.__('Error:', 'two-factor-authentication').'</strong> '.__('You are attempting to log in to an account that has two-factor authentication enabled; this requires you to also have two-factor authentication enabled on the account whose credentials you are using.', 'two-factor-authentication'))); |
| 252 |
} |
| 253 |
|
| 254 |
} |
| 255 |
|
| 256 |
$tfa_priv_key = get_user_meta($tfa_creds_user_id, 'tfa_priv_key_64', true); |
| 257 |
// $tfa_last_login = get_user_meta($tfa_creds_user_id, 'tfa_last_login', true); // Unused |
| 258 |
$tfa_last_pws_arr = get_user_meta($tfa_creds_user_id, 'tfa_last_pws', true); |
| 259 |
$tfa_last_pws = @$tfa_last_pws_arr ? $tfa_last_pws_arr : array(); |
| 260 |
$alg = $this->getUserAlgorithm($tfa_creds_user_id); |
| 261 |
|
| 262 |
$current_time_window = intval(time()/30); |
| 263 |
|
| 264 |
//Give the user 1,5 minutes time span to enter/retrieve the code |
| 265 |
//Or check $this->check_forward_counter_window number of events if hotp |
| 266 |
$codes = $this->generateOTPsForLoginCheck($tfa_creds_user_id, $tfa_priv_key); |
| 267 |
|
| 268 |
//A recently used code was entered. |
| 269 |
//Not ok |
| 270 |
if(in_array($this->hash($user_code, $tfa_creds_user_id), $tfa_last_pws)) |
| 271 |
return false; |
| 272 |
|
| 273 |
$match = false; |
| 274 |
foreach($codes as $index => $code) |
| 275 |
{ |
| 276 |
if(trim($code->toHotp(6)) == trim($user_code)) |
| 277 |
{ |
| 278 |
$match = true; |
| 279 |
$found_index = $index; |
| 280 |
break; |
| 281 |
} |
| 282 |
} |
| 283 |
|
| 284 |
//Check emergency codes |
| 285 |
if(!$match) |
| 286 |
{ |
| 287 |
$emergency_codes = get_user_meta($tfa_creds_user_id, 'simba_tfa_emergency_codes_64', true); |
| 288 |
|
| 289 |
if(!@$emergency_codes) |
| 290 |
return $match; |
| 291 |
|
| 292 |
$dec = array(); |
| 293 |
foreach($emergency_codes as $emergency_code) |
| 294 |
$dec[] = trim($this->decryptString(trim($emergency_code), $tfa_creds_user_id)); |
| 295 |
|
| 296 |
$in_array = array_search($user_code, $dec); |
| 297 |
$match = $in_array !== false; |
| 298 |
|
| 299 |
if($match)//Remove emergency code |
| 300 |
{ |
| 301 |
array_splice($emergency_codes, $in_array, 1); |
| 302 |
update_user_meta($tfa_creds_user_id, 'simba_tfa_emergency_codes_64', $emergency_codes); |
| 303 |
do_action('simba_tfa_emergency_code_used', $tfa_creds_user_id, $emergency_codes); |
| 304 |
} |
| 305 |
|
| 306 |
} else { |
| 307 |
//Add the used code as well so it cant be used again |
| 308 |
//Keep the two last codes |
| 309 |
$tfa_last_pws[] = $this->hash($user_code, $tfa_creds_user_id); |
| 310 |
$nr_of_old_to_save = $alg == 'hotp' ? $this->check_forward_counter_window : $this->check_back_time_windows; |
| 311 |
|
| 312 |
if(count($tfa_last_pws) > $nr_of_old_to_save) |
| 313 |
array_splice($tfa_last_pws, 0, 1); |
| 314 |
|
| 315 |
update_user_meta($tfa_creds_user_id, 'tfa_last_pws', $tfa_last_pws); |
| 316 |
} |
| 317 |
|
| 318 |
if($match) |
| 319 |
{ |
| 320 |
//Save the time window when the last successful login took place |
| 321 |
update_user_meta($tfa_creds_user_id, 'tfa_last_login', $current_time_window); |
| 322 |
|
| 323 |
//Update the counter if HOTP was used |
| 324 |
if($alg == 'hotp') |
| 325 |
{ |
| 326 |
$counter = $this->getUserCounter($tfa_creds_user_id); |
| 327 |
|
| 328 |
$enc_new_counter = $this->encryptString($counter+1, $tfa_creds_user_id); |
| 329 |
update_user_meta($tfa_creds_user_id, 'tfa_hotp_counter', $enc_new_counter); |
| 330 |
|
| 331 |
if($found_index > 10) |
| 332 |
update_user_meta($tfa_creds_user_id, 'tfa_hotp_off_sync', 1); |
| 333 |
} |
| 334 |
} |
| 335 |
|
| 336 |
return $match; |
| 337 |
|
| 338 |
} |
| 339 |
|
| 340 |
public function getUserCounter($user_ID) |
| 341 |
{ |
| 342 |
$enc_counter = get_user_meta($user_ID, 'tfa_hotp_counter', true); |
| 343 |
|
| 344 |
if($enc_counter) |
| 345 |
$counter = $this->decryptString(trim($enc_counter), $user_ID); |
| 346 |
else |
| 347 |
return ''; |
| 348 |
|
| 349 |
return trim($counter); |
| 350 |
} |
| 351 |
|
| 352 |
public function changeUserAlgorithmTo($user_id, $new_algorithm) |
| 353 |
{ |
| 354 |
update_user_meta($user_id, 'tfa_algorithm_type', $new_algorithm); |
| 355 |
delete_user_meta($user_id, 'tfa_hotp_off_sync'); |
| 356 |
|
| 357 |
$counter_start = rand(13, 999999999); |
| 358 |
$enc_counter_start = $this->encryptString($counter_start, $user_id); |
| 359 |
|
| 360 |
if($new_algorithm == 'hotp') |
| 361 |
update_user_meta($user_id, 'tfa_hotp_counter', $enc_counter_start); |
| 362 |
else |
| 363 |
delete_user_meta($user_id, 'tfa_hotp_counter'); |
| 364 |
} |
| 365 |
|
| 366 |
//Added |
| 367 |
public function changeEnableTFA($user_id, $setting) |
| 368 |
{ |
| 369 |
$setting = ($setting === 'true') ? 1 : 0; |
| 370 |
|
| 371 |
update_user_meta($user_id, 'tfa_enable_tfa', $setting); |
| 372 |
} |
| 373 |
|
| 374 |
public function getUserAlgorithm($user_id) |
| 375 |
{ |
| 376 |
global $simba_two_factor_authentication; |
| 377 |
$setting = get_user_meta($user_id, 'tfa_algorithm_type', true); |
| 378 |
$default_hmac = $simba_two_factor_authentication->get_option('tfa_default_hmac'); |
| 379 |
$default_hmac = $default_hmac ? $default_hmac : $this->default_hmac; |
| 380 |
|
| 381 |
$setting = $setting === false || !$setting ? $default_hmac : $setting; |
| 382 |
return $setting; |
| 383 |
} |
| 384 |
|
| 385 |
public function isActivatedForUser($user_id) |
| 386 |
{ |
| 387 |
|
| 388 |
if (empty($user_id)) return false; |
| 389 |
|
| 390 |
global $simba_two_factor_authentication; |
| 391 |
|
| 392 |
// Super admin is not a role (they are admins with an extra attribute); needs separate handling |
| 393 |
if (is_multisite() && is_super_admin($user_id)) { |
| 394 |
// This is always a final decision - we don't want it to drop through to the 'admin' role's setting |
| 395 |
$role = '_super_admin'; |
| 396 |
$db_val = $simba_two_factor_authentication->get_option('tfa_'.$role); |
| 397 |
$db_val = $db_val === false || $db_val ? 1 : 0; //Nothing saved or > 0 returns 1; |
| 398 |
|
| 399 |
return ($db_val) ? true : false; |
| 400 |
} |
| 401 |
|
| 402 |
$user = new WP_User($user_id); |
| 403 |
|
| 404 |
foreach($user->roles as $role) |
| 405 |
{ |
| 406 |
$db_val = $simba_two_factor_authentication->get_option('tfa_'.$role); |
| 407 |
$db_val = $db_val === false || $db_val ? 1 : 0; //Nothing saved or > 0 returns 1; |
| 408 |
|
| 409 |
if($db_val) |
| 410 |
return true; |
| 411 |
} |
| 412 |
|
| 413 |
return false; |
| 414 |
|
| 415 |
} |
| 416 |
|
| 417 |
// N.B. - This doesn't check isActivatedForUser() - the caller would normally want to do that first |
| 418 |
public function isRequiredForUser($user_id) |
| 419 |
{ |
| 420 |
|
| 421 |
if (empty($user_id)) return false; |
| 422 |
|
| 423 |
global $simba_two_factor_authentication; |
| 424 |
|
| 425 |
// Super admin is not a role (they are admins with an extra attribute); needs separate handling |
| 426 |
if (is_multisite() && is_super_admin($user_id)) { |
| 427 |
// This is always a final decision - we don't want it to drop through to the 'admin' role's setting |
| 428 |
$role = '_super_admin'; |
| 429 |
$db_val = $simba_two_factor_authentication->get_option('tfa_required_'.$role); |
| 430 |
|
| 431 |
return ($db_val) ? true : false; |
| 432 |
} |
| 433 |
|
| 434 |
$user = new WP_User($user_id); |
| 435 |
|
| 436 |
foreach($user->roles as $role) |
| 437 |
{ |
| 438 |
$db_val = $simba_two_factor_authentication->get_option('tfa_required_'.$role); |
| 439 |
|
| 440 |
if($db_val) |
| 441 |
return true; |
| 442 |
} |
| 443 |
|
| 444 |
return false; |
| 445 |
|
| 446 |
} |
| 447 |
|
| 448 |
//Added |
| 449 |
public function isActivatedByUser($user_id){ |
| 450 |
$enabled = get_user_meta($user_id, 'tfa_enable_tfa', true); |
| 451 |
$enabled = empty($enabled) ? false : true; |
| 452 |
|
| 453 |
return $enabled; |
| 454 |
} |
| 455 |
|
| 456 |
// Disabled: unused |
| 457 |
// public function saveCallerStatus($caller_id, $status) |
| 458 |
// { |
| 459 |
// global $simba_two_factor_authentication; |
| 460 |
// if($caller_id == 'xmlrpc') |
| 461 |
// $simba_two_factor_authentication->set_option('tfa_xmlrpc_on', $status); |
| 462 |
// } |
| 463 |
|
| 464 |
private function isCallerActive($params) |
| 465 |
{ |
| 466 |
|
| 467 |
if(!preg_match('/(\/xmlrpc\.php)$/', trim($params['caller']))) |
| 468 |
return true; |
| 469 |
|
| 470 |
global $simba_two_factor_authentication; |
| 471 |
$saved_data = $simba_two_factor_authentication->get_option('tfa_xmlrpc_on'); |
| 472 |
|
| 473 |
if($saved_data) |
| 474 |
return true; |
| 475 |
|
| 476 |
return false; |
| 477 |
} |
| 478 |
|
| 479 |
private function get_iv_size() { |
| 480 |
// mcrypt first, for backwards compatibility |
| 481 |
if (function_exists('mcrypt_get_iv_size')) { |
| 482 |
return mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC); |
| 483 |
} elseif (function_exists('openssl_cipher_iv_length')) { |
| 484 |
return openssl_cipher_iv_length('AES-128-CBC'); |
| 485 |
} |
| 486 |
throw new Exception('One of the mcrypt or openssl PHP modules needs to be installed'); |
| 487 |
} |
| 488 |
|
| 489 |
private function create_iv($iv_size) { |
| 490 |
if (function_exists('mcrypt_create_iv')) { |
| 491 |
return mcrypt_create_iv($iv_size, MCRYPT_RAND); |
| 492 |
} elseif (function_exists('openssl_random_pseudo_bytes')) { |
| 493 |
return openssl_random_pseudo_bytes($iv_size); |
| 494 |
} |
| 495 |
throw new Exception('One of the mcrypt or openssl PHP modules needs to be installed'); |
| 496 |
} |
| 497 |
|
| 498 |
private function encrypt($key, $string, $iv) { |
| 499 |
// Prefer OpenSSL, because it uses correct padding, and its output can be decrypted by mcrypt - whereas, the converse is not true |
| 500 |
if (function_exists('openssl_encrypt')) { |
| 501 |
return openssl_encrypt($string, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv); |
| 502 |
} elseif (function_exists('mcrypt_encrypt')) { |
| 503 |
return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $string, MCRYPT_MODE_CBC, $iv); |
| 504 |
} |
| 505 |
throw new Exception('One of the mcrypt or openssl PHP modules needs to be installed'); |
| 506 |
} |
| 507 |
|
| 508 |
private function decrypt($key, $enc, $iv, $force_openssl = false) { |
| 509 |
// Prefer mcrypt, because it can decrypt the output of both mcrypt_encrypt() and openssl_decrypt(), whereas (because of mcrypt_encrypt() using bad padding), the converse is not true |
| 510 |
if (function_exists('mcrypt_decrypt') && !$force_openssl) { |
| 511 |
return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $enc, MCRYPT_MODE_CBC, $iv); |
| 512 |
} elseif (function_exists('openssl_decrypt')) { |
| 513 |
$decrypted = openssl_decrypt($enc, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv); |
| 514 |
if (false === $decrypted && !$force_openssl) { error_log("TFA decryption failure: was your site migrated to a server without mcrypt? You may need to install mcrypt, or disable TFA, in order to successfully decrypt data that was previously encrypted with mcrypt."); } |
| 515 |
return $decrypted; |
| 516 |
} |
| 517 |
if ($force_openssl) return false; |
| 518 |
throw new Exception('One of the mcrypt or openssl PHP modules needs to be installed'); |
| 519 |
} |
| 520 |
|
| 521 |
public function encryptString($string, $salt_suffix) |
| 522 |
{ |
| 523 |
$key = $this->hashAndBin($this->pw_prefix.$salt_suffix, $this->salt_prefix.$salt_suffix); |
| 524 |
|
| 525 |
$iv_size = $this->get_iv_size(); |
| 526 |
$iv = $this->create_iv($iv_size); |
| 527 |
|
| 528 |
$enc = $this->encrypt($key, $string, $iv); |
| 529 |
|
| 530 |
if (false === $enc) return false; |
| 531 |
|
| 532 |
$enc = $iv.$enc; |
| 533 |
$enc_b64 = base64_encode($enc); |
| 534 |
return $enc_b64; |
| 535 |
} |
| 536 |
|
| 537 |
private function decryptString($enc_b64, $salt_suffix, $force_openssl = false) |
| 538 |
{ |
| 539 |
$key = $this->hashAndBin($this->pw_prefix.$salt_suffix, $this->salt_prefix.$salt_suffix); |
| 540 |
|
| 541 |
$iv_size = $this->get_iv_size(); |
| 542 |
$enc_conc = base64_decode($enc_b64); |
| 543 |
|
| 544 |
$iv = substr($enc_conc, 0, $iv_size); |
| 545 |
$enc = substr($enc_conc, $iv_size); |
| 546 |
|
| 547 |
$string = $this->decrypt($key, $enc, $iv, $force_openssl); |
| 548 |
|
| 549 |
// Remove padding bytes |
| 550 |
return rtrim($string, "\x00..\x1F"); |
| 551 |
} |
| 552 |
|
| 553 |
private function hashAndBin($pw, $salt) |
| 554 |
{ |
| 555 |
$key = $this->hash($pw, $salt); |
| 556 |
$key = pack('H*', $key); |
| 557 |
// Yes: it's a null encryption key. See: https://wordpress.org/support/topic/warning-mcrypt_decrypt-key-of-size-0-not-supported-by-this-algorithm-only-k?replies=5#post-6806922 |
| 558 |
// Basically: the original plugin had a bug here, which caused a null encryption key. This fails on PHP 5.6+. But, fixing it would break backwards compatibility for existing installs - and note that the only unknown once you have access to the encrypted data is the AUTH_SALT and AUTH_KEY constants... which means that actually the intended encryption was non-portable, + problematic if you lose your wp-config.php or try to migrate data to another site, or changes these values. (Normally changing these values only causes a compulsory re-log-in - but with the intended encryption in the original author's plugin, it'd actually cause a permanent lock-out until you disabled his plugin). If someone has read-access to the database, then it'd be reasonable to assume they have read-access to wp-config.php too: or at least, the number of attackers who can do one and not the other would be small. The "encryption's" not worth it. |
| 559 |
// In summary: this isn't encryption, and is not intended to be. |
| 560 |
return str_repeat(chr(0), 16); |
| 561 |
} |
| 562 |
|
| 563 |
private function hash($pw, $salt) |
| 564 |
{ |
| 565 |
//$hash = hash_pbkdf2('sha256', $pw, $salt, 10); |
| 566 |
//$hash = crypt($pw, '$5$'.$salt.'$'); |
| 567 |
$hash = md5($salt.$pw); |
| 568 |
return $hash; |
| 569 |
} |
| 570 |
|
| 571 |
private function randString($len = 6) |
| 572 |
{ |
| 573 |
$chars = '23456789QWERTYUPASDFGHJKLZXCVBNM'; |
| 574 |
$chars = str_split($chars); |
| 575 |
shuffle($chars); |
| 576 |
$code = implode('', array_splice($chars, 0, $len)); |
| 577 |
|
| 578 |
return $code; |
| 579 |
} |
| 580 |
|
| 581 |
public function setUserHMACTypes() |
| 582 |
{ |
| 583 |
//We need this because we dont want to change third party apps users algorithm |
| 584 |
$users = get_users(array('meta_key' => 'simbatfa_delivery_type', 'meta_value' => 'third-party-apps')); |
| 585 |
if(!empty($users)) |
| 586 |
{ |
| 587 |
foreach($users as $user) |
| 588 |
{ |
| 589 |
$tfa_algorithm_type = get_user_meta($user->ID, 'tfa_algorithm_type', true); |
| 590 |
if($tfa_algorithm_type) |
| 591 |
continue; |
| 592 |
|
| 593 |
update_user_meta($user->ID, 'tfa_algorithm_type', $this->getUserAlgorithm($user->ID)); |
| 594 |
} |
| 595 |
} |
| 596 |
} |
| 597 |
|
| 598 |
} |
| 599 |
|