| 1 |
<?php |
| 2 |
|
| 3 |
if (!defined('ABSPATH')) die('No direct access.'); |
| 4 |
|
| 5 |
if (!class_exists('HOTP')) require_once(__DIR__.'/hotp-php-master/hotp.php'); |
| 6 |
if (!class_exists('Base32')) require_once(__DIR__.'/Base32/Base32.php'); |
| 7 |
|
| 8 |
class Simba_TFA_Provider_totp { |
| 9 |
|
| 10 |
/** |
| 11 |
* Simba 2FA object |
| 12 |
* |
| 13 |
* @var object instance of Simba_Two_Factor_Authentication(_version) |
| 14 |
*/ |
| 15 |
private $tfa; |
| 16 |
|
| 17 |
/** |
| 18 |
* OTP helper object |
| 19 |
* |
| 20 |
* @var object instance of HOTP |
| 21 |
*/ |
| 22 |
private $otp_helper; |
| 23 |
|
| 24 |
/** |
| 25 |
* Forward counter window to check number of times |
| 26 |
* |
| 27 |
* @var int |
| 28 |
*/ |
| 29 |
private $check_forward_counter_window; |
| 30 |
|
| 31 |
/** |
| 32 |
* Salt prefix |
| 33 |
* |
| 34 |
* @var string |
| 35 |
*/ |
| 36 |
private $salt_prefix; |
| 37 |
|
| 38 |
/** |
| 39 |
* Password prefix |
| 40 |
* |
| 41 |
* @var string |
| 42 |
*/ |
| 43 |
private $pw_prefix; |
| 44 |
|
| 45 |
/** |
| 46 |
* Time window size |
| 47 |
* |
| 48 |
* @var int |
| 49 |
*/ |
| 50 |
private $time_window_size; |
| 51 |
|
| 52 |
/** |
| 53 |
* Check back time window |
| 54 |
* |
| 55 |
* @var int |
| 56 |
*/ |
| 57 |
private $check_back_time_windows; |
| 58 |
|
| 59 |
/** |
| 60 |
* Check forward time windows |
| 61 |
* |
| 62 |
* @var int |
| 63 |
*/ |
| 64 |
private $check_forward_time_windows; |
| 65 |
|
| 66 |
/** |
| 67 |
* OTP length |
| 68 |
* |
| 69 |
* @var int |
| 70 |
*/ |
| 71 |
private $otp_length = 6; |
| 72 |
|
| 73 |
/** |
| 74 |
* Emergency codes length |
| 75 |
* |
| 76 |
* @var int |
| 77 |
*/ |
| 78 |
private $emergency_codes_length = 8; |
| 79 |
|
| 80 |
/** |
| 81 |
* Default HMAC type |
| 82 |
* |
| 83 |
* @var string |
| 84 |
*/ |
| 85 |
public $default_hmac = 'totp'; |
| 86 |
|
| 87 |
/** |
| 88 |
* Settings saved flag |
| 89 |
* |
| 90 |
* @var boolean |
| 91 |
*/ |
| 92 |
private $settings_saved = false; |
| 93 |
|
| 94 |
/** |
| 95 |
* Class constructor |
| 96 |
* |
| 97 |
* @param Object - main Simba_Two_Factor_Authentication(_version) plugin class |
| 98 |
*/ |
| 99 |
public function __construct($tfa) { |
| 100 |
$this->tfa = $tfa; |
| 101 |
|
| 102 |
$this->otp_helper = new HOTP(); |
| 103 |
|
| 104 |
add_action('plugins_loaded', array($this, 'plugins_loaded')); |
| 105 |
|
| 106 |
add_action('admin_init', array($this, 'admin_init')); |
| 107 |
|
| 108 |
if (!is_admin()) { |
| 109 |
add_action('init', array($this, 'check_possible_reset')); |
| 110 |
} |
| 111 |
|
| 112 |
// Potentially show off-sync message for hotp |
| 113 |
add_action('admin_notices', array($this, 'tfa_show_hotp_off_sync_message')); |
| 114 |
} |
| 115 |
|
| 116 |
/** |
| 117 |
* Return whether or not this class detected and saved new settings |
| 118 |
* |
| 119 |
* @return Boolean |
| 120 |
*/ |
| 121 |
public function were_settings_saved() { |
| 122 |
return $this->settings_saved; |
| 123 |
} |
| 124 |
|
| 125 |
/** |
| 126 |
* Runs upon the WP action admin_init |
| 127 |
*/ |
| 128 |
public function admin_init() { |
| 129 |
|
| 130 |
$this->check_possible_reset(); |
| 131 |
|
| 132 |
global $current_user; |
| 133 |
|
| 134 |
if (!empty($_REQUEST['_tfa_activate_nonce']) && !empty($_POST['tfa_enable_tfa']) && wp_verify_nonce($_REQUEST['_tfa_activate_nonce'], 'tfa_activate') && !empty($_GET['settings-updated'])) { |
| 135 |
$this->tfa->change_tfa_enabled_status($current_user->ID, $_POST['tfa_enable_tfa']); |
| 136 |
$this->settings_saved = true; |
| 137 |
} |
| 138 |
|
| 139 |
if (!empty($_REQUEST['_tfa_algorithm_nonce']) && !empty($_POST['tfa_algorithm_type']) && !empty($_GET['settings-updated']) && wp_verify_nonce($_REQUEST['_tfa_algorithm_nonce'], 'tfa_algorithm')) { |
| 140 |
|
| 141 |
$old_algorithm = $this->get_user_otp_algorithm($current_user->ID); |
| 142 |
|
| 143 |
if ($old_algorithm != $_POST['tfa_algorithm_type']) { |
| 144 |
$this->changeUserAlgorithmTo($current_user->ID, $_POST['tfa_algorithm_type']); |
| 145 |
} |
| 146 |
|
| 147 |
$this->settings_saved = true; |
| 148 |
} |
| 149 |
|
| 150 |
if (!empty($_GET['warning_button_clicked']) && !empty($_REQUEST['resyncnonce']) && wp_verify_nonce($_REQUEST['resyncnonce'], 'tfaresync')) { |
| 151 |
delete_user_meta($current_user->ID, 'tfa_hotp_off_sync'); |
| 152 |
} |
| 153 |
|
| 154 |
} |
| 155 |
|
| 156 |
/** |
| 157 |
* Enqueue adding of JavaScript for footer |
| 158 |
*/ |
| 159 |
public function add_footer() { |
| 160 |
|
| 161 |
static $added_footer = false; |
| 162 |
if ($added_footer) return; |
| 163 |
$added_footer = true; |
| 164 |
|
| 165 |
$qr_script_file = (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) ? 'jquery-qrcode.js' : 'jquery-qrcode.min.js'; |
| 166 |
|
| 167 |
$qr_script_ver = (defined('WP_DEBUG') && WP_DEBUG) ? time() : filemtime($this->tfa->includes_dir()."/jquery-qrcode/$qr_script_file"); |
| 168 |
|
| 169 |
wp_register_script('jquery-qrcode', $this->tfa->includes_url()."/jquery-qrcode/$qr_script_file", array('jquery'), $qr_script_ver); |
| 170 |
|
| 171 |
$script_ver = (defined('WP_DEBUG') && WP_DEBUG) ? time() : filemtime($this->tfa->includes_dir()."/totp.js"); |
| 172 |
|
| 173 |
// Adds the necessary JavaScript for rendering and updating QR codes, and handling trusted devices removal in the admin area |
| 174 |
wp_enqueue_script('simba-tfa-totp', $this->tfa->includes_url()."/totp.js", array('jquery-qrcode'), $script_ver); |
| 175 |
|
| 176 |
wp_localize_script('simba-tfa-totp', 'simbatfa_totp', $this->translation_strings()); |
| 177 |
|
| 178 |
} |
| 179 |
|
| 180 |
/** |
| 181 |
* Get textual strings used from JavaScript |
| 182 |
* |
| 183 |
* @return Array |
| 184 |
*/ |
| 185 |
private function translation_strings() { |
| 186 |
|
| 187 |
// It's possible that FORCE_ADMIN_SSL will make that SSL, whilst the user is on the front-end having logged in over non-SSL - and as a result, their login cookies won't get sent, and they're not registered as logged in. |
| 188 |
$ajax_url = admin_url('admin-ajax.php'); |
| 189 |
$also_try = ''; |
| 190 |
if (!is_admin() && substr(strtolower($ajax_url), 0, 6) == 'https:' && !is_ssl()) { |
| 191 |
$also_try = 'http:'.substr($ajax_url, 6); |
| 192 |
} |
| 193 |
|
| 194 |
return apply_filters('simba_tfa_totp_translation_strings', array( |
| 195 |
'ajax_url' => $ajax_url, |
| 196 |
'updating' => __('Updating...', 'two-factor-authentication'), |
| 197 |
'tfa_shared_nonce' => wp_create_nonce('tfa_shared_nonce'), |
| 198 |
'also_try' => $also_try, |
| 199 |
'response' => __('Response:', 'two-factor-authentication'), |
| 200 |
)); |
| 201 |
} |
| 202 |
|
| 203 |
/** |
| 204 |
* Return a link to refresh the current OTP code |
| 205 |
* |
| 206 |
* @return String |
| 207 |
*/ |
| 208 |
public function refresh_current_otp_link() { |
| 209 |
return '<a href="#" class="simbaotp_refresh">'.__('(update)', 'two-factor-authentication').'</a>'; |
| 210 |
} |
| 211 |
|
| 212 |
/** |
| 213 |
* Echo the radio buttons for changing between TOTP/HOTP |
| 214 |
* |
| 215 |
* TODO: Hide this choice on new installs (TOTP only) |
| 216 |
* |
| 217 |
* @param Integer $user_id |
| 218 |
*/ |
| 219 |
protected function print_algorithm_choice_radios($user_id) { |
| 220 |
if (!$user_id) return; |
| 221 |
|
| 222 |
$types = array( |
| 223 |
'totp' => __('TOTP (time based - most common algorithm; used by Google Authenticator)', 'two-factor-authentication'), |
| 224 |
'hotp' => __('HOTP (event based)', 'two-factor-authentication') |
| 225 |
); |
| 226 |
|
| 227 |
$setting = $this->get_user_otp_algorithm($user_id); |
| 228 |
|
| 229 |
foreach ($types as $id => $name) { |
| 230 |
print '<input type="radio" id="tfa_algorithm_type_'.esc_attr($id).'" name="tfa_algorithm_type" value="'.$id.'" '.($setting == $id ? 'checked="checked"' :'').'> <label for="tfa_algorithm_type_'.esc_attr($id).'">'.$name."</label><br>\n"; |
| 231 |
} |
| 232 |
} |
| 233 |
|
| 234 |
/** |
| 235 |
* Print out the advanced settings box - choice of algorithm |
| 236 |
* |
| 237 |
* @param Boolean|Callable $submit_button_callback - if not a callback, then <form> tags will be added |
| 238 |
*/ |
| 239 |
public function advanced_settings_box($submit_button_callback = false) { |
| 240 |
|
| 241 |
global $current_user; |
| 242 |
$algorithm_type = $this->get_user_otp_algorithm($current_user->ID); |
| 243 |
|
| 244 |
?> |
| 245 |
<h2 id="tfa_advanced_heading" style="clear:both;"><?php _e('Advanced settings', 'two-factor-authentication'); ?></h2> |
| 246 |
|
| 247 |
<div id="tfa_advanced_box" class="tfa_settings_form" style="margin-top: 20px;"> |
| 248 |
|
| 249 |
<?php if (false === $submit_button_callback) { ?> |
| 250 |
<form method="post" action="<?php print esc_url(add_query_arg('settings-updated', 'true', $_SERVER['REQUEST_URI'])); ?>"> |
| 251 |
<?php wp_nonce_field('tfa_algorithm', '_tfa_algorithm_nonce', false, true); ?> |
| 252 |
<?php } ?> |
| 253 |
|
| 254 |
<?php _e('Choose which algorithm for One Time Passwords you want to use.', 'two-factor-authentication'); ?> |
| 255 |
<p> |
| 256 |
<?php |
| 257 |
$this->print_algorithm_choice_radios($current_user->ID); |
| 258 |
if ('hotp' == $algorithm_type) { |
| 259 |
$counter = $this->getUserCounter($current_user->ID); |
| 260 |
print '<br>'.__('Your counter on the server is currently on', 'two-factor-authentication').': '.$counter; |
| 261 |
} |
| 262 |
?> |
| 263 |
|
| 264 |
</p> |
| 265 |
<?php if (false === $submit_button_callback) { submit_button(); echo '</form>'; } else { call_user_func($submit_button_callback); } ?> |
| 266 |
|
| 267 |
</div> |
| 268 |
<?php |
| 269 |
} |
| 270 |
|
| 271 |
/** |
| 272 |
* Return an HTML snippet for the current OTP code |
| 273 |
* |
| 274 |
* @param Integer|Boolean $user_id |
| 275 |
* |
| 276 |
* @return String |
| 277 |
*/ |
| 278 |
public function current_otp_code($user_id = false) { |
| 279 |
global $current_user; |
| 280 |
if (false == $user_id) $user_id = $current_user->ID; |
| 281 |
$tfa_priv_key_64 = get_user_meta($user_id, 'tfa_priv_key_64', true); |
| 282 |
if (!$tfa_priv_key_64) $tfa_priv_key_64 = $this->addPrivateKey($user_id); |
| 283 |
$time_now = time(); |
| 284 |
$refresh_after = 30 - ($time_now % 30); |
| 285 |
return '<span class="simba_current_otp" data-refresh_after="'.$refresh_after.'">'.$this->generateOTP($user_id, $tfa_priv_key_64).'</span>'; |
| 286 |
} |
| 287 |
|
| 288 |
/** |
| 289 |
* Runs upon the WP 'init' action - check for possible private key reset request from the user |
| 290 |
*/ |
| 291 |
public function check_possible_reset() { |
| 292 |
if (!empty($_GET['simbatfa_priv_key_reset']) && !empty($_REQUEST['nonce']) && wp_verify_nonce($_REQUEST['nonce'], 'simbatfa_reset_private_key')) { |
| 293 |
$this->reset_private_key_and_emergency_codes(); |
| 294 |
exit; |
| 295 |
} |
| 296 |
} |
| 297 |
|
| 298 |
/** |
| 299 |
* Remove private key and emergency codes for the specified (or logged-in) user |
| 300 |
* |
| 301 |
* @param Boolean|Integer $user_id - WP user ID, or false for the currently logged-in user |
| 302 |
* @param Boolean $redirect - if this is not false, then a redirection will occur - where to depends upon the value of $_REQUEST['noredirect'] |
| 303 |
*/ |
| 304 |
public function reset_private_key_and_emergency_codes($user_id = false, $redirect = true) { |
| 305 |
|
| 306 |
if (!$user_id) { |
| 307 |
global $current_user; |
| 308 |
$user_id = $current_user->ID; |
| 309 |
} |
| 310 |
|
| 311 |
delete_user_meta($user_id, 'tfa_priv_key_64'); |
| 312 |
delete_user_meta($user_id, 'simba_tfa_emergency_codes_64'); |
| 313 |
|
| 314 |
if (!$redirect) return; |
| 315 |
|
| 316 |
if (empty($_REQUEST['noredirect'])) { |
| 317 |
// TODO: Re-factoring |
| 318 |
wp_safe_redirect(admin_url('admin.php').'?page='. $this->tfa->get_user_settings_page_slug() .'&settings-updated=1'); |
| 319 |
} else { |
| 320 |
$url = (is_ssl() ? 'https://' : 'http://') . $_SERVER['HTTP_HOST'] . remove_query_arg(array('simbatfa_priv_key_reset', 'noredirect', 'nonce')); |
| 321 |
wp_redirect(esc_url_raw($url)); |
| 322 |
} |
| 323 |
} |
| 324 |
|
| 325 |
/** |
| 326 |
* Return HTML for a link to reset the current user's private key |
| 327 |
* |
| 328 |
* @return String |
| 329 |
*/ |
| 330 |
public function reset_link() { |
| 331 |
|
| 332 |
// TODO: Refactoring |
| 333 |
$url_base = is_admin() ? admin_url('admin.php').'?page='. $this->tfa->get_user_settings_page_slug() .'&settings-updated=1' : (( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST']); |
| 334 |
|
| 335 |
$add_query_args = array('simbatfa_priv_key_reset' => 1); |
| 336 |
|
| 337 |
if (!is_admin()) $add_query_args['noredirect'] = 1; |
| 338 |
|
| 339 |
$url = $url_base.add_query_arg($add_query_args); |
| 340 |
|
| 341 |
$url = wp_nonce_url($url, 'simbatfa_reset_private_key', 'nonce'); |
| 342 |
|
| 343 |
return '<a href="javascript:if(confirm(\''.__('Warning: if you reset this key you will have to update your apps with the new one. Are you sure you want this?', 'two-factor-authentication').'\')) { window.location = \''.esc_js($url).'\'; }">'.__('Reset private key', 'two-factor-authentication').'</a>'; |
| 344 |
|
| 345 |
} |
| 346 |
|
| 347 |
/** |
| 348 |
* Output the current codes box |
| 349 |
* |
| 350 |
* @param Boolean|Integer $user_id |
| 351 |
*/ |
| 352 |
public function current_codes_box($user_id = false) { |
| 353 |
|
| 354 |
global $current_user; |
| 355 |
if (false == $user_id) $user_id = $current_user->ID; |
| 356 |
|
| 357 |
$admin = is_admin(); |
| 358 |
|
| 359 |
$this->add_footer(); |
| 360 |
|
| 361 |
$url = preg_replace('/^https?:\/\//i', '', site_url()); |
| 362 |
|
| 363 |
// TODO Replace this with an appropriate method |
| 364 |
$tfa_priv_key_64 = get_user_meta($user_id, 'tfa_priv_key_64', true); |
| 365 |
if (!$tfa_priv_key_64) $tfa_priv_key_64 = $this->addPrivateKey($user_id); |
| 366 |
|
| 367 |
$tfa_priv_key = trim($this->getPrivateKeyPlain($tfa_priv_key_64, $user_id), "\x00..\x1F"); |
| 368 |
|
| 369 |
$tfa_priv_key_32 = Base32::encode($tfa_priv_key); |
| 370 |
|
| 371 |
$algorithm_type = $this->get_user_otp_algorithm($user_id); |
| 372 |
|
| 373 |
if ($admin && $current_user->ID != $user_id) { |
| 374 |
$user = get_user_by('id', $user_id); |
| 375 |
$user_descrip = htmlspecialchars($user->user_nicename.' - '.$user->user_email); |
| 376 |
echo '<h2>'.sprintf(__('Current codes (login: %s)', 'two-factor-authentication'), $user_descrip).'</h2>'; |
| 377 |
} |
| 378 |
|
| 379 |
?> |
| 380 |
<div class="postbox" style="clear:both;"> |
| 381 |
|
| 382 |
<?php if ($admin) { ?> |
| 383 |
<h3 style="padding: 10px 6px 0px; margin:4px 0 0; cursor: default;"> |
| 384 |
<span style="cursor: default;"><?php echo __('Current one-time password', 'two-factor-authentication').' '; |
| 385 |
if ($current_user->ID == $user_id) { echo $this->refresh_current_otp_link(); } ?> |
| 386 |
</span> |
| 387 |
<div class="inside"> |
| 388 |
<p><strong style="font-size: 3em;"><?php echo $this->current_otp_code($user_id); ?></strong></p> |
| 389 |
</div> |
| 390 |
</h3> |
| 391 |
<?php } else { ?> |
| 392 |
|
| 393 |
<div class="inside"> |
| 394 |
<p class="simbatfa-frontend-current-otp" style="font-size: 1.5em; margin-top:6px;"> |
| 395 |
<strong><?php echo __('Current one-time password', 'two-factor-authentication').' '.$this->refresh_current_otp_link(); ?></strong> : |
| 396 |
|
| 397 |
<?php |
| 398 |
// TODO: Compare this with what's in current_otp_code() - why are we not using that? |
| 399 |
$time_now = time(); |
| 400 |
$refresh_after = 30 - ($time_now % 30); |
| 401 |
?><span class="simba_current_otp" data-refresh_after="<?php echo $refresh_after; ?>"><?php print $this->generateOTP($user_id, $tfa_priv_key_64); ?></span> |
| 402 |
|
| 403 |
</p> |
| 404 |
</div> |
| 405 |
|
| 406 |
<?php } ?> |
| 407 |
|
| 408 |
<?php if ($admin) { ?> |
| 409 |
<h3 style="padding-left: 10px; cursor: default;"> |
| 410 |
<span style="cursor: default;"><?php _e('Setting up - either scan the code, or type in the private key', 'two-factor-authentication'); ?></span> |
| 411 |
</h3> |
| 412 |
<?php } else { ?> |
| 413 |
<h2><?php _e('Setting up', 'two-factor-authentication'); ?></h2> |
| 414 |
<?php } ?> |
| 415 |
|
| 416 |
<div class="inside"> |
| 417 |
<p> |
| 418 |
<?php |
| 419 |
_e('For OTP apps that support using a camera to scan a setup code (below), that is the quickest way to set the app up (e.g. with Duo Mobile, Google Authenticator).', 'two-factor-authentication'); |
| 420 |
echo ' '; |
| 421 |
_e('Otherwise, you can type the textual private key (shown below) into your app. Always keep private keys secret.', 'two-factor-authentication'); |
| 422 |
?> |
| 423 |
|
| 424 |
<?php printf(__('You are currently using %s, %s', 'two-factor-authentication'), strtoupper($algorithm_type), ($algorithm_type == 'totp') ? __('a time based algorithm', 'two-factor-authentication') : __('an event based algorithm', 'two-factor-authentication')); ?>. |
| 425 |
</p> |
| 426 |
|
| 427 |
<?php $qr_url = $this->tfa_qr_code_url($algorithm_type, $url, $tfa_priv_key, $user_id); ?> |
| 428 |
<div style="float: left; padding-right: 20px;" class="simbaotp_qr_container" data-qrcode="<?php echo esc_attr($qr_url); ?>"></div> |
| 429 |
|
| 430 |
<p> |
| 431 |
<?php |
| 432 |
$this->print_private_keys('full', $user_id); |
| 433 |
if ($current_user->ID == $user_id) { |
| 434 |
echo $this->reset_link($admin); |
| 435 |
} else { |
| 436 |
echo '<a id="tfa-reset-privkey-for-user" data-user_id="'.$user_id.'" href="#">'.__('Reset private key', 'two-factor-authentication').'</a>'; |
| 437 |
} |
| 438 |
?> |
| 439 |
</p> |
| 440 |
|
| 441 |
<?php |
| 442 |
if ($admin || false !== apply_filters('simba_tfa_emergency_codes_user_settings', false, $user_id)) { |
| 443 |
?> |
| 444 |
|
| 445 |
<div style="min-height: 100px;"> |
| 446 |
<h3 class="normal" style="cursor: default"><?php _e('Emergency codes', 'two-factor-authentication'); ?></h3> |
| 447 |
<?php |
| 448 |
$default_text = '<a href="'.esc_url($this->tfa->get_premium_version_url()).'">'.__('One-time emergency codes are a feature of the Premium version of this plugin.', 'two-factor-authentication').'</a>'; |
| 449 |
echo apply_filters('simba_tfa_emergency_codes_user_settings', $default_text, $user_id); |
| 450 |
?> |
| 451 |
</div> |
| 452 |
|
| 453 |
<?php } ?> |
| 454 |
</div> |
| 455 |
|
| 456 |
</div> |
| 457 |
<?php |
| 458 |
} |
| 459 |
|
| 460 |
/** |
| 461 |
* Print out HTML showing the specified user's private key |
| 462 |
* |
| 463 |
* @param String $type |
| 464 |
* @param Boolean|Integer $user_id |
| 465 |
*/ |
| 466 |
public function print_private_keys($type = 'full', $user_id = false) { |
| 467 |
|
| 468 |
global $current_user; |
| 469 |
if ($user_id == false) $user_id = $current_user->ID; |
| 470 |
|
| 471 |
$tfa_priv_key_64 = get_user_meta($user_id, 'tfa_priv_key_64', true); |
| 472 |
if (!$tfa_priv_key_64) $tfa_priv_key_64 = $this->addPrivateKey($user_id); |
| 473 |
|
| 474 |
$tfa_priv_key = trim($this->getPrivateKeyPlain($tfa_priv_key_64, $user_id), "\x00..\x1F"); |
| 475 |
|
| 476 |
$tfa_priv_key_32 = Base32::encode($tfa_priv_key); |
| 477 |
|
| 478 |
// The first (base32) private key used to have the description "base 32 - used by Google Authenticator and Authy", and the base64 version was just described as "private key". But basically the former is what everything uses. |
| 479 |
//<strong>Private key:</strong> htmlspecialchars($tfa_priv_key) |
| 480 |
if ('full' == $type) { |
| 481 |
?> |
| 482 |
<strong><?php _e('Private key:', 'two-factor-authentication');?></strong> |
| 483 |
<?php echo htmlspecialchars($tfa_priv_key_32); ?><br> |
| 484 |
<?php |
| 485 |
} elseif ('plain' == $type) { |
| 486 |
echo htmlspecialchars($tfa_priv_key); |
| 487 |
} elseif ('base32' == $type) { |
| 488 |
echo htmlspecialchars($tfa_priv_key_32); |
| 489 |
} elseif ('base64' == $type) { |
| 490 |
echo htmlspecialchars($tfa_priv_key_64); |
| 491 |
} |
| 492 |
} |
| 493 |
|
| 494 |
/** |
| 495 |
* Return the URL for a QR code image |
| 496 |
* |
| 497 |
* @param String $algorithm_type - 'totp' or 'hotp' |
| 498 |
* @param String $url |
| 499 |
* @param String $tfa_priv_key |
| 500 |
* @param Boolean|Integer $user_id |
| 501 |
* |
| 502 |
* @return String |
| 503 |
*/ |
| 504 |
public function tfa_qr_code_url($algorithm_type, $url, $tfa_priv_key, $user_id = false) { |
| 505 |
global $current_user; |
| 506 |
|
| 507 |
$user = (false == $user_id) ? $current_user : get_user_by('id', $user_id); |
| 508 |
|
| 509 |
$encode = 'otpauth://'.$algorithm_type.'/'.$url.':'.rawurlencode($user->user_login).'?secret='.Base32::encode($tfa_priv_key).'&issuer='.$url.'&counter='.$this->getUserCounter($user->ID); |
| 510 |
|
| 511 |
return $encode; |
| 512 |
} |
| 513 |
|
| 514 |
/** |
| 515 |
* See if HOTP is off sync, and if show, print out a message |
| 516 |
*/ |
| 517 |
public function tfa_show_hotp_off_sync_message() { |
| 518 |
|
| 519 |
global $current_user; |
| 520 |
$is_off_sync = get_user_meta($current_user->ID, 'tfa_hotp_off_sync', true); |
| 521 |
if (!$is_off_sync) return; |
| 522 |
|
| 523 |
?> |
| 524 |
<div class="error"> |
| 525 |
<h3><?php _e('Two Factor Authentication re-sync needed', 'two-factor-authentication');?></h3> |
| 526 |
<p> |
| 527 |
<?php _e('You need to resync your device for Two Factor Authentication since the OTP you last used is many steps ahead of the server.', 'two-factor-authentication'); ?> |
| 528 |
<br> |
| 529 |
<?php _e('Please re-sync or you might not be able to log in if you generate more OTPs without logging in.', 'two-factor-authentication');?> |
| 530 |
<br><br> |
| 531 |
<a href="<?php echo esc_url(wp_nonce_url('admin.php?page='. $this->tfa->get_user_settings_page_slug() .'&warning_button_clicked=1', 'tfaresync', 'resyncnonce')); ?>" class="button"><?php _e('Click here and re-scan the QR-Code', 'two-factor-authentication');?></a> |
| 532 |
</p> |
| 533 |
</div> |
| 534 |
|
| 535 |
<?php |
| 536 |
|
| 537 |
} |
| 538 |
|
| 539 |
/** |
| 540 |
* Runs upon the WP action plugins_loaded |
| 541 |
*/ |
| 542 |
public function plugins_loaded() { |
| 543 |
$this->time_window_size = apply_filters('simbatfa_time_window_size', 30); |
| 544 |
$this->check_back_time_windows = apply_filters('simbatfa_check_back_time_windows', 2); |
| 545 |
$this->check_forward_time_windows = apply_filters('simbatfa_check_forward_time_windows', 1); |
| 546 |
$this->check_forward_counter_window = apply_filters('simbatfa_check_forward_counter_window', 20); |
| 547 |
|
| 548 |
$this->salt_prefix = defined('AUTH_SALT') ? AUTH_SALT : wp_salt('auth'); |
| 549 |
$this->pw_prefix = defined('AUTH_KEY') ? AUTH_KEY : get_site_option('auth_key'); |
| 550 |
} |
| 551 |
|
| 552 |
/** |
| 553 |
* Generate the current code for a specified user |
| 554 |
* |
| 555 |
* @param $user_id Integer - WordPress user ID |
| 556 |
* |
| 557 |
* @return String|Boolean - false if not set up |
| 558 |
*/ |
| 559 |
public function get_current_code($user_id) { |
| 560 |
|
| 561 |
$tfa_priv_key_64 = get_user_meta($user_id, 'tfa_priv_key_64', true); |
| 562 |
|
| 563 |
if (!$tfa_priv_key_64) return false; |
| 564 |
|
| 565 |
return $this->generateOTP($user_id, $tfa_priv_key_64); |
| 566 |
|
| 567 |
} |
| 568 |
|
| 569 |
public function print_default_hmac_radios() { |
| 570 |
|
| 571 |
$setting = $this->tfa->get_option('tfa_default_hmac'); |
| 572 |
if (!$setting) $setting = $this->default_hmac; |
| 573 |
|
| 574 |
$types = array('totp' => __('TOTP (time based - most common algorithm; used by Google Authenticator)', 'two-factor-authentication'), 'hotp' => __('HOTP (event based)', 'two-factor-authentication')); |
| 575 |
|
| 576 |
foreach ($types as $id => $name) { |
| 577 |
print '<input type="radio" id="tfa_default_hmac_'.esc_attr($id).'" name="tfa_default_hmac" value="'.$id.'" '.($setting == $id ? 'checked="checked"' :'').'> '.'<label for="tfa_default_hmac_'.esc_attr($id).'">'."$name</label><br>\n"; |
| 578 |
} |
| 579 |
} |
| 580 |
|
| 581 |
public function generateOTP($user_ID, $key_b64, $length = 6, $counter = false) { |
| 582 |
|
| 583 |
$length = $length ? (int)$length : 6; |
| 584 |
|
| 585 |
$key = $this->decryptString($key_b64, $user_ID); |
| 586 |
$alg = $this->get_user_otp_algorithm($user_ID); |
| 587 |
|
| 588 |
if ('hotp' == $alg) { |
| 589 |
$db_counter = $this->getUserCounter($user_ID); |
| 590 |
|
| 591 |
$counter = $counter ? $counter : $db_counter; |
| 592 |
$otp_res = $this->otp_helper->generateByCounter($key, $counter); |
| 593 |
} else { |
| 594 |
//time() is supposed to be UTC |
| 595 |
$time = $counter ? $counter : time(); |
| 596 |
$otp_res = $this->otp_helper->generateByTime($key, $this->time_window_size, $time); |
| 597 |
} |
| 598 |
$code = $otp_res->toHotp($length); |
| 599 |
|
| 600 |
return $code; |
| 601 |
} |
| 602 |
|
| 603 |
/** |
| 604 |
* Generate a list of OTP codes based on the user, key and time window |
| 605 |
* |
| 606 |
* @param Integer $user_ID - user ID |
| 607 |
* @param String $key_b64 - the user's private key, in base64 format |
| 608 |
* |
| 609 |
* @return Array |
| 610 |
*/ |
| 611 |
private function generate_otps_for_login_check($user_ID, $key_b64) { |
| 612 |
$key = trim($this->decryptString($key_b64, $user_ID)); |
| 613 |
$alg = $this->get_user_otp_algorithm($user_ID); |
| 614 |
|
| 615 |
if ('totp' == $alg) { |
| 616 |
$otp_res = $this->otp_helper->generateByTimeWindow($key, $this->time_window_size, -1*$this->check_back_time_windows, $this->check_forward_time_windows); |
| 617 |
} elseif ('hotp' == $alg) { |
| 618 |
|
| 619 |
$counter = $this->getUserCounter($user_ID); |
| 620 |
|
| 621 |
$otp_res = array(); |
| 622 |
|
| 623 |
for ($i = 0; $i < $this->check_forward_counter_window; $i++) { |
| 624 |
$otp_res[] = $this->otp_helper->generateByCounter($key, $counter+$i); |
| 625 |
} |
| 626 |
} |
| 627 |
return $otp_res; |
| 628 |
} |
| 629 |
|
| 630 |
|
| 631 |
/** |
| 632 |
* Generate a private key for the user. |
| 633 |
* |
| 634 |
* @param Integer $user_id - WordPress user ID |
| 635 |
* @param Boolean|String $key |
| 636 |
* |
| 637 |
* @return String |
| 638 |
*/ |
| 639 |
public function addPrivateKey($user_id, $key = false) { |
| 640 |
|
| 641 |
// To work with Google Authenticator it has to be 10 bytes = 16 chars in base32 |
| 642 |
$code = $key ? $key : strtoupper($this->randString(10)); |
| 643 |
|
| 644 |
// Encrypt the key |
| 645 |
$code = $this->encryptString($code, $user_id); |
| 646 |
|
| 647 |
// Add private key to usermeta |
| 648 |
update_user_meta($user_id, 'tfa_priv_key_64', $code); |
| 649 |
|
| 650 |
$alg = $this->get_user_otp_algorithm($user_id); |
| 651 |
|
| 652 |
// This hook is used for generation of emergency codes to accompany the key |
| 653 |
do_action('simba_tfa_adding_private_key', $alg, $user_id, $code, $this); |
| 654 |
|
| 655 |
$this->changeUserAlgorithmTo($user_id, $alg); |
| 656 |
|
| 657 |
return $code; |
| 658 |
} |
| 659 |
|
| 660 |
/** |
| 661 |
* 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 |
| 662 |
*/ |
| 663 |
public function potentially_port_private_keys() { |
| 664 |
|
| 665 |
$simba_tfa_priv_key_format = get_site_option('simba_tfa_priv_key_format', false); |
| 666 |
|
| 667 |
if ($simba_tfa_priv_key_format >= 1 || !function_exists('openssl_encrypt')) return; |
| 668 |
|
| 669 |
$attempts = 0; |
| 670 |
$successes = 0; |
| 671 |
|
| 672 |
error_log("TFA: Beginning attempt to port private key encryption over to openssl"); |
| 673 |
|
| 674 |
global $wpdb; |
| 675 |
|
| 676 |
$sql = "SELECT user_id, meta_value FROM ".$wpdb->usermeta." WHERE meta_key = 'tfa_priv_key_64'"; |
| 677 |
|
| 678 |
$user_results = $wpdb->get_results($sql); |
| 679 |
|
| 680 |
foreach ($user_results as $u) { |
| 681 |
$dec_openssl = $this->decryptString($u->meta_value, $u->user_id, true); |
| 682 |
|
| 683 |
$ported = false; |
| 684 |
if ('' == $dec_openssl) { |
| 685 |
|
| 686 |
$attempts++; |
| 687 |
|
| 688 |
$dec_default = $this->decryptString($u->meta_value, $u->user_id); |
| 689 |
|
| 690 |
if ('' != $dec_default) { |
| 691 |
|
| 692 |
$enc = $this->encryptString($dec_default, $u->user_id); |
| 693 |
|
| 694 |
if ($enc) { |
| 695 |
|
| 696 |
$ported = true; |
| 697 |
$successes++; |
| 698 |
update_user_meta($u->user_id, 'tfa_priv_key_64', $enc); |
| 699 |
} |
| 700 |
} |
| 701 |
|
| 702 |
} |
| 703 |
|
| 704 |
if ($ported) { |
| 705 |
error_log("TFA: Successfully ported the key for user with ID ".$u->user_id." over to openssl"); |
| 706 |
} else { |
| 707 |
error_log("TFA: Failed to port the key for user with ID ".$u->user_id." over to openssl"); |
| 708 |
} |
| 709 |
} |
| 710 |
|
| 711 |
if ($attempts == 0 || $successes > 0) update_site_option('simba_tfa_priv_key_format', 1); |
| 712 |
|
| 713 |
} |
| 714 |
|
| 715 |
public function getPrivateKeyPlain($enc, $user_ID) { |
| 716 |
$dec = $this->decryptString($enc, $user_ID); |
| 717 |
$this->potentially_port_private_keys(); |
| 718 |
return $dec; |
| 719 |
} |
| 720 |
|
| 721 |
/** |
| 722 |
* @param Integer $user_id - WP user ID |
| 723 |
* @param Boolean $generate_if_empty - generate some new codes if the list is empty |
| 724 |
* |
| 725 |
* @return String - human-usable codes, separated by ', ' (or a human-readable message, if there were none) |
| 726 |
*/ |
| 727 |
public function get_emergency_codes_as_string($user_id, $generate_if_empty = false) { |
| 728 |
|
| 729 |
$codes = get_user_meta($user_id, 'simba_tfa_emergency_codes_64', true); |
| 730 |
if (!is_array($codes)) $codes = array(); |
| 731 |
|
| 732 |
if ($generate_if_empty && empty($codes)) { |
| 733 |
$tfa_priv_key = get_user_meta($user_id, 'tfa_priv_key_64', true); |
| 734 |
$algorithm = get_user_meta($user_id, 'tfa_algorithm_type', true); |
| 735 |
do_action('simba_tfa_emergency_codes_empty', $algorithm, $user_id, $tfa_priv_key, $this); |
| 736 |
$codes = get_user_meta($user_id, 'simba_tfa_emergency_codes_64', true); |
| 737 |
if (!is_array($codes)) $codes = array(); |
| 738 |
} |
| 739 |
|
| 740 |
$emergency_str = ''; |
| 741 |
|
| 742 |
foreach ($codes as $p_code) { |
| 743 |
$emergency_str .= $this->decryptString($p_code, $user_id).', '; |
| 744 |
} |
| 745 |
|
| 746 |
$emergency_str = rtrim($emergency_str, ', '); |
| 747 |
|
| 748 |
$emergency_str = $emergency_str ? $emergency_str : '<em>'.__('There are no emergency codes left. You will need to reset your private key.', 'two-factor-authentication').'</em>'; |
| 749 |
|
| 750 |
return $emergency_str; |
| 751 |
} |
| 752 |
|
| 753 |
/** |
| 754 |
* Check a code for a user (checks the code only - does not check activation status etc.) |
| 755 |
* |
| 756 |
* @param Integer $user_id - WP user ID |
| 757 |
* @param String $user_code - the code to check |
| 758 |
* @param Boolean $allow_emergency_code - whether to check against emergency codes |
| 759 |
* |
| 760 |
* @return Boolean |
| 761 |
*/ |
| 762 |
public function check_code_for_user($user_id, $user_code, $allow_emergency_code = true) { |
| 763 |
|
| 764 |
$tfa_priv_key = get_user_meta($user_id, 'tfa_priv_key_64', true); |
| 765 |
// $tfa_last_login = get_user_meta($user_id, 'tfa_last_login', true); // Unused |
| 766 |
$tfa_last_pws_arr = get_user_meta($user_id, 'tfa_last_pws', true); |
| 767 |
$tfa_last_pws = @$tfa_last_pws_arr ? $tfa_last_pws_arr : array(); |
| 768 |
$alg = $this->get_user_otp_algorithm($user_id); |
| 769 |
|
| 770 |
$current_time_window = intval(time()/30); |
| 771 |
|
| 772 |
//Give the user 1,5 minutes time span to enter/retrieve the code |
| 773 |
//Or check $this->check_forward_counter_window number of events if hotp |
| 774 |
$codes = $this->generate_otps_for_login_check($user_id, $tfa_priv_key); |
| 775 |
|
| 776 |
//A recently used code was entered; that's not OK. |
| 777 |
if (in_array($this->hash($user_code, $user_id), $tfa_last_pws)) return false; |
| 778 |
|
| 779 |
$match = false; |
| 780 |
foreach ($codes as $index => $code) { |
| 781 |
if (trim($code->toHotp(6)) == trim($user_code)) { |
| 782 |
$match = true; |
| 783 |
$found_index = $index; |
| 784 |
break; |
| 785 |
} |
| 786 |
} |
| 787 |
|
| 788 |
// Check emergency codes |
| 789 |
if (!$match) { |
| 790 |
$emergency_codes = $allow_emergency_code ? get_user_meta($user_id, 'simba_tfa_emergency_codes_64', true) : array(); |
| 791 |
|
| 792 |
if (!$emergency_codes) return $match; |
| 793 |
|
| 794 |
$dec = array(); |
| 795 |
foreach ($emergency_codes as $emergency_code) |
| 796 |
$dec[] = trim($this->decryptString(trim($emergency_code), $user_id)); |
| 797 |
|
| 798 |
$in_array = array_search($user_code, $dec); |
| 799 |
$match = $in_array !== false; |
| 800 |
|
| 801 |
//Remove emergency code |
| 802 |
if ($match) { |
| 803 |
array_splice($emergency_codes, $in_array, 1); |
| 804 |
update_user_meta($user_id, 'simba_tfa_emergency_codes_64', $emergency_codes); |
| 805 |
do_action('simba_tfa_emergency_code_used', $user_id, $emergency_codes); |
| 806 |
} |
| 807 |
|
| 808 |
} else { |
| 809 |
//Add the used code as well so it cant be used again |
| 810 |
//Keep the two last codes |
| 811 |
$tfa_last_pws[] = $this->hash($user_code, $user_id); |
| 812 |
$nr_of_old_to_save = $alg == 'hotp' ? $this->check_forward_counter_window : $this->check_back_time_windows; |
| 813 |
|
| 814 |
if (count($tfa_last_pws) > $nr_of_old_to_save) array_splice($tfa_last_pws, 0, 1); |
| 815 |
|
| 816 |
update_user_meta($user_id, 'tfa_last_pws', $tfa_last_pws); |
| 817 |
} |
| 818 |
|
| 819 |
if ($match) { |
| 820 |
//Save the time window when the last successful login took place |
| 821 |
update_user_meta($user_id, 'tfa_last_login', $current_time_window); |
| 822 |
|
| 823 |
//Update the counter if HOTP was used |
| 824 |
if ($alg == 'hotp') { |
| 825 |
$counter = $this->getUserCounter($user_id); |
| 826 |
|
| 827 |
$enc_new_counter = $this->encryptString($counter+1, $user_id); |
| 828 |
update_user_meta($user_id, 'tfa_hotp_counter', $enc_new_counter); |
| 829 |
|
| 830 |
if ($found_index > 10) update_user_meta($user_id, 'tfa_hotp_off_sync', 1); |
| 831 |
} |
| 832 |
} |
| 833 |
|
| 834 |
return $match; |
| 835 |
|
| 836 |
} |
| 837 |
|
| 838 |
public function getUserCounter($user_ID) { |
| 839 |
$enc_counter = get_user_meta($user_ID, 'tfa_hotp_counter', true); |
| 840 |
return $enc_counter ? trim($this->decryptString(trim($enc_counter), $user_ID)) : ''; |
| 841 |
} |
| 842 |
|
| 843 |
public function changeUserAlgorithmTo($user_id, $new_algorithm) { |
| 844 |
update_user_meta($user_id, 'tfa_algorithm_type', $new_algorithm); |
| 845 |
delete_user_meta($user_id, 'tfa_hotp_off_sync'); |
| 846 |
|
| 847 |
$counter_start = rand(13, 999999999); |
| 848 |
$enc_counter_start = $this->encryptString($counter_start, $user_id); |
| 849 |
|
| 850 |
if ('hotp' == $new_algorithm) { |
| 851 |
update_user_meta($user_id, 'tfa_hotp_counter', $enc_counter_start); |
| 852 |
} else { |
| 853 |
delete_user_meta($user_id, 'tfa_hotp_counter'); |
| 854 |
} |
| 855 |
} |
| 856 |
|
| 857 |
/** |
| 858 |
* Whether HOTP or TOTP is being used |
| 859 |
* |
| 860 |
* @param Integer|Boolean $user_id - WordPress user ID, or false for the site-wide default |
| 861 |
* |
| 862 |
* @return String - 'hotp' or 'totp' |
| 863 |
*/ |
| 864 |
public function get_user_otp_algorithm($user_id = false) { |
| 865 |
|
| 866 |
$setting = $user_id ? get_user_meta($user_id, 'tfa_algorithm_type', true) : false; |
| 867 |
|
| 868 |
$default_hmac = $this->tfa->get_option('tfa_default_hmac'); |
| 869 |
if (!$default_hmac) $default_hmac = $this->default_hmac; |
| 870 |
|
| 871 |
return $setting ? $setting : $default_hmac; |
| 872 |
} |
| 873 |
|
| 874 |
private function get_iv_size() { |
| 875 |
// mcrypt first, for backwards compatibility |
| 876 |
if (function_exists('mcrypt_get_iv_size')) { |
| 877 |
return $GLOBALS['simba_two_factor_authentication']->is_mcrypt_deprecated() ? @mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC) : mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC); |
| 878 |
} elseif (function_exists('openssl_cipher_iv_length')) { |
| 879 |
return openssl_cipher_iv_length('AES-128-CBC'); |
| 880 |
} |
| 881 |
throw new Exception('One of the mcrypt or openssl PHP modules needs to be installed'); |
| 882 |
} |
| 883 |
|
| 884 |
private function encrypt($key, $string, $iv) { |
| 885 |
// Prefer OpenSSL, because it uses correct padding, and its output can be decrypted by mcrypt - whereas, the converse is not true |
| 886 |
if (function_exists('openssl_encrypt')) { |
| 887 |
return openssl_encrypt($string, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv); |
| 888 |
} elseif (function_exists('mcrypt_encrypt')) { |
| 889 |
return $GLOBALS['simba_two_factor_authentication']->is_mcrypt_deprecated() ? @mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $string, MCRYPT_MODE_CBC, $iv) : mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $string, MCRYPT_MODE_CBC, $iv); |
| 890 |
} |
| 891 |
throw new Exception('One of the mcrypt or openssl PHP modules needs to be installed'); |
| 892 |
} |
| 893 |
|
| 894 |
private function decrypt($key, $enc, $iv, $force_openssl = false) { |
| 895 |
// 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 |
| 896 |
if (function_exists('mcrypt_decrypt') && !$force_openssl) { |
| 897 |
return $GLOBALS['simba_two_factor_authentication']->is_mcrypt_deprecated() ? @mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $enc, MCRYPT_MODE_CBC, $iv) : mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $enc, MCRYPT_MODE_CBC, $iv); |
| 898 |
} elseif (function_exists('openssl_decrypt')) { |
| 899 |
$decrypted = openssl_decrypt($enc, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv); |
| 900 |
if (false === $decrypted && !$force_openssl) { |
| 901 |
$extra = function_exists('wp_debug_backtrace_summary') ? " backtrace: ".wp_debug_backtrace_summary() : ''; |
| 902 |
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.$extra"); |
| 903 |
} |
| 904 |
return $decrypted; |
| 905 |
} |
| 906 |
if ($force_openssl) return false; |
| 907 |
throw new Exception('One of the mcrypt or openssl PHP modules needs to be installed'); |
| 908 |
} |
| 909 |
|
| 910 |
public function encryptString($string, $salt_suffix) { |
| 911 |
$key = $this->hashAndBin($this->pw_prefix.$salt_suffix, $this->salt_prefix.$salt_suffix); |
| 912 |
|
| 913 |
$iv_size = $this->get_iv_size(); |
| 914 |
$iv = $GLOBALS['simba_two_factor_authentication']->random_bytes($iv_size); |
| 915 |
|
| 916 |
$enc = $this->encrypt($key, $string, $iv); |
| 917 |
|
| 918 |
if (false === $enc) return false; |
| 919 |
|
| 920 |
$enc = $iv.$enc; |
| 921 |
$enc_b64 = base64_encode($enc); |
| 922 |
return $enc_b64; |
| 923 |
} |
| 924 |
|
| 925 |
private function decryptString($enc_b64, $salt_suffix, $force_openssl = false) { |
| 926 |
$key = $this->hashAndBin($this->pw_prefix.$salt_suffix, $this->salt_prefix.$salt_suffix); |
| 927 |
|
| 928 |
$iv_size = $this->get_iv_size(); |
| 929 |
$enc_conc = bin2hex(base64_decode($enc_b64)); |
| 930 |
|
| 931 |
$iv = hex2bin(substr($enc_conc, 0, $iv_size*2)); |
| 932 |
$enc = hex2bin(substr($enc_conc, $iv_size*2)); |
| 933 |
|
| 934 |
$string = $this->decrypt($key, $enc, $iv, $force_openssl); |
| 935 |
|
| 936 |
// Remove padding bytes |
| 937 |
return rtrim($string, "\x00..\x1F"); |
| 938 |
} |
| 939 |
|
| 940 |
private function hashAndBin($pw, $salt) { |
| 941 |
$key = $this->hash($pw, $salt); |
| 942 |
$key = pack('H*', $key); |
| 943 |
// 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 |
| 944 |
// 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. |
| 945 |
// In summary: this isn't encryption, and is not intended to be. |
| 946 |
return str_repeat(chr(0), 16); |
| 947 |
} |
| 948 |
|
| 949 |
private function hash($pw, $salt) { |
| 950 |
//$hash = hash_pbkdf2('sha256', $pw, $salt, 10); |
| 951 |
//$hash = crypt($pw, '$5$'.$salt.'$'); |
| 952 |
$hash = md5($salt.$pw); |
| 953 |
return $hash; |
| 954 |
} |
| 955 |
|
| 956 |
private function randString($len = 10) { |
| 957 |
$chars = '23456789QWERTYUPASDFGHJKLZXCVBNM'; |
| 958 |
$chars = str_split($chars); |
| 959 |
shuffle($chars); |
| 960 |
if (function_exists('random_int')) { |
| 961 |
$code = ''; |
| 962 |
for ($i = 1; $i <= $len; $i++) { |
| 963 |
$code .= $chars[random_int(0, count($chars)-1)]; |
| 964 |
} |
| 965 |
} else { |
| 966 |
$code = implode('', array_splice($chars, 0, $len)); |
| 967 |
} |
| 968 |
return $code; |
| 969 |
} |
| 970 |
|
| 971 |
public function setUserHMACTypes() { |
| 972 |
trigger_error("Deprecated: setUserHMACTypes() does nothing: remove any calls to it"); |
| 973 |
} |
| 974 |
|
| 975 |
} |
| 976 |
|